Skip to content
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

Clipboard & Copy Button - Fix handling of empty string and zero values (HDS-4447) #2685

Merged
merged 9 commits into from
Feb 7, 2025
7 changes: 7 additions & 0 deletions .changeset/early-knives-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hashicorp/design-system-components": patch
---

`Copy Button` - Fixed issue preventing copying of empty string and zero number values.
KristinLBradley marked this conversation as resolved.
Show resolved Hide resolved

`Copy Snippet` - Fixed issue preventing copying of empty string and zero number values.
85 changes: 40 additions & 45 deletions packages/components/src/modifiers/hds-clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,20 @@ export interface HdsClipboardModifierSignature {
export const getTextToCopy = (text: TextToCopy): string => {
let textToCopy: string = '';

if (text) {
if (typeof text === 'string') {
textToCopy = text;
} else if (
// context: https://github.com/hashicorp/design-system/pull/1564
typeof text === 'number' ||
typeof text === 'bigint'
) {
textToCopy = text.toString();
} else {
assert(
`\`hds-clipboard\` modifier - \`text\` argument must be a string - provided: ${typeof text}`
);
}
if (typeof text === 'string') {
textToCopy = text;
} else if (
// context: https://github.com/hashicorp/design-system/pull/1564
typeof text === 'number' ||
typeof text === 'bigint'
) {
textToCopy = text.toString();
} else {
assert(
`\`hds-clipboard\` modifier - \`text\` argument must be a string or number - provided: ${typeof text}`
);
}

return textToCopy;
};

Expand Down Expand Up @@ -109,38 +108,34 @@ export const writeTextToClipboard = async (
): Promise<boolean> => {
// finally copy the text to the clipboard using the Clipboard API
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
if (textToCopy) {
try {
// notice: the "clipboard-write" permission is granted automatically to pages when they are in the active tab
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write
await navigator.clipboard.writeText(textToCopy);
// DEBUG uncomment this for easy debugging
// console.log('success', textToCopy);
return true;
} catch {
// if it is not a secure context, use the polyfill
// to test that this works in a non-secure context, access the port through your IP address (ie. XXX.XXX.X.XXX:4200/)
if (!navigator.clipboard) {
try {
const clipboard = await import('clipboard-polyfill');
await clipboard.writeText(textToCopy);
return true;
} catch (error) {
warn(
`copy action failed, unable to use clipboard-polyfill: ${JSON.stringify(
error
)}`,
{
id: 'hds-clipboard.write-text-to-clipboard.catch-error',
}
);
return false;
}
try {
// notice: the "clipboard-write" permission is granted automatically to pages when they are in the active tab
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write
await navigator.clipboard.writeText(textToCopy);
// DEBUG uncomment this for easy debugging
// console.log('success', textToCopy);
return true;
} catch {
// if it is not a secure context, use the polyfill
// to test that this works in a non-secure context, access the port through your IP address (ie. XXX.XXX.X.XXX:4200/)
if (!navigator.clipboard) {
try {
const clipboard = await import('clipboard-polyfill');
await clipboard.writeText(textToCopy);
return true;
} catch (error) {
warn(
`copy action failed, unable to use clipboard-polyfill: ${JSON.stringify(
error
)}`,
{
id: 'hds-clipboard.write-text-to-clipboard.catch-error',
}
);
return false;
}

return false;
}
} else {

return false;
}
};
Expand All @@ -151,7 +146,7 @@ export const copyToClipboard = async (
): Promise<boolean> => {
let textToCopy: string = '';

if (text) {
if (text !== undefined) {
textToCopy = getTextToCopy(text);
} else if (target) {
const targetElement = getTargetElement(target);
Expand Down
11 changes: 11 additions & 0 deletions showcase/app/templates/components/copy/button.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@
</SF.Item>
</Shw::Flex>

<Shw::Text::H4>Special cases</Shw::Text::H4>
KristinLBradley marked this conversation as resolved.
Show resolved Hide resolved

<Shw::Flex as |SF|>
<SF.Item>
<Hds::Copy::Button @text="Copy an empty string" @textToCopy="" />
</SF.Item>
<SF.Item>
<Hds::Copy::Button @text="Copy the number '0'" @textToCopy={{0}} />
</SF.Item>
</Shw::Flex>

<Shw::Divider @level={{2}} />

<Shw::Text::H3>With <code>target</code> element</Shw::Text::H3>
Expand Down
27 changes: 24 additions & 3 deletions showcase/tests/integration/modifiers/hds-clipboard-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,6 @@ module(
const success = await writeTextToClipboard('test');
assert.false(success);
});
test('returns `false` as response if no `textToCopy` argument is provided', async function (assert) {
assert.false(await writeTextToClipboard());
});
}
);

Expand Down Expand Up @@ -265,6 +262,18 @@ module('Integration | Modifier | hds-clipboard', function (hooks) {
assert.true(this.success);
});

test('it should copy an empty string provided as a `@text` argument', async function (assert) {
await render(
hbs`<button id="test-button" {{hds-clipboard
text=""
onSuccess=this.onSuccess
onError=this.onError
}}>Test</button>`
);
await click('button#test-button');
assert.true(this.success);
});

// context: https://github.com/hashicorp/design-system/pull/1564
test('it should allow to copy an `integer` provided as `@text` argument', async function (assert) {
await render(
Expand All @@ -278,6 +287,18 @@ module('Integration | Modifier | hds-clipboard', function (hooks) {
assert.true(this.success);
});

test('it should copy a zero number value provided as a `@text` argument', async function (assert) {
await render(
hbs`<button id="test-button" {{hds-clipboard
text=0
didoo marked this conversation as resolved.
Show resolved Hide resolved
onSuccess=this.onSuccess
onError=this.onError
}}>Test</button>`
);
await click('button#test-button');
assert.true(this.success);
});

// @TARGET ARGUMENT

test('it should allow to target an element using a `string` selector for the `@target` argument', async function (assert) {
Expand Down