From a75697a68982c52da036eabd8b15c6fe31a22aa6 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/23] 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 c30924d017780c42af0bcfa13398096f92c2fc23 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/23] 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 87d42a5270deb74e2672c14d1a52dcb390d4e8d2 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/23] 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 1603d00814cddea49a56e1d4fe11b6dfca1a5bf5 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/23] 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 2523f0af0df2ca0fcda3c35e56148861cc6e6b6b 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/23] 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 688ce0f0d444821c5e406056613d8b35ae5a62a9 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/23] 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 194eb3778e795c44bab192ed988773c51e56a887 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/23] 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 b46f61ba5b8fb3e7b806b26e12b22ba4aff1eddc 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/23] 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 9875cd9532cfe68a2cb1706d69983c17a0453df9 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/23] 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 f77c82fb68c20edda6e937b884e2fb0f49a7237a 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/23] 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 848c7c47f6c86e45d6791b4aa8ccff59c4eee8b2 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/23] 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 c7ec5b05b40b192db851cba31f68961bb6ae7ca4 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/23] 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 f2fe2f188d8570c54f7ca360653b47fe680fb514 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/23] 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 bd464744d445a19d0ca71d501417c3ee5a67b042 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/23] 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 42b10b811c25def97e2c2aa767d7765913eb52f8 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/23] 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 28bcab3cf85f33e8a36d9e9aa17dee5e88e4cda9 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/23] 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 52aa90c699a03e007b22a0d1066ab2cf88e46b5d 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/23] 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 d04229f0373ae0d549c361c2c1f9efdbacb50dda 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/23] 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 e3bf0cd295227174b4fba42a81252e65b16ec113 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/23] 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. From 7698e40b35f4224e86de99baeeda1f65a3c88909 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:53:36 +0200 Subject: [PATCH 20/23] Check onFailure callback is a function --- .../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 5eb98826d..057773ad3 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -60,7 +60,7 @@ export class CopyToClipboardComponent extends StatefulComponent { this.notify(); } catch (error) { - if (onFailure) { + if (typeof onFailure === 'function') { onFailure(error); } } From 63bc8ca5174438823a50576a3ea2b9d849fc8079 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:54:03 +0200 Subject: [PATCH 21/23] Add loading class to copy button example className attr --- .../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 057773ad3..136f1cc27 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -112,7 +112,7 @@ export class CopyToClipboardComponent extends StatefulComponent { * @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='btn-primary'] The CSS classes to be applied to the copy button. + * @param {string} [vnode.attrs.className='btn-primary loading'] 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 From a32c1ccc554ce0480f10945145670d75db401ac7 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:54:29 +0200 Subject: [PATCH 22/23] Fix CopyToClipboard button type --- Framework/Frontend/js/src/components/CopyToClipboardComponent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index 136f1cc27..f49100c3b 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -128,6 +128,7 @@ export class CopyToClipboardComponent extends StatefulComponent { 'button.btn', { id: id ? `copy-${id}` : undefined, + type: 'button', onclick: () => this.copyToClipboard(clipboardTargetValue, onFailure), disabled: !this._available, title: this._message, From 241b63bacc67062b5d293a4b2c25c7aa149ee51b Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:55:36 +0200 Subject: [PATCH 23/23] Add content class support to copy button --- .../Frontend/js/src/components/CopyToClipboardComponent.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index f49100c3b..b8b5dd5e3 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -114,12 +114,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.className='btn-primary loading'] 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 {string} [vnode.attrs.contentClassName] The CSS classes to be applied to the content of 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; - const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, onFailure } = attrs; + const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, contentClassName, onFailure } = attrs; const defaultContent = [iconLinkIntact(), children]; const successContent = [iconCheck(), h('', 'Copied!')]; @@ -136,7 +137,8 @@ export class CopyToClipboardComponent extends StatefulComponent { className, }, h( - 'div.flex-row.g1.justify-center', + 'div.flex-row.g1', + { className: contentClassName }, this._successStateTimeout ? successContent : defaultContent, ), );