From e74bcf90c0cbd7d4fb249fc3b7911f4a0ab1bd50 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 68d40bd8b902cefad04e79ba8094574d059c3d50 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 85124739ab15bde9ec6432021f1f49207cefad24 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 2578f803c45d11bc389fb3856f152727ef33d5c1 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 a86fdf07a669de4945edbd29239ed389c0d3c856 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 5d2794cf4e1c8cbc0619292496c696fa2ed5e876 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 5255f624dbd8ecd2d176b0ecf9731c761ee87481 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 548c806cbad4e2292c8bb0f715300b1968c0b6da 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 21f3272a46013f105cf7ae8be1c9f935d3afd468 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 598423579b7da2f3e01a90966bd792403448517b 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 b40326a0f9fbda66cd57d1d7bcc01b232c5e376c 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 6b9969711808ab128b4919dde1bb9d30efd2f850 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 01eb5307df32fe489096fafb9621209f2a11923a 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 af3e95c40a47287d549a10f338b94e5141c18446 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 af98cfd69ebc97ebaaf13956c30ba175e365ba53 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 ce025ae353456dfdf85ad2c7415c72e8a8b4c336 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 886161696703805abf8e69c43f4b41860e51fe94 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 b9b64cb9dd41616461e2a04de2167624caa548ac 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 38ac42aac32b7f8ddb28e1551ea8cba239938a1f 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.