Skip to content

Commit

Permalink
Merge pull request #1116 from capricorn86/task/833-add-navigatorclipb…
Browse files Browse the repository at this point in the history
…oard

Task/833 add navigatorclipboard
  • Loading branch information
capricorn86 committed Oct 3, 2023
2 parents 410beb0 + 02002a6 commit 5394f83
Show file tree
Hide file tree
Showing 26 changed files with 1,324 additions and 245 deletions.
96 changes: 96 additions & 0 deletions packages/happy-dom/src/clipboard/Clipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import DOMException from '../exception/DOMException.js';
import IWindow from '../window/IWindow.js';
import ClipboardItem from './ClipboardItem.js';
import Blob from '../file/Blob.js';

/**
* Clipboard API.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/Clipboard.
*/
export default class Clipboard {
#ownerWindow: IWindow;
#data: ClipboardItem[] = [];

/**
* Constructor.
*
* @param ownerWindow Owner window.
*/
constructor(ownerWindow: IWindow) {
this.#ownerWindow = ownerWindow;
}

/**
* Returns data.
*
* @returns Data.
*/
public async read(): Promise<ClipboardItem[]> {
const permissionStatus = await this.#ownerWindow.navigator.permissions.query({
name: 'clipboard-read'
});
if (permissionStatus.state === 'denied') {
throw new DOMException(`Failed to execute 'read' on 'Clipboard': The request is not allowed`);
}
return this.#data;
}

/**
* Returns text.
*
* @returns Text.
*/
public async readText(): Promise<string> {
const permissionStatus = await this.#ownerWindow.navigator.permissions.query({
name: 'clipboard-read'
});
if (permissionStatus.state === 'denied') {
throw new DOMException(
`Failed to execute 'readText' on 'Clipboard': The request is not allowed`
);
}
let text = '';
for (const item of this.#data) {
if (item.types.includes('text/plain')) {
text += await (await item.getType('text/plain')).text();
}
}
return text;
}

/**
* Writes data.
*
* @param data Data.
*/
public async write(data: ClipboardItem[]): Promise<void> {
const permissionStatus = await this.#ownerWindow.navigator.permissions.query({
name: 'clipboard-write'
});
if (permissionStatus.state === 'denied') {
throw new DOMException(
`Failed to execute 'write' on 'Clipboard': The request is not allowed`
);
}
this.#data = data;
}

/**
* Writes text.
*
* @param text Text.
*/
public async writeText(text: string): Promise<void> {
const permissionStatus = await this.#ownerWindow.navigator.permissions.query({
name: 'clipboard-write'
});
if (permissionStatus.state === 'denied') {
throw new DOMException(
`Failed to execute 'writeText' on 'Clipboard': The request is not allowed`
);
}
this.#data = [new ClipboardItem({ 'text/plain': new Blob([text], { type: 'text/plain' }) })];
}
}
59 changes: 59 additions & 0 deletions packages/happy-dom/src/clipboard/ClipboardItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import DOMException from '../exception/DOMException.js';
import Blob from '../file/Blob.js';

/**
* Clipboard Item API.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem.
*/
export default class ClipboardItem {
public readonly presentationStyle: 'unspecified' | 'inline' | 'attachment' = 'unspecified';
#data: { [mimeType: string]: Blob };

/**
* Constructor.
*
* @param data Data.
* @param [options] Options.
* @param [options.presentationStyle] Presentation style.
*/
constructor(
data: { [mimeType: string]: Blob },
options?: { presentationStyle?: 'unspecified' | 'inline' | 'attachment' }
) {
for (const mimeType of Object.keys(data)) {
if (mimeType !== data[mimeType].type) {
throw new DOMException(`Type ${mimeType} does not match the blob's type`);
}
}
this.#data = data;
if (options?.presentationStyle) {
this.presentationStyle = options.presentationStyle;
}
}

/**
* Returns types.
*
* @returns Types.
*/
public get types(): string[] {
return Object.keys(this.#data);
}

/**
* Returns data by type.
*
* @param type Type.
* @returns Data.
*/
public async getType(type: string): Promise<Blob> {
if (!this.#data[type]) {
throw new DOMException(
`Failed to execute 'getType' on 'ClipboardItem': The type '${type}' was not found`
);
}
return this.#data[type];
}
}
52 changes: 0 additions & 52 deletions packages/happy-dom/src/config/NonImplemenetedElementClasses.ts

This file was deleted.

92 changes: 88 additions & 4 deletions packages/happy-dom/src/event/DataTransfer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,97 @@
import File from '../file/File.js';
import DataTransferItem from './DataTransferItem.js';
import DataTransferItemList from './DataTransferItemList.js';

/**
* DataTransfer.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
*/
export default class DataTransfer {
public dropEffect = 'none';
public effectAllowed = 'none';
public files: File[] = [];
public dropEffect: 'none' | 'copy' | 'link' | 'move' = 'none';
public effectAllowed:
| 'none'
| 'copy'
| 'copyLink'
| 'copyMove'
| 'link'
| 'linkMove'
| 'move'
| 'all'
| 'uninitialized' = 'none';
public readonly items: DataTransferItemList = new DataTransferItemList();
public readonly types: string[] = [];

/**
* Returns files.
*
* @returns Files.
*/
public get files(): File[] {
const files = [];
for (const item of this.items) {
if (item.kind === 'file') {
files.push(item.getAsFile());
}
}
return files;
}

/**
* Returns types.
*
* @returns Types.
*/
public get types(): string[] {
return this.items.map((item) => item.type);
}

/**
* Clears the data.
*/
public clearData(): void {
this.items.clear();
}

/**
* Sets the data.
*
* @param format Format.
* @param data Data.
*/
public setData(format: string, data: string): void {
for (let i = 0, max = this.items.length; i < max; i++) {
if (this.items[i].type === format) {
this.items[i] = new DataTransferItem(data, format);
return;
}
}
this.items.add(data, format);
}

/**
* Gets the data.
*
* @param format Format.
* @returns Data.
*/
public getData(format: string): string {
for (let i = 0, max = this.items.length; i < max; i++) {
if (this.items[i].type === format) {
let data = '';
this.items[i].getAsString((s) => (data = s));
return data;
}
}
return '';
}

/**
* Sets drag image.
*
* TODO: Implement.
*/
public setDragImage(): void {
throw new Error('Not implemented.');
}
}
25 changes: 16 additions & 9 deletions packages/happy-dom/src/event/DataTransferItem.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import File from '../file/File.js';

/**
* Data transfer item.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem.
*/
export default class DataTransferItem {
public readonly kind: string = '';
public readonly type: string = '';
private _item: string | File = null;
public readonly kind: 'string' | 'file';
public readonly type: string;
#item: string | File = null;

/**
* Constructor.
*
* @param item Item.
* @param type Type.
*/
constructor(item: string | File) {
constructor(item: string | File, type = '') {
this.kind = typeof item === 'string' ? 'string' : 'file';
this._item = item;
this.type = this.kind === 'string' ? type : (<File>item).type;
this.#item = item;
}

/**
Expand All @@ -25,16 +30,18 @@ export default class DataTransferItem {
if (this.kind === 'string') {
return null;
}
return <File>this._item;
return <File>this.#item;
}

/**
* Returns string.
*
* @param callback Callback.
*/
public getAsString(): string {
public getAsString(callback: (text: string) => void): void {
if (this.kind === 'file') {
return null;
callback;
}
return <string>this._item;
callback(<string>this.#item);
}
}

0 comments on commit 5394f83

Please sign in to comment.