diff --git a/docs/pages/components/copy-button.md b/docs/pages/components/copy-button.md
new file mode 100644
index 00000000..f5a1f156
--- /dev/null
+++ b/docs/pages/components/copy-button.md
@@ -0,0 +1,252 @@
+---
+meta:
+ title: Copy Button
+ description: Copies data to the clipboard when the user clicks the button.
+layout: component
+---
+
+```html:preview
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+
+const App = () => (
+
+);
+```
+
+## Examples
+
+### Custom Labels
+
+Copy Buttons display feedback labels in a tooltip. You can change the labels by setting the `copy-label`, `success-label`, and `error-label` attributes.
+
+```html:preview
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+
+const App = () => (
+
+);
+```
+
+### Custom Icons
+
+Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [``](/components/icon) or your own images.
+
+```html:preview
+
+
+
+
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+import { SlIcon } from '@shoelace-style/shoelace/dist/react/sl-icon';
+
+const App = () => (
+ <>
+
+
+
+
+
+ >
+);
+```
+
+### Copying Values From Other Elements
+
+Normally, the data that gets copied will come from the component's `value` attribute, but you can copy data from any element within the same document by providing its `id` to the `from` attribute.
+
+When using the `from` attribute, the element's [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) will be copied by default. Passing an attribute or property modifier will let you copy data from one of the element's attributes or properties instead.
+
+To copy data from an attribute, use `from="id[attr]"` where `id` is the id of the target element and `attr` is the name of the attribute you'd like to copy. To copy data from a property, use `from="id.prop"` where `id` is the id of the target element and `prop` is the name of the property you'd like to copy.
+
+```html:preview
+
++1 (234) 456-7890
+
+
+
+
+
+
+
+
+
+
+
+Shoelace Website
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+import { SlInput } from '@shoelace-style/shoelace/dist/react/sl-input';
+
+const App = () => (
+ <>
+ {/* Copies the span's textContent */}
+ +1 (234) 456-7890
+
+
+
+
+ {/* Copies the input's "value" property */}
+
+
+
+
+
+ {/* Copies the link's "href" attribute */}
+ Shoelace Website
+
+ >
+);
+```
+
+### Handling Errors
+
+Copy errors occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. This example shows what happens when a copy error occurs.
+
+```html:preview
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+
+const App = () => (
+
+);
+```
+
+### Disabled
+
+Copy buttons can be disabled by adding the `disabled` attribute.
+
+```html:preview
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+
+const App = () => (
+
+);
+```
+
+### Changing Feedback Duration
+
+A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
+
+```html:preview
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+
+const App = () => (
+
+);
+```
+
+### Custom Styles
+
+You can customize the button to your liking with CSS.
+
+```html:preview
+
+
+
+```
+
+```jsx:react
+import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button';
+
+const css = `
+ .custom-styles {
+ --success-color: white;
+ --error-color: white;
+ color: white;
+ }
+
+ .custom-styles::part(button) {
+ background-color: #ff1493;
+ border: solid 4px #ff7ac1;
+ border-right-color: #ad005c;
+ border-bottom-color: #ad005c;
+ border-radius: 0;
+ transition: 100ms scale ease-in-out, 100ms translate ease-in-out;
+ }
+
+ .custom-styles::part(button):hover {
+ scale: 1.1;
+ }
+
+ .custom-styles::part(button):active {
+ translate: 0 2px;
+ }
+
+ .custom-styles::part(button):focus-visible {
+ outline: dashed 2px deeppink;
+ outline-offset: 4px;
+ }
+`;
+
+const App = () => (
+ <>
+
+
+
+ >
+);
+```
diff --git a/docs/pages/components/copy.md b/docs/pages/components/copy.md
deleted file mode 100644
index 0206f012..00000000
--- a/docs/pages/components/copy.md
+++ /dev/null
@@ -1,156 +0,0 @@
----
-meta:
- title: Copy
- description: Copies data to the clipboard when the user clicks or taps the trigger.
-layout: component
----
-
-```html:preview
-
-```
-
-```jsx:react
-import { SlCopy } from '@shoelace-style/shoelace/dist/react';
-
-const App = () => (
-
-);
-```
-
-## Examples
-
-### Custom Buttons
-
-Use the default slot to customize the copy trigger. You can also customize the success and error messages using the respective slots.
-
-```html:preview
-
- Copy
- Copied!
- Error
-
-```
-
-```jsx:react
-import { SlButton, SlCopy } from '@shoelace-style/shoelace/dist/react';
-
-const App = () => (
- <>
-
- Copy
- Copied!
- Error
-
- >
-);
-```
-
-### Copying the Value From Other Elements
-
-By default, the data to copy will come from the `value` attribute. You
-
-```html:preview
-+1 (234) 456-7890
-
-
-
-
-
-
-
-
-
-Shoelace Website
-
-```
-
-```jsx:react
-import { SlCopy, SlInput } from '@shoelace-style/shoelace/dist/react';
-
-const App = () => (
- <>
- +1 (234) 456-7890
-
-
-
-
-
-
-
-
-
- Shoelace Website
-
- >
-);
-```
-
-### Displaying Copy Errors
-
-Copy errors can occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation. You can customize the error that's shown by populating the `error` slot with your own content.
-
-```html:preview
-
-
-
-
-
- Copy
- Copied
- Error
-
-```
-
-```jsx:react
-import { SlCopy } from '@shoelace-style/shoelace/dist/react';
-
-const App = () => (
- <>
-
-
-
-
-
- Copy
- Copied
- Error
-
- >
-);
-```
-
-### Showing Tooltips
-
-You can wrap a tooltip around `` to provide a hint to users.
-
-```html:preview
-
-
-
-```
-
-```jsx:react
-import { SlCopy, SlTooltip } from '@shoelace-style/shoelace/dist/react';
-
-const App = () => (
-
-
-
-);
-```
-
-### Changing Feedback Duration
-
-A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
-
-```html:preview
-
-```
-
-```jsx:react
-import { SlCopy } from '@shoelace-style/shoelace/dist/react';
-
-const App = () => (
-
-);
-```
diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md
index 0a4184b0..d5550563 100644
--- a/docs/pages/resources/changelog.md
+++ b/docs/pages/resources/changelog.md
@@ -14,9 +14,10 @@ New versions of Shoelace are released as-needed and generally occur when a criti
## Next
-- Added the `` component [#1473]
+- Added the `` component [#1473]
- Fixed a bug in `` where pressing [[Up]] or [[Down]] when focused on the trigger wouldn't focus the first/last menu items [#1472]
- Improved the behavior of the clear button in `` to prevent the component's width from shifting when toggled [#1496]
+- Improved `` to prevent user selection so the tooltip doesn't get highlighted when dragging selections
- Removed `sideEffects` key from `package.json`. Update React docs to use cherry-picking. [#1485]
- Updated Bootstrap Icons to 1.10.5
diff --git a/src/components/copy-button/copy-button.component.ts b/src/components/copy-button/copy-button.component.ts
new file mode 100644
index 00000000..fa205640
--- /dev/null
+++ b/src/components/copy-button/copy-button.component.ts
@@ -0,0 +1,234 @@
+import { classMap } from 'lit/directives/class-map.js';
+import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
+import { html } from 'lit';
+import { LocalizeController } from '../../utilities/localize.js';
+import { property, query, state } from 'lit/decorators.js';
+import ShoelaceElement from '../../internal/shoelace-element.js';
+import SlIcon from '../icon/icon.component.js';
+import SlTooltip from '../tooltip/tooltip.component.js';
+import styles from './copy-button.styles.js';
+import type { CSSResultGroup } from 'lit';
+
+/**
+ * @summary Copies text data to the clipboard when the user clicks the trigger.
+ * @documentation https://shoelace.style/components/copy
+ * @status experimental
+ * @since 2.7
+ *
+ * @dependency sl-icon
+ * @dependency sl-tooltip
+ *
+ * @event sl-copy - Emitted when the data has been copied.
+ * @event sl-error - Emitted when the data could not be copied.
+ *
+ * @slot copy-icon - The icon to show in the default copy state. Works best with ``.
+ * @slot success-icon - The icon to show when the content is copied. Works best with ``.
+ * @slot error-icon - The icon to show when a copy error occurs. Works best with ``.
+ *
+ * @csspart button - The internal `` element.
+ *
+ * @cssproperty --success-color - The color to use for success feedback.
+ * @cssproperty --error-color - The color to use for error feedback.
+ *
+ * @animation copy.in - The animation to use when feedback icons animate in.
+ * @animation copy.out - The animation to use when feedback icons animate out.
+ */
+export default class SlCopyButton extends ShoelaceElement {
+ static styles: CSSResultGroup = styles;
+ static dependencies = {
+ 'sl-icon': SlIcon,
+ 'sl-tooltip': SlTooltip
+ };
+
+ private readonly localize = new LocalizeController(this);
+
+ @query('slot[name="copy-icon"]') copyIcon: HTMLSlotElement;
+ @query('slot[name="success-icon"]') successIcon: HTMLSlotElement;
+ @query('slot[name="error-icon"]') errorIcon: HTMLSlotElement;
+ @query('sl-tooltip') tooltip: SlTooltip;
+
+ @state() isCopying = false;
+ @state() status: 'rest' | 'success' | 'error' = 'rest';
+
+ /** The text value to copy. */
+ @property() value = '';
+
+ /**
+ * An id that references an element in the same document from which data will be copied. If both this and `value` are
+ * present, this value will take precedence. By default, the target element's `textContent` will be copied. To copy an
+ * attribute, append the attribute name wrapped in square brackets, e.g. `from="el[value]"`. To copy a property,
+ * append a dot and the property name, e.g. `from="el.value"`.
+ */
+ @property() from = '';
+
+ /** Disables the copy button. */
+ @property({ type: Boolean, reflect: true }) disabled = false;
+
+ /** A custom label to show in the tooltip. */
+ @property({ attribute: 'copy-label' }) copyLabel = '';
+
+ /** A custom label to show in the tooltip after copying. */
+ @property({ attribute: 'success-label' }) successLabel = '';
+
+ /** A custom label to show in the tooltip when a copy error occurs. */
+ @property({ attribute: 'error-label' }) errorLabel = '';
+
+ /** The length of time to show feedback before restoring the default trigger. */
+ @property({ attribute: 'feedback-duration', type: Number }) feedbackDuration = 1000;
+
+ /** The preferred placement of the tooltip. */
+ @property({ attribute: 'tooltip-placement' }) tooltipPlacement: 'top' | 'right' | 'bottom' | 'left' = 'top';
+
+ private async handleCopy() {
+ if (this.disabled || this.isCopying) {
+ return;
+ }
+ this.isCopying = true;
+
+ // Copy the value by default
+ let valueToCopy = this.value;
+
+ // If an element is specified, copy from that instead
+ if (this.from) {
+ const root = this.getRootNode() as ShadowRoot | Document;
+
+ // Simple way to parse ids, properties, and attributes
+ const isProperty = this.from.includes('.');
+ const isAttribute = this.from.includes('[');
+ let id = this.from;
+ let field = '';
+
+ if (isProperty) {
+ [id, field] = this.from.trim().split('.');
+ } else if (isAttribute) {
+ [id, field] = this.from.trim().replace(/\]$/, '').split('[');
+ }
+
+ // Locate the target element by id
+ const target = 'getElementById' in root ? root.getElementById(id) : null;
+
+ if (target) {
+ if (isAttribute) {
+ valueToCopy = target.getAttribute(field) || '';
+ } else if (isProperty) {
+ // @ts-expect-error - deal with it
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ valueToCopy = target[field] || '';
+ } else {
+ valueToCopy = target.textContent || '';
+ }
+ } else {
+ // No target
+ this.showStatus('error');
+ this.emit('sl-error');
+ }
+ }
+
+ // No value
+ if (!valueToCopy) {
+ this.showStatus('error');
+ this.emit('sl-error');
+ } else {
+ try {
+ await navigator.clipboard.writeText(valueToCopy);
+ this.showStatus('success');
+ this.emit('sl-copy', {
+ detail: {
+ value: valueToCopy
+ }
+ });
+ } catch (error) {
+ // Rejected by browser
+ this.showStatus('error');
+ this.emit('sl-error');
+ }
+ }
+ }
+
+ private async showStatus(status: 'success' | 'error') {
+ const copyLabel = this.copyLabel || this.localize.term('copy');
+ const successLabel = this.successLabel || this.localize.term('copied');
+ const errorLabel = this.errorLabel || this.localize.term('error');
+ const iconToShow = status === 'success' ? this.successIcon : this.errorIcon;
+ const showAnimation = getAnimation(this, 'copy.in', { dir: 'ltr' });
+ const hideAnimation = getAnimation(this, 'copy.out', { dir: 'ltr' });
+
+ this.tooltip.content = status === 'success' ? successLabel : errorLabel;
+
+ // Show the feedback icon
+ await this.copyIcon.animate(hideAnimation.keyframes, hideAnimation.options).finished;
+ this.copyIcon.hidden = true;
+ this.status = status;
+ iconToShow.hidden = false;
+ await iconToShow.animate(showAnimation.keyframes, showAnimation.options).finished;
+
+ // After a brief delay, restore the original state
+ setTimeout(async () => {
+ await iconToShow.animate(hideAnimation.keyframes, hideAnimation.options).finished;
+ iconToShow.hidden = true;
+ this.status = 'rest';
+ this.copyIcon.hidden = false;
+ await this.copyIcon.animate(showAnimation.keyframes, showAnimation.options).finished;
+
+ this.tooltip.content = copyLabel;
+ this.isCopying = false;
+ }, this.feedbackDuration);
+ }
+
+ render() {
+ const copyLabel = this.copyLabel || this.localize.term('copy');
+
+ return html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ }
+}
+
+setDefaultAnimation('copy.in', {
+ keyframes: [
+ { scale: '.25', opacity: '.25' },
+ { scale: '1', opacity: '1' }
+ ],
+ options: { duration: 100 }
+});
+
+setDefaultAnimation('copy.out', {
+ keyframes: [
+ { scale: '1', opacity: '1' },
+ { scale: '.25', opacity: '0' }
+ ],
+ options: { duration: 100 }
+});
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'sl-copy-button': SlCopyButton;
+ }
+}
diff --git a/src/components/copy-button/copy-button.styles.ts b/src/components/copy-button/copy-button.styles.ts
new file mode 100644
index 00000000..29cd4cfb
--- /dev/null
+++ b/src/components/copy-button/copy-button.styles.ts
@@ -0,0 +1,49 @@
+import { css } from 'lit';
+import componentStyles from '../../styles/component.styles.js';
+
+export default css`
+ ${componentStyles}
+
+ :host {
+ --error-color: var(--sl-color-danger-600);
+ --success-color: var(--sl-color-success-600);
+
+ display: inline-block;
+ }
+
+ .copy-button__button {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ background: none;
+ border: none;
+ border-radius: var(--sl-border-radius-medium);
+ font-size: inherit;
+ color: inherit;
+ padding: var(--sl-spacing-x-small);
+ cursor: pointer;
+ transition: var(--sl-transition-x-fast) color;
+ }
+
+ .copy-button--success .copy-button__button {
+ color: var(--success-color);
+ }
+
+ .copy-button--error .copy-button__button {
+ color: var(--error-color);
+ }
+
+ .copy-button__button:focus-visible {
+ outline: var(--sl-focus-ring);
+ outline-offset: var(--sl-focus-ring-offset);
+ }
+
+ .copy-button__button[disabled] {
+ opacity: 0.5;
+ cursor: not-allowed !important;
+ }
+
+ slot {
+ display: inline-flex;
+ }
+`;
diff --git a/src/components/copy-button/copy-button.test.ts b/src/components/copy-button/copy-button.test.ts
new file mode 100644
index 00000000..79eb344c
--- /dev/null
+++ b/src/components/copy-button/copy-button.test.ts
@@ -0,0 +1,20 @@
+import '../../../dist/shoelace.js';
+import { expect, fixture, html } from '@open-wc/testing';
+import type SlCopyButton from './copy-button.js';
+
+// We use aria-live to announce labels via tooltips
+const ignoredRules = ['button-name'];
+
+describe('', () => {
+ let el: SlCopyButton;
+
+ describe('when provided no parameters', () => {
+ before(async () => {
+ el = await fixture(html` `);
+ });
+
+ it('should pass accessibility tests', async () => {
+ await expect(el).to.be.accessible({ ignoredRules });
+ });
+ });
+});
diff --git a/src/components/copy-button/copy-button.ts b/src/components/copy-button/copy-button.ts
new file mode 100644
index 00000000..0283a1e8
--- /dev/null
+++ b/src/components/copy-button/copy-button.ts
@@ -0,0 +1,4 @@
+import SlCopyButton from './copy-button.component.js';
+export * from './copy-button.component.js';
+export default SlCopyButton;
+SlCopyButton.define('sl-copy-button');
diff --git a/src/components/copy/copy.component.ts b/src/components/copy/copy.component.ts
deleted file mode 100644
index 61b99da1..00000000
--- a/src/components/copy/copy.component.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
-import { html } from 'lit';
-import { LocalizeController } from '../../utilities/localize.js';
-import { property, query } from 'lit/decorators.js';
-import ShoelaceElement from '../../internal/shoelace-element.js';
-import SlIconButton from '../icon-button/icon-button.component.js';
-import styles from './copy.styles.js';
-import type { CSSResultGroup } from 'lit';
-
-/**
- * @summary Copies data to the clipboard when the user clicks or taps the trigger.
- * @documentation https://shoelace.style/components/copy
- * @status experimental
- * @since 2.7
- *
- * @dependency sl-icon-button
- *
- * @event sl-copied - Emitted when the data has been copied.
- * @event sl-error - Emitted when the data could not be copied.
- *
- * @slot - A button that triggers copying.
- * @slot success - A button to briefly show when copying is successful.
- * @slot error - A button to briefly show when a copy error occurs.
- *
- * @animation copy.in - The animation to use when copy buttons animate in.
- * @animation copy.out - The animation to use when copy buttons animate out.
- */
-export default class SlCopy extends ShoelaceElement {
- static styles: CSSResultGroup = styles;
- static dependencies = {
- 'sl-icon-button': SlIconButton
- };
-
- private readonly localize = new LocalizeController(this);
-
- @query('slot:not([name])') defaultSlot: HTMLSlotElement;
- @query('slot[name="success"]') successSlot: HTMLSlotElement;
- @query('slot[name="error"]') errorSlot: HTMLSlotElement;
-
- /** The text value to copy. */
- @property({ type: String }) value = '';
-
- /** The length of time to show feedback before restoring the default trigger. */
- @property({ attribute: 'feedback-duration', type: Number }) feedbackDuration = 1000;
-
- /**
- * An id that references an element in the same document from which data will be copied. If the element is a link, the
- * `href` will be copied. If the element is a form control or has a `value` property, its `value` will be copied.
- * Otherwise, the element's text content will be copied.
- */
- @property({ type: String }) from = '';
-
- connectedCallback() {
- super.connectedCallback();
- this.setAttribute('aria-live', 'polite');
- }
-
- private async handleCopy() {
- // Copy the value by default
- let valueToCopy = this.value;
-
- // If an element is specified, copy from that instead
- if (this.from) {
- const root = this.getRootNode() as ShadowRoot | Document;
- const target = 'getElementById' in root ? root.getElementById(this.from) : false;
-
- if (target) {
- if (target instanceof HTMLAnchorElement && target.hasAttribute('href')) {
- valueToCopy = target.href;
- } else if ('value' in target) {
- valueToCopy = String(target.value);
- } else {
- valueToCopy = target.textContent || '';
- }
- } else {
- this.showStatus('error');
- this.emit('sl-error');
- }
- }
-
- // Copy from the value property otherwise
- if (valueToCopy) {
- try {
- await navigator.clipboard.writeText(valueToCopy);
- this.showStatus('success');
- this.emit('sl-copied');
- } catch (error) {
- this.showStatus('error');
- this.emit('sl-error');
- }
- }
- }
-
- private async showStatus(status: 'success' | 'error') {
- const target = status === 'success' ? this.successSlot : this.errorSlot;
- const showAnimation = getAnimation(this, 'copy.in', { dir: 'ltr' });
- const hideAnimation = getAnimation(this, 'copy.out', { dir: 'ltr' });
-
- await this.defaultSlot.animate(hideAnimation.keyframes, hideAnimation.options).finished;
- this.defaultSlot.hidden = true;
-
- target.hidden = false;
- await target.animate(showAnimation.keyframes, showAnimation.options).finished;
-
- setTimeout(async () => {
- await target.animate(hideAnimation.keyframes, hideAnimation.options).finished;
- target.hidden = true;
- this.defaultSlot.hidden = false;
- this.defaultSlot.animate(showAnimation.keyframes, showAnimation.options);
- }, this.feedbackDuration);
- }
-
- render() {
- return html`
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-}
-
-setDefaultAnimation('copy.in', {
- keyframes: [
- { scale: '.25', opacity: '.25' },
- { scale: '1', opacity: '1' }
- ],
- options: { duration: 125 }
-});
-
-setDefaultAnimation('copy.out', {
- keyframes: [
- { scale: '1', opacity: '1' },
- { scale: '.25', opacity: '0' }
- ],
- options: { duration: 125 }
-});
-
-declare global {
- interface HTMLElementTagNameMap {
- 'sl-copy': SlCopy;
- }
-}
diff --git a/src/components/copy/copy.styles.ts b/src/components/copy/copy.styles.ts
deleted file mode 100644
index 77cbf72c..00000000
--- a/src/components/copy/copy.styles.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { css } from 'lit';
-import componentStyles from '../../styles/component.styles.js';
-
-export default css`
- ${componentStyles}
-
- :host {
- display: inline-block;
- cursor: pointer;
- }
-
- slot {
- display: inline-flex;
- }
-
- .copy {
- background: none;
- border: none;
- font: inherit;
- color: inherit;
- padding: 0;
- margin: 0;
- }
-`;
diff --git a/src/components/copy/copy.test.ts b/src/components/copy/copy.test.ts
deleted file mode 100644
index 91349632..00000000
--- a/src/components/copy/copy.test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import '../../../dist/shoelace.js';
-import { expect, fixture, html } from '@open-wc/testing';
-import type SlCopy from './copy.js';
-
-describe('', () => {
- let el: SlCopy;
-
- describe('when provided no parameters', () => {
- before(async () => {
- el = await fixture(html` `);
- });
-
- it('should pass accessibility tests', async () => {
- await expect(el).to.be.accessible();
- });
-
- // it('should initially be in the trigger status', () => {
- // expect(el.copyStatus).to.equal('trigger');
- // });
-
- // it('should reset copyStatus after 2 seconds', async () => {
- // expect(el.copyStatus).to.equal('trigger');
- // await el.copy(); // this will result in an error as copy needs to always be called from a user action
- // expect(el.copyStatus).to.equal('error');
- // await aTimeout(2100);
- // expect(el.copyStatus).to.equal('trigger');
- // });
- });
-});
diff --git a/src/components/copy/copy.ts b/src/components/copy/copy.ts
deleted file mode 100644
index ce56b309..00000000
--- a/src/components/copy/copy.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import SlCopy from './copy.component.js';
-export * from './copy.component.js';
-export default SlCopy;
-SlCopy.define('sl-copy');
diff --git a/src/components/icon/library.system.ts b/src/components/icon/library.system.ts
index 0dedf7e6..e822055f 100644
--- a/src/components/icon/library.system.ts
+++ b/src/components/icon/library.system.ts
@@ -41,8 +41,8 @@ const icons = {
`,
copy: `
-
-
+
+
`,
eye: `
diff --git a/src/components/tooltip/tooltip.styles.ts b/src/components/tooltip/tooltip.styles.ts
index a88bf1f6..c4d9d6db 100644
--- a/src/components/tooltip/tooltip.styles.ts
+++ b/src/components/tooltip/tooltip.styles.ts
@@ -51,5 +51,6 @@ export default css`
color: var(--sl-tooltip-color);
padding: var(--sl-tooltip-padding);
pointer-events: none;
+ user-select: none;
}
`;
diff --git a/src/events/events.ts b/src/events/events.ts
index 8786b25d..739fcd67 100644
--- a/src/events/events.ts
+++ b/src/events/events.ts
@@ -8,7 +8,7 @@ export type { default as SlChangeEvent } from './sl-change';
export type { default as SlClearEvent } from './sl-clear';
export type { default as SlCloseEvent } from './sl-close';
export type { default as SlCollapseEvent } from './sl-collapse';
-export type { default as SlCopiedEvent } from './sl-copied';
+export type { default as SlCopyEvent } from './sl-copy';
export type { default as SlErrorEvent } from './sl-error';
export type { default as SlExpandEvent } from './sl-expand';
export type { default as SlFinishEvent } from './sl-finish';
diff --git a/src/events/sl-copied.ts b/src/events/sl-copied.ts
deleted file mode 100644
index b170d5cb..00000000
--- a/src/events/sl-copied.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-type SlCopiedEvent = CustomEvent>;
-
-declare global {
- interface GlobalEventHandlersEventMap {
- 'sl-copied': SlCopiedEvent;
- }
-}
-
-export default SlCopiedEvent;
diff --git a/src/events/sl-copy.ts b/src/events/sl-copy.ts
new file mode 100644
index 00000000..65e697b5
--- /dev/null
+++ b/src/events/sl-copy.ts
@@ -0,0 +1,9 @@
+type SlCopyEvent = CustomEvent<{ value: string }>;
+
+declare global {
+ interface GlobalEventHandlersEventMap {
+ 'sl-copy': SlCopyEvent;
+ }
+}
+
+export default SlCopyEvent;
diff --git a/src/shoelace.ts b/src/shoelace.ts
index 5c02bdfd..e6660e9d 100644
--- a/src/shoelace.ts
+++ b/src/shoelace.ts
@@ -13,7 +13,7 @@ export { default as SlCarousel } from './components/carousel/carousel.js';
export { default as SlCarouselItem } from './components/carousel-item/carousel-item.js';
export { default as SlCheckbox } from './components/checkbox/checkbox.js';
export { default as SlColorPicker } from './components/color-picker/color-picker.js';
-export { default as SlCopy } from './components/copy/copy.js';
+export { default as SlCopyButton } from './components/copy-button/copy-button.js';
export { default as SlDetails } from './components/details/details.js';
export { default as SlDialog } from './components/dialog/dialog.js';
export { default as SlDivider } from './components/divider/divider.js';