Aller au contenu

CopyButton

composant

A button that copies a text, given or read from another element, and says so.

Aperçu

Clair

Code
<twig:CopyButton text="https://example.com/invite" />

Sombre

Code
<twig:CopyButton text="https://example.com/invite" />

Installation

php bin/console ux:install copy-button --kit=github.com/parler-haut-interagir-librement/ux-kit

Propriétés

PropriétéTypePar défautDescription
text string|null null The text to copy.
source string|null null The CSS selector of the element whose value or text is copied, when text is null.
label string 'Copy' The name of the button.
copiedLabel string 'Copied' What the button says once the text is copied.
failedLabel string 'Copy failed' What the button says when the browser refuses.
duration int 2000 How long the outcome shows, in milliseconds.

Accessibility

What happened is written on the button and in a polite live region. The button stays hidden where the browser offers no clipboard, which needs a secure context.

Fichiers à copier

assets/controllers/copy_button_controller.js
import { Controller } from '@hotwired/stimulus';

/*
 * Copies a text to the clipboard, says so on the button for a moment and in the live region. The button shows only
 * where the browser can copy.
 */
export default class extends Controller {
    static targets = ['trigger', 'status'];

    static values = {
        text: String,
        source: String,
        label: String,
        copiedLabel: String,
        failedLabel: String,
        duration: { type: Number, default: 2000 },
    };

    #timer = null;

    connect() {
        this.triggerTarget.hidden = !navigator.clipboard;
    }

    disconnect() {
        clearTimeout(this.#timer);
    }

    async copy() {
        try {
            await navigator.clipboard.writeText(this.#text());
            this.#say(this.copiedLabelValue);
            this.dispatch('copied');
        } catch {
            this.#say(this.failedLabelValue);
        }
    }

    #text() {
        if (this.sourceValue === '') {
            return this.textValue;
        }

        const source = document.querySelector(this.sourceValue);

        return source?.value ?? source?.textContent ?? '';
    }

    #say(message) {
        clearTimeout(this.#timer);
        this.triggerTarget.textContent = message;
        this.statusTarget.textContent = message;
        this.#timer = setTimeout(() => {
            this.triggerTarget.textContent = this.labelValue;
            this.statusTarget.textContent = '';
        }, this.durationValue);
    }
}
templates/components/CopyButton.html.twig
{#-
 # A button that copies a text: given here, or read from another element. It stays hidden while no script can copy, and
 # says what happened in a live region of its own.
 #}
{%- props
    ## string|null The text to copy.
    text = null,
    ## string|null The CSS selector of the element whose value or text is copied, when `text` is null.
    source = null,
    ## string The name of the button.
    label = 'Copy',
    ## string What the button says once the text is copied.
    copiedLabel = 'Copied',
    ## string What the button says when the browser refuses.
    failedLabel = 'Copy failed',
    ## int How long the outcome shows, in milliseconds.
    duration = 2000
-%}
<span data-copy-button-text-value="{{ text ?? '' }}" data-copy-button-source-value="{{ source ?? '' }}" data-copy-button-label-value="{{ label }}" data-copy-button-copied-label-value="{{ copiedLabel }}" data-copy-button-failed-label-value="{{ failedLabel }}" data-copy-button-duration-value="{{ duration }}"{{ attributes.defaults({class: 'isCopyButton', 'data-controller': 'copy-button'}) }}><button type="button" class="isCopyButtonTrigger" hidden data-copy-button-target="trigger" data-action="copy-button#copy">{{ label }}</button><span class="isVisuallyHidden" role="status" aria-live="polite" data-copy-button-target="status"></span></span>