All files / clipboard.ts

90.91% Branches 10/11
100.00% Lines 95/95
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
x1
 
x4
 
 
x4
x4
x4
x349
x349
x349
x349
 
x4
x4
 
x4
x4
 
x4
x4
x24
x8
x10
x10
x10
x8
 
x4
x4
x24
x8
x10
x10
x10
x10
x8
 
x4
x4
x6
x6
x7
x7
x7
x7
x7
x6
 
x4
x4
x30
x6
x4
 
 
 
 
x4
x4
x4
x14
x15
x15
x23
x23
x14
x14
 
x4
 
x14
x14
x18
x18
 
x14
x16
x16
 
x14
 
x14
x14
x17
x18
x18
x19
x76
x17
 
x14
x14
x18
x18
 
x14
x16
x16
 
x4
 
x4
x4
x4
x4
x40
x8
x4
 
x4
x4
x4
x4
x4
x5
x5
x4






















































































I




























// Imports
import type { Promisable } from "@libs/typing"
import { type _Clipboard, type _ClipboardEvent, type _ClipboardItem, illegal, type internal, unimplemented } from "./_.ts"
import type { Navigator } from "./navigator.ts"

/** https://developer.mozilla.org/en-US/docs/Web/API/Clipboard */
export class Clipboard extends EventTarget implements _Clipboard {
  constructor({ navigator } = {} as { [internal]?: boolean; navigator?: Navigator }) {
    super()
    illegal(arguments)
    this.#navigator = navigator!
  }

  /** {@link Navigator} */
  readonly #navigator: Navigator

  /** {@link ClipboardItem} */
  readonly #items = [] as ClipboardItem[]

  // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/read
  async read(): Promise<ClipboardItem[]> {
    const permission = await this.#navigator.permissions.query({ name: "clipboard-read" as PermissionName })
    if (permission.state !== "granted") {
      throw new DOMException(`Clipboard read operation is not allowed.`, "NotAllowedError")
    }
    return this.#items.slice()
  }

  // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write
  async write(items: ClipboardItem[]): Promise<void> {
    const permission = await this.#navigator.permissions.query({ name: "clipboard-write" as PermissionName })
    if (permission.state !== "granted") {
      throw new DOMException(`Clipboard write operation is not allowed.`, "NotAllowedError")
    }
    this.#items.length = 0
    this.#items.push(...items)
  }

  // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText
  async readText(): Promise<string> {
    let text = ""
    for (const item of await this.read()) {
      if (item.types.includes("text/plain")) {
        text += await item.getType("text/plain").then((blob) => blob.text())
      }
    }
    return text
  }

  // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
  async writeText(text: string): Promise<void> {
    await this.write([new ClipboardItem({ "text/plain": text })])
  }
}

/** {@link ClipboardItem}.presentationStyle allowed values. */
type ClipboardItemPresentationStyle = "unspecified" | "inline" | "attachment"

/** https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem */
export class ClipboardItem implements _ClipboardItem {
  constructor(data: { [mime: string]: Promisable<Blob | string> }, options?: { presentationStyle?: ClipboardItemPresentationStyle }) {
    if (!Object.keys(data).length) {
      throw new TypeError("At least one entry required")
    }
    this.#data = data
    this.#types = Object.keys(data)
    this.#presentationStyle = options?.presentationStyle ?? "unspecified"
  }

  readonly #data

  // https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/types
  get types(): string[] {
    return this.#types
  }

  set types(_: string[]) {
    return
  }

  readonly #types

  // https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/getType
  async getType(type: string): Promise<Blob> {
    if (!this.#data[type]) {
      throw new DOMException(`The type '${type}' was not found`, "NotFoundError")
    }
    const data = await this.#data[type]
    return (typeof data === "string") ? new Blob([data], { type }) : data
  }

  // https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/presentationStyle
  get presentationStyle(): ClipboardItemPresentationStyle {
    return this.#presentationStyle
  }

  set presentationStyle(_: ClipboardItemPresentationStyle) {
    return
  }

  readonly #presentationStyle

  // https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/supports_static
  // Note: plain text, HTML and PNG files are mandatory
  // SVG and custom types support is optional
  static supports(type: string): boolean {
    return ["text/plain", "text/html", "image/png"].includes(type)
  }
}

/** https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent */
export class ClipboardEvent extends Event implements _ClipboardEvent {
  // https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData
  // Depends: DataTransfer implementation
  get clipboardData(): DataTransfer {
    return unimplemented.getter<"immutable">()
  }
}