-
Notifications
You must be signed in to change notification settings - Fork 32
add navigator clipboard, ClipboardItem #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f054df8
add navigator clipboard, ClipboardItem
keithamus 6ff62ff
fix typo
keithamus 43ad819
Merge branch 'main' into add-navigator-clipboard-clipboarditem
keithamus 65cefe8
fix up ClipboardItemData type
keithamus bf7dd0a
hoist clipboarditems type
keithamus c6bbad7
lint
keithamus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| const records = new WeakMap<ClipboardItem, Record<string, ClipboardItemDataType | PromiseLike<ClipboardItemDataType>>>() | ||
| const presentationStyles = new WeakMap<ClipboardItem, PresentationStyle>() | ||
| export class ClipboardItem { | ||
| constructor( | ||
| items: Record<string, ClipboardItemDataType | PromiseLike<ClipboardItemDataType>>, | ||
| options: ClipboardItemOptions | undefined = {} | ||
| ) { | ||
| if (Object.keys(items).length === 0) throw new TypeError('Empty dictionary argument') | ||
| records.set(this, items) | ||
| presentationStyles.set(this, options.presentationStyle || 'unspecified') | ||
| } | ||
|
|
||
| get presentationStyle(): PresentationStyle { | ||
| return presentationStyles.get(this) || 'unspecified' | ||
| } | ||
|
|
||
| get types() { | ||
| return Object.freeze(Object.keys(records.get(this) || {})) | ||
| } | ||
|
|
||
| async getType(type: string): Promise<Blob> { | ||
| const record = records.get(this) | ||
| if (record && type in record) { | ||
| const item = await record[type]! | ||
| if (typeof item === 'string') return new Blob([item], {type}) | ||
| return item | ||
| } | ||
| throw new DOMException("Failed to execute 'getType' on 'ClipboardItem': The type was not found", 'NotFoundError') | ||
| } | ||
| } | ||
|
|
||
| export function isSupported(): boolean { | ||
| try { | ||
| new globalThis.ClipboardItem({'text/plain': Promise.resolve('')}) | ||
| return true | ||
| } catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| export function isPolyfilled(): boolean { | ||
| return globalThis.ClipboardItem === ClipboardItem | ||
| } | ||
|
|
||
| export function apply(): void { | ||
| if (!isSupported()) { | ||
| globalThis.ClipboardItem = ClipboardItem | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| export async function clipboardWrite(data: ClipboardItems) { | ||
| if (data.length === 0) return | ||
| const item = data[0] | ||
| const blob = await item.getType(item.types.includes('text/plain') ? 'text/plain' : item.types[0]) | ||
| return navigator.clipboard.writeText(typeof blob == 'string' ? blob : await blob.text()) | ||
| } | ||
|
|
||
| export async function clipboardRead() { | ||
| const str = navigator.clipboard.readText() | ||
| return [new ClipboardItem({'text/plain': str})] | ||
| } | ||
|
|
||
| export function isSupported(): boolean { | ||
| return typeof navigator.clipboard.read === 'function' && typeof navigator.clipboard.write === 'function' | ||
| } | ||
|
|
||
| export function isPolyfilled(): boolean { | ||
| return navigator.clipboard.write === clipboardWrite || navigator.clipboard.read === clipboardRead | ||
| } | ||
|
|
||
| export function apply(): void { | ||
| if (!isSupported()) { | ||
| navigator.clipboard.write = clipboardWrite | ||
| navigator.clipboard.read = clipboardRead | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import {ClipboardItem, apply, isSupported, isPolyfilled} from '../lib/clipboarditem.js' | ||
|
|
||
| describe('ClipboardItem', () => { | ||
| it('has standard isSupported, isPolyfilled, apply API', () => { | ||
| expect(isSupported).to.be.a('function') | ||
| expect(isPolyfilled).to.be.a('function') | ||
| expect(apply).to.be.a('function') | ||
| expect(isSupported()).to.be.a('boolean') | ||
| expect(isPolyfilled()).to.equal(false) | ||
| }) | ||
|
|
||
| it('takes a Promise type, that can resolve', async () => { | ||
| const c = new ClipboardItem({'text/plain': Promise.resolve('hi')}) | ||
| expect(c.types).to.eql(['text/plain']) | ||
| expect(await c.getType('text/plain')).to.be.instanceof(Blob) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import {clipboardRead, clipboardWrite, apply, isPolyfilled, isSupported} from '../lib/navigator-clipboard.js' | ||
|
|
||
| describe('navigator clipboard', () => { | ||
| it('has standard isSupported, isPolyfilled, apply API', () => { | ||
| expect(isSupported).to.be.a('function') | ||
| expect(isPolyfilled).to.be.a('function') | ||
| expect(apply).to.be.a('function') | ||
| expect(isSupported()).to.be.a('boolean') | ||
| expect(isPolyfilled()).to.equal(false) | ||
| }) | ||
|
|
||
| describe('read', () => { | ||
| it('read returns array of 1 clipboard entry with plaintext of readText value', async () => { | ||
| navigator.clipboard.readText = () => Promise.resolve('foo') | ||
| const arr = await clipboardRead() | ||
| expect(arr).to.have.lengthOf(1) | ||
| expect(arr[0]).to.be.an.instanceof(globalThis.ClipboardItem) | ||
| expect(arr[0].types).to.eql(['text/plain']) | ||
| expect(await arr[0].getType('text/plain')).to.eql('foo') | ||
| }) | ||
| }) | ||
|
|
||
| describe('write', () => { | ||
| it('unpacks text/plain content to writeText', async () => { | ||
| const calls = [] | ||
| navigator.clipboard.writeText = (...args) => calls.push(args) | ||
| await clipboardWrite([ | ||
| new globalThis.ClipboardItem({ | ||
| 'foo/bar': 'horrible', | ||
| 'text/plain': Promise.resolve('foo') | ||
| }) | ||
| ]) | ||
| expect(calls).to.have.lengthOf(1) | ||
| expect(calls[0]).to.eql(['foo']) | ||
| }) | ||
|
|
||
| it('accepts multiple clipboard items, picking the first', async () => { | ||
| const calls = [] | ||
| navigator.clipboard.writeText = (...args) => calls.push(args) | ||
| await clipboardWrite([ | ||
| new globalThis.ClipboardItem({ | ||
| 'foo/bar': 'horrible', | ||
| 'text/plain': Promise.resolve('multiple-pass') | ||
| }), | ||
| new globalThis.ClipboardItem({ | ||
| 'foo/bar': 'multiple-fail', | ||
| 'text/plain': Promise.resolve('multiple-fail') | ||
| }) | ||
| ]) | ||
| expect(calls).to.have.lengthOf(1) | ||
| expect(calls[0]).to.eql(['multiple-pass']) | ||
| }) | ||
| }) | ||
| }) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.