From 63c259624a52ac3687e64029d21a36c22307260f Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:38:21 +0200 Subject: [PATCH 01/19] Make copy button style configurable Allow callers to override the button class and pass through inline styles, while keeping the existing primary-button default. The button content wrapper is also centred so the default and copied states stay aligned. --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index ccc4c898a..d4d03d65c 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -89,7 +89,7 @@ export class CopyToClipboardComponent extends StatefulComponent { */ view(vnode) { const { attrs, children } = vnode; - const { value: clipboardTargetValue = '', id } = attrs; + const { value: clipboardTargetValue = '', id, className = 'button.btn.btn-primary' } = attrs; let available = true; let message = ''; @@ -104,14 +104,15 @@ export class CopyToClipboardComponent extends StatefulComponent { const successContent = [iconCheck(), h('', 'Copied!')]; return h( - 'button.btn.btn-primary', + className, { id: `copy-${id}`, onclick: () => this.copyToClipboard(clipboardTargetValue), disabled: !available, title: message || null, + style: attrs.style, }, - h('div.flex-row.g1', this._successStateTimeout ? successContent : defaultContent), + h('div.flex-row.g1.justify-center', this._successStateTimeout ? successContent : defaultContent), ); } } From de002e5ab31c2c1d150de9c7637f76ca76597cc9 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:40:04 +0200 Subject: [PATCH 02/19] Stealth fix to restart the success timer if clicked before completed Reset the success timer if clicked again otherwise the button text resets at unpredictable moments and user is left confused if the copy worked or not. --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index d4d03d65c..04e857bc5 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -36,6 +36,10 @@ export class CopyToClipboardComponent extends StatefulComponent { */ copyToClipboard(clipboardTargetValue) { navigator.clipboard.writeText(clipboardTargetValue); + if (this._successStateTimeout) { + clearTimeout(this._successStateTimeout); + } + this._successStateTimeout = setTimeout(() => { this._successStateTimeout = null; this.notify(); From 04ebdcc165527d96ceb227f65b53e5dc97992ee3 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:07:49 +0200 Subject: [PATCH 03/19] Simplify clipboard button styling Update `CopyToClipboardComponent` to accept a `classes` suffix instead of a full `className` selector, and build the button selector from a consistent `button.btn` base. --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 04e857bc5..ef432b21c 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -93,7 +93,7 @@ export class CopyToClipboardComponent extends StatefulComponent { */ view(vnode) { const { attrs, children } = vnode; - const { value: clipboardTargetValue = '', id, className = 'button.btn.btn-primary' } = attrs; + const { value: clipboardTargetValue = '', id, classes = '.btn-primary' } = attrs; let available = true; let message = ''; @@ -108,7 +108,7 @@ export class CopyToClipboardComponent extends StatefulComponent { const successContent = [iconCheck(), h('', 'Copied!')]; return h( - className, + `button.btn${classes}`, { id: `copy-${id}`, onclick: () => this.copyToClipboard(clipboardTargetValue), From 97e71f2c6014634b129c582de62aa490b8815da4 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:18:21 +0200 Subject: [PATCH 04/19] Improve clipboard button docs and accessibility Documented expected `vnode.attrs` fields in `CopyToClipboardComponent.view()` and added `ariaLive: 'polite'` to the status content container so copy success feedback is announced to assistive technologies. --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index ef432b21c..b3acdd83e 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -89,6 +89,11 @@ export class CopyToClipboardComponent extends StatefulComponent { * Renders the button that allows copying text to the clipboard. * * @param {vnode} vnode The virtual DOM node containing the attrs and children. + * @param {object} vnode.attrs The attributes passed to the component. + * @param {string} vnode.attrs.value The text to be copied to the clipboard. + * @param {string} vnode.attrs.id The unique identifier for the copy button will become 'copy-{id}'. + * @param {string} vnode.attrs.classes The CSS classes to be applied to the copy button. + * @param {string} vnode.attrs.style The inline styles to be applied to the copy button. * @returns {Component} The copyToClipboard button component */ view(vnode) { @@ -116,7 +121,7 @@ export class CopyToClipboardComponent extends StatefulComponent { title: message || null, style: attrs.style, }, - h('div.flex-row.g1.justify-center', this._successStateTimeout ? successContent : defaultContent), + h('div.flex-row.g1.justify-center', { ariaLive: 'polite' }, this._successStateTimeout ? successContent : defaultContent), ); } } From 21ab573e91a03d1670b2c51cf1bc057f0823af5f Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:47:40 +0200 Subject: [PATCH 05/19] Fix CopyToClipboard button class handling --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index b3acdd83e..b340f01dd 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -98,7 +98,7 @@ export class CopyToClipboardComponent extends StatefulComponent { */ view(vnode) { const { attrs, children } = vnode; - const { value: clipboardTargetValue = '', id, classes = '.btn-primary' } = attrs; + const { value: clipboardTargetValue = '', id, className = 'btn-primary' } = attrs; let available = true; let message = ''; @@ -113,13 +113,14 @@ export class CopyToClipboardComponent extends StatefulComponent { const successContent = [iconCheck(), h('', 'Copied!')]; return h( - `button.btn${classes}`, + `button.btn`, { id: `copy-${id}`, onclick: () => this.copyToClipboard(clipboardTargetValue), disabled: !available, title: message || null, style: attrs.style, + className, }, h('div.flex-row.g1.justify-center', { ariaLive: 'polite' }, this._successStateTimeout ? successContent : defaultContent), ); From 95ce4ac9810c8d0ccd41633b61221a48f4691fc8 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:26:51 +0200 Subject: [PATCH 06/19] Handle clipboard copy failures Wrap clipboard writes in error handling and add an optional `onFailure` callback so callers can react when copying is unavailable or fails. The component also now destructures `style` explicitly. --- .../components/CopyToClipboardComponent.js | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index b340f01dd..ac2d9486c 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -32,12 +32,25 @@ export class CopyToClipboardComponent extends StatefulComponent { * Copies the specified text to the clipboard. * * @param {string} clipboardTargetValue The text to be copied to the clipboard. + * @param {function} onFailure The callback function to be invoked if copying to the clipboard fails. * @returns {void} */ - copyToClipboard(clipboardTargetValue) { - navigator.clipboard.writeText(clipboardTargetValue); - if (this._successStateTimeout) { - clearTimeout(this._successStateTimeout); + copyToClipboard(clipboardTargetValue, onFailure) { + try { + navigator.clipboard.writeText(clipboardTargetValue); + if (this._successStateTimeout) { + clearTimeout(this._successStateTimeout); + } + + this._successStateTimeout = setTimeout(() => { + this._successStateTimeout = null; + this.notify(); + }, 2000); + this.notify(); + } catch (error) { + if (onFailure) { + onFailure(error); + } } this._successStateTimeout = setTimeout(() => { @@ -94,11 +107,13 @@ export class CopyToClipboardComponent extends StatefulComponent { * @param {string} vnode.attrs.id The unique identifier for the copy button will become 'copy-{id}'. * @param {string} vnode.attrs.classes The CSS classes to be applied to the copy button. * @param {string} vnode.attrs.style The inline styles to be applied to the copy button. + * @param {function} vnode.attrs.onFailure The callback function to be invoked if copying to the clipboard fails. * @returns {Component} The copyToClipboard button component */ view(vnode) { const { attrs, children } = vnode; - const { value: clipboardTargetValue = '', id, className = 'btn-primary' } = attrs; + // Attributes other than those listed are not forwarded to the button element + const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, onFailure } = attrs; let available = true; let message = ''; @@ -113,13 +128,13 @@ export class CopyToClipboardComponent extends StatefulComponent { const successContent = [iconCheck(), h('', 'Copied!')]; return h( - `button.btn`, + 'button.btn', { id: `copy-${id}`, - onclick: () => this.copyToClipboard(clipboardTargetValue), + onclick: () => this.copyToClipboard(clipboardTargetValue, onFailure), disabled: !available, title: message || null, - style: attrs.style, + style, className, }, h('div.flex-row.g1.justify-center', { ariaLive: 'polite' }, this._successStateTimeout ? successContent : defaultContent), From ccc0d00137bcdda3a4fd9f539a53058266a80108 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:41:22 +0200 Subject: [PATCH 07/19] Fix copying/pasting error --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index ac2d9486c..27f6740ca 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -52,12 +52,6 @@ export class CopyToClipboardComponent extends StatefulComponent { onFailure(error); } } - - this._successStateTimeout = setTimeout(() => { - this._successStateTimeout = null; - this.notify(); - }, 2000); - this.notify(); } /** From c9737f2a4e5e3fac7bbcd40d99ec594be65a5d97 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:41:49 +0200 Subject: [PATCH 08/19] Await clipboard writes in copy component Done so clipboard write failures are properly caught by the existing error handling path, instead of proceeding as if copy succeeded. --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 27f6740ca..3966cc425 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -35,9 +35,9 @@ export class CopyToClipboardComponent extends StatefulComponent { * @param {function} onFailure The callback function to be invoked if copying to the clipboard fails. * @returns {void} */ - copyToClipboard(clipboardTargetValue, onFailure) { + async copyToClipboard(clipboardTargetValue, onFailure) { try { - navigator.clipboard.writeText(clipboardTargetValue); + await navigator.clipboard.writeText(clipboardTargetValue); if (this._successStateTimeout) { clearTimeout(this._successStateTimeout); } From 0f5862792613cee0bd177f9e11230d5f2a1d5161 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:00:40 +0200 Subject: [PATCH 09/19] Fix tooltip text when no disabled message --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 3966cc425..ccc5d0ded 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -127,7 +127,7 @@ export class CopyToClipboardComponent extends StatefulComponent { id: `copy-${id}`, onclick: () => this.copyToClipboard(clipboardTargetValue, onFailure), disabled: !available, - title: message || null, + title: message || '', style, className, }, From b16a6f74f0d7b397b244be02c6359978eddd980d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:13:47 +0200 Subject: [PATCH 10/19] Clarify onFailure callback type --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index ccc5d0ded..db543ab86 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -101,7 +101,7 @@ export class CopyToClipboardComponent extends StatefulComponent { * @param {string} vnode.attrs.id The unique identifier for the copy button will become 'copy-{id}'. * @param {string} vnode.attrs.classes The CSS classes to be applied to the copy button. * @param {string} vnode.attrs.style The inline styles to be applied to the copy button. - * @param {function} vnode.attrs.onFailure The callback function to be invoked if copying to the clipboard fails. + * @param {(error: Error) => void} vnode.attrs.onFailure The callback function to be invoked if copying to the clipboard fails. * @returns {Component} The copyToClipboard button component */ view(vnode) { From ea033c3ffa6b84a6d9fb8e34da6881e2e8ec5417 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:17:53 +0200 Subject: [PATCH 11/19] Replace 2nd occurrence of vague param type --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index db543ab86..9b4639c8b 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -32,7 +32,7 @@ export class CopyToClipboardComponent extends StatefulComponent { * Copies the specified text to the clipboard. * * @param {string} clipboardTargetValue The text to be copied to the clipboard. - * @param {function} onFailure The callback function to be invoked if copying to the clipboard fails. + * @param {(error: Error) => void} onFailure The callback function to be invoked if copying to the clipboard fails. * @returns {void} */ async copyToClipboard(clipboardTargetValue, onFailure) { From 572873d06b7b48f536980baf130b37f495de606c Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:25:57 +0200 Subject: [PATCH 12/19] Make clipboard component more readable --- .../js/src/components/CopyToClipboardComponent.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 9b4639c8b..8f3fe6413 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -38,6 +38,7 @@ export class CopyToClipboardComponent extends StatefulComponent { async copyToClipboard(clipboardTargetValue, onFailure) { try { await navigator.clipboard.writeText(clipboardTargetValue); + if (this._successStateTimeout) { clearTimeout(this._successStateTimeout); } @@ -46,6 +47,7 @@ export class CopyToClipboardComponent extends StatefulComponent { this._successStateTimeout = null; this.notify(); }, 2000); + this.notify(); } catch (error) { if (onFailure) { @@ -84,9 +86,9 @@ export class CopyToClipboardComponent extends StatefulComponent { } /** - * Check if the window is embeded in a frame. + * Check if the window is embedded in a frame. * - * @returns {boolean} Returns `true` if it is embeded + * @returns {boolean} Returns `true` if it is embedded */ isWindowEmbedded() { return window !== window.parent; @@ -108,6 +110,7 @@ export class CopyToClipboardComponent extends StatefulComponent { const { attrs, children } = vnode; // Attributes other than those listed are not forwarded to the button element const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, onFailure } = attrs; + let available = true; let message = ''; @@ -131,7 +134,11 @@ export class CopyToClipboardComponent extends StatefulComponent { style, className, }, - h('div.flex-row.g1.justify-center', { ariaLive: 'polite' }, this._successStateTimeout ? successContent : defaultContent), + h( + 'div.flex-row.g1.justify-center', + { ariaLive: 'polite' }, + this._successStateTimeout ? successContent : defaultContent, + ), ); } } From 6a8781019ccef1441585a9e5aa7ccc0eddea08f4 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:35:03 +0200 Subject: [PATCH 13/19] Fix JSDOC --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 8f3fe6413..7b540b617 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -96,19 +96,19 @@ export class CopyToClipboardComponent extends StatefulComponent { /** * Renders the button that allows copying text to the clipboard. + * Attributes other than those listed are not forwarded to the button element * * @param {vnode} vnode The virtual DOM node containing the attrs and children. * @param {object} vnode.attrs The attributes passed to the component. * @param {string} vnode.attrs.value The text to be copied to the clipboard. * @param {string} vnode.attrs.id The unique identifier for the copy button will become 'copy-{id}'. - * @param {string} vnode.attrs.classes The CSS classes to be applied to the copy button. + * @param {string} vnode.attrs.className The CSS classes to be applied to the copy button. * @param {string} vnode.attrs.style The inline styles to be applied to the copy button. * @param {(error: Error) => void} vnode.attrs.onFailure The callback function to be invoked if copying to the clipboard fails. * @returns {Component} The copyToClipboard button component */ view(vnode) { const { attrs, children } = vnode; - // Attributes other than those listed are not forwarded to the button element const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, onFailure } = attrs; let available = true; From 9fca7770f184b9d414fa0a3552df857822ae5502 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:44:22 +0200 Subject: [PATCH 14/19] Remove aria-live On second thought if no other elements have it, I won't add here and wait for a proper strategy. --- Framework/Frontend/js/src/components/CopyToClipboardComponent.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 7b540b617..85b8a65d6 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -136,7 +136,6 @@ export class CopyToClipboardComponent extends StatefulComponent { }, h( 'div.flex-row.g1.justify-center', - { ariaLive: 'polite' }, this._successStateTimeout ? successContent : defaultContent, ), ); From 1c917fb1e621dc13ad693655e10136f1abdb7b0c Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:49:33 +0200 Subject: [PATCH 15/19] Another JSDoc fix --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 85b8a65d6..d64022bdb 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -33,7 +33,7 @@ export class CopyToClipboardComponent extends StatefulComponent { * * @param {string} clipboardTargetValue The text to be copied to the clipboard. * @param {(error: Error) => void} onFailure The callback function to be invoked if copying to the clipboard fails. - * @returns {void} + * @returns {Promise} */ async copyToClipboard(clipboardTargetValue, onFailure) { try { From 34a04a8c756537f98e15ed3b05998a99be1d831d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:08:03 +0200 Subject: [PATCH 16/19] More JSDoc fixes --- .../js/src/components/CopyToClipboardComponent.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index d64022bdb..2edbe9bba 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -100,11 +100,11 @@ export class CopyToClipboardComponent extends StatefulComponent { * * @param {vnode} vnode The virtual DOM node containing the attrs and children. * @param {object} vnode.attrs The attributes passed to the component. - * @param {string} vnode.attrs.value The text to be copied to the clipboard. - * @param {string} vnode.attrs.id The unique identifier for the copy button will become 'copy-{id}'. - * @param {string} vnode.attrs.className The CSS classes to be applied to the copy button. - * @param {string} vnode.attrs.style The inline styles to be applied to the copy button. - * @param {(error: Error) => void} vnode.attrs.onFailure The callback function to be invoked if copying to the clipboard fails. + * @param {string} [vnode.attrs.value] The text to be copied to the clipboard. + * @param {string} [vnode.attrs.id] The unique identifier for the copy button will become 'copy-{id}'. + * @param {string} [vnode.attrs.className='btn-primary'] The CSS classes to be applied to the copy button. + * @param {string|object} [vnode.attrs.style] The inline styles to be applied to the copy button. + * @param {(error: Error) => void} [vnode.attrs.onFailure] The callback function to be invoked if copying to the clipboard fails. * @returns {Component} The copyToClipboard button component */ view(vnode) { From ad01ce2fb0d8c4f28debef896796ccb5ffcca71f Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:08:21 +0200 Subject: [PATCH 17/19] Avoid undefined copy button IDs --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 2edbe9bba..788a3b505 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -127,7 +127,7 @@ export class CopyToClipboardComponent extends StatefulComponent { return h( 'button.btn', { - id: `copy-${id}`, + id: id ? `copy-${id}` : undefined, onclick: () => this.copyToClipboard(clipboardTargetValue, onFailure), disabled: !available, title: message || '', From 18f7f5f4fa45d32f5d73e0a11b6470123a583cd6 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:09:31 +0200 Subject: [PATCH 18/19] Cache clipboard availability in constructor Initialise clipboard support state once in constructor and reuse it during rendering. This removes repeated availability checks on each render and drives button disabled/title directly from persisted fields. --- .../components/CopyToClipboardComponent.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 788a3b505..849bd5ba8 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -26,6 +26,16 @@ export class CopyToClipboardComponent extends StatefulComponent { constructor() { super(); this._successStateTimeout = null; + + this._available = true; + this._message = ''; + + try { + this.checkClipboardAvailability(); + } catch ({ message: errorMessage }) { + this._available = false; + this._message = errorMessage; + } } /** @@ -111,16 +121,6 @@ export class CopyToClipboardComponent extends StatefulComponent { const { attrs, children } = vnode; const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, onFailure } = attrs; - let available = true; - let message = ''; - - try { - this.checkClipboardAvailability(); - } catch ({ message: errorMessage }) { - available = false; - message = errorMessage; - } - const defaultContent = [iconLinkIntact(), children]; const successContent = [iconCheck(), h('', 'Copied!')]; @@ -129,8 +129,8 @@ export class CopyToClipboardComponent extends StatefulComponent { { id: id ? `copy-${id}` : undefined, onclick: () => this.copyToClipboard(clipboardTargetValue, onFailure), - disabled: !available, - title: message || '', + disabled: !this._available, + title: this._message, style, className, }, From 94a5753ab898dc62e0007d0ae6afd2acdbb2a824 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:20:43 +0200 Subject: [PATCH 19/19] More JSDoc --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 849bd5ba8..5eb98826d 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -42,7 +42,7 @@ export class CopyToClipboardComponent extends StatefulComponent { * Copies the specified text to the clipboard. * * @param {string} clipboardTargetValue The text to be copied to the clipboard. - * @param {(error: Error) => void} onFailure The callback function to be invoked if copying to the clipboard fails. + * @param {(error: Error) => void} [onFailure] The callback function to be invoked if copying to the clipboard fails. * @returns {Promise} */ async copyToClipboard(clipboardTargetValue, onFailure) { @@ -106,11 +106,11 @@ export class CopyToClipboardComponent extends StatefulComponent { /** * Renders the button that allows copying text to the clipboard. - * Attributes other than those listed are not forwarded to the button element + * Attributes other than those listed are not forwarded to the button element. * * @param {vnode} vnode The virtual DOM node containing the attrs and children. * @param {object} vnode.attrs The attributes passed to the component. - * @param {string} [vnode.attrs.value] The text to be copied to the clipboard. + * @param {string} [vnode.attrs.value=''] The text to be copied to the clipboard. * @param {string} [vnode.attrs.id] The unique identifier for the copy button will become 'copy-{id}'. * @param {string} [vnode.attrs.className='btn-primary'] The CSS classes to be applied to the copy button. * @param {string|object} [vnode.attrs.style] The inline styles to be applied to the copy button.