@leavepulse/ui 0.85.2 → 0.86.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +367 -72
  2. package/dist/assets/{LpTable-CCk0ZHo7.css → LpTable-CEFsRz5J.css} +19 -19
  3. package/dist/chunks/{LpCodeBlock-Dx0J9xlm.js → LpCodeBlock-kGT15Zou.js} +1 -1
  4. package/dist/chunks/{LpCodeDisplay-BlN7U8Z-.js → LpCodeDisplay-DlwzZo9o.js} +1 -1
  5. package/dist/chunks/{LpCopyButton-BrbvIVMG.js → LpCopyButton-TGoE43mE.js} +2 -2
  6. package/dist/chunks/{LpDataList-DhETPjzv.js → LpDataList-Due909Cy.js} +1 -1
  7. package/dist/chunks/{LpInspector-CqPgVXrm.js → LpInspector-BNxjuWMP.js} +1 -1
  8. package/dist/chunks/{LpLightbox-Bg1LW_ED.js → LpLightbox-DVyiB4dw.js} +1 -1
  9. package/dist/chunks/{LpMediaGallery-pX-znMeP.js → LpMediaGallery-OJemmbV7.js} +1 -1
  10. package/dist/chunks/{LpPaletteTheme-Bi6pD1GL.js → LpPaletteTheme-CWFz4H2D.js} +1 -1
  11. package/dist/chunks/{LpSecretField-_B2qPfk8.js → LpSecretField-Dp-c7wWc.js} +1 -1
  12. package/dist/chunks/{LpThemeStudio-C1xKiv8C.js → LpThemeStudio-RyI-SWiw.js} +1 -1
  13. package/dist/chunks/{LpThemeSwitcher-Dk4Bk6u1.js → LpThemeSwitcher-BZYJ0AXy.js} +1 -1
  14. package/dist/chunks/{LpToaster-C_CFWSys.js → LpToaster-YcLJFSsH.js} +1 -1
  15. package/dist/chunks/{LpTruncate-CBUKnr36.js → LpTruncate-DCZjfcE-.js} +1 -1
  16. package/dist/components/LpCodeBlock.vue.js +1 -1
  17. package/dist/components/LpCodeDisplay.vue.js +1 -1
  18. package/dist/components/LpCopyButton.vue.js +1 -1
  19. package/dist/components/LpDataList.vue.js +1 -1
  20. package/dist/components/LpInspector.vue.js +1 -1
  21. package/dist/components/LpLightbox.vue.js +1 -1
  22. package/dist/components/LpMediaGallery.vue.js +1 -1
  23. package/dist/components/LpPaletteTheme.vue.js +1 -1
  24. package/dist/components/LpSecretField.vue.js +1 -1
  25. package/dist/components/LpTable.vue.d.ts +2 -0
  26. package/dist/components/LpTable.vue.js +381 -370
  27. package/dist/components/LpThemeStudio.vue.js +1 -1
  28. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  29. package/dist/components/LpToaster.vue.js +1 -1
  30. package/dist/components/LpTruncate.vue.js +1 -1
  31. package/dist/composables/useClipboard.d.ts +17 -3
  32. package/dist/composables/useClipboard.js +6 -2
  33. package/dist/index.js +13 -13
  34. package/package.json +3 -2
  35. package/src/components/LpCopyButton.vue +2 -2
  36. package/src/components/LpTable.vue +32 -9
  37. package/src/composables/useClipboard.ts +18 -5
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeStudio-C1xKiv8C.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeStudio-RyI-SWiw.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-Dk4Bk6u1.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-BZYJ0AXy.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpToaster-C_CFWSys.js";
1
+ import { _ as _sfc_main } from "../chunks/LpToaster-YcLJFSsH.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTruncate-CBUKnr36.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTruncate-DCZjfcE-.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,16 +1,30 @@
1
+ /**
2
+ * The kit's words for copying, scope "copyButton": LpCopyButton's own text and
3
+ * what `toast: true` says. One set, so an app that translates the scope once
4
+ * has every copy in it speak the same language.
5
+ */
6
+ export declare const COPY_LABELS: {
7
+ copy: string;
8
+ copied: string;
9
+ failed: string;
10
+ };
1
11
  export interface CopyOptions {
2
12
  /**
3
13
  * Toast to raise on success. `false` (the default) stays silent, for callers
4
14
  * whose button already shows the tick and would otherwise say it twice.
15
+ * `true` says the kit's "Copied", in the app's language when its config
16
+ * translates the "copyButton" scope, so a menu item that copies does not
17
+ * have to bring its own word for it.
5
18
  */
6
- toast?: string | false;
19
+ toast?: string | boolean;
7
20
  /**
8
21
  * Toast to raise when the copy fails. Defaults to silence for the same
9
22
  * reason the kit's other best-effort paths are silent: a copy that didn't
10
23
  * happen is usually obvious, and a scary red box over a blocked clipboard
11
- * helps nobody. Pass a string where losing the copy actually costs something.
24
+ * helps nobody. Pass a string where losing the copy actually costs something,
25
+ * or `true` for the kit's own "Couldn't copy".
12
26
  */
13
- errorToast?: string | false;
27
+ errorToast?: string | boolean;
14
28
  }
15
29
  export interface UseClipboard {
16
30
  /**
@@ -1,4 +1,5 @@
1
1
  import { ref, onScopeDispose } from "vue";
2
+ import { useLabels } from "./useLpConfig.js";
2
3
  import { useToast } from "./useToast.js";
3
4
  async function writeAsync(text) {
4
5
  if (typeof navigator === "undefined" || !navigator.clipboard) return false;
@@ -44,9 +45,11 @@ async function writeBlob(blob) {
44
45
  }
45
46
  }
46
47
  const COPIED_MS = 1400;
48
+ const COPY_LABELS = { copy: "Copy", copied: "Copied", failed: "Couldn't copy" };
47
49
  function useClipboard() {
48
50
  const copied = ref(false);
49
51
  const { success, error } = useToast();
52
+ const labels = useLabels("copyButton", COPY_LABELS);
50
53
  let timer;
51
54
  async function copy(data, options = {}) {
52
55
  const { toast = false, errorToast = false } = options;
@@ -56,10 +59,10 @@ function useClipboard() {
56
59
  copied.value = true;
57
60
  clearTimeout(timer);
58
61
  timer = setTimeout(() => copied.value = false, COPIED_MS);
59
- if (toast) success(toast);
62
+ if (toast) success(toast === true ? labels.value.copied : toast);
60
63
  return true;
61
64
  } catch {
62
- if (errorToast) error(errorToast);
65
+ if (errorToast) error(errorToast === true ? labels.value.failed : errorToast);
63
66
  return false;
64
67
  }
65
68
  }
@@ -67,5 +70,6 @@ function useClipboard() {
67
70
  return { copied, copy };
68
71
  }
69
72
  export {
73
+ COPY_LABELS,
70
74
  useClipboard
71
75
  };
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import { chartView, lttbIndices, minMaxBuckets, panRange, slotOf, sourceOf, zoom
19
19
  import { useChartSync } from "./composables/useChartSync.js";
20
20
  import { createLinkedView, linkChart, linkLog, linkPipeline, linkTable, linkTopology, provideLinkedView, useLinkedView } from "./composables/useLinkedView.js";
21
21
  import { _ as _14 } from "./chunks/LpCheckbox-DcXC2qd5.js";
22
- import { _ as _15 } from "./chunks/LpCodeBlock-Dx0J9xlm.js";
22
+ import { _ as _15 } from "./chunks/LpCodeBlock-kGT15Zou.js";
23
23
  import { tokenizeLines } from "./components/codeHighlight.js";
24
24
  import { _ as _16 } from "./chunks/LpColorPicker-Cjna_Bio.js";
25
25
  import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv } from "./components/color.js";
@@ -56,14 +56,14 @@ import { i, b, a, c } from "./chunks/icons-V0k93Puc.js";
56
56
  import { default as default10 } from "./components/LpImageEditor.vue.js";
57
57
  import { _ as _33 } from "./chunks/LpInfraNode-BsExhdPE.js";
58
58
  import { _ as _34 } from "./chunks/LpInput-CKMblRtp.js";
59
- import { _ as _35 } from "./chunks/LpLightbox-Bg1LW_ED.js";
59
+ import { _ as _35 } from "./chunks/LpLightbox-DVyiB4dw.js";
60
60
  import { fileNameOf, isVideoItem } from "./components/lightbox.js";
61
61
  import { _ as _36 } from "./chunks/LpLink-Krkjhe_y.js";
62
62
  import { _ as _37 } from "./chunks/LpLogViewer-Bki-IdNC.js";
63
63
  import { _ as _38 } from "./chunks/LpLogInspector-Yj310k4n.js";
64
64
  import { compilePattern, findIds, messageShape, structuredPayload, timeMs } from "./components/logTools.js";
65
65
  import { countLevels, matchPositions, matchesTerm, ringWindow, stepMatch } from "./components/logSearch.js";
66
- import { _ as _39 } from "./chunks/LpMediaGallery-pX-znMeP.js";
66
+ import { _ as _39 } from "./chunks/LpMediaGallery-OJemmbV7.js";
67
67
  import { _ as _40 } from "./chunks/LpModal-B5y9_LgA.js";
68
68
  import { _ as _41 } from "./chunks/LpModalNav-BLvDVl2-.js";
69
69
  import { _ as _42 } from "./chunks/LpNotificationBell-Bf0u3t5q.js";
@@ -121,9 +121,9 @@ import { _ as _79 } from "./chunks/LpBottomNav-BcJVkAhh.js";
121
121
  import { _ as _80 } from "./chunks/LpTagsInput-By7LMb47.js";
122
122
  import { addTags, splitTags } from "./components/tags.js";
123
123
  import { _ as _81 } from "./chunks/LpTextarea-Ck6RAfzu.js";
124
- import { _ as _82 } from "./chunks/LpThemeSwitcher-Dk4Bk6u1.js";
124
+ import { _ as _82 } from "./chunks/LpThemeSwitcher-BZYJ0AXy.js";
125
125
  import { _ as _83 } from "./chunks/LpTilt-B7KHX1zH.js";
126
- import { _ as _84 } from "./chunks/LpToaster-C_CFWSys.js";
126
+ import { _ as _84 } from "./chunks/LpToaster-YcLJFSsH.js";
127
127
  import { _ as _85 } from "./chunks/LpTooltip-Bz_rrq5S.js";
128
128
  import { _ as _86 } from "./chunks/LpTour-D_AN_SsZ.js";
129
129
  import { holeOf, openContextMenu, panelsAround } from "./components/tour.js";
@@ -139,7 +139,7 @@ import { blockTitle, defineBlocks } from "./layout/registry.js";
139
139
  import { layoutController, useLayout } from "./layout/useLayout.js";
140
140
  import { useWorkspaces } from "./layout/useWorkspaces.js";
141
141
  import { _ as _92 } from "./chunks/LpWorkspaceSwitcher-D50sHs9N.js";
142
- import { _ as _93 } from "./chunks/LpInspector-CqPgVXrm.js";
142
+ import { _ as _93 } from "./chunks/LpInspector-BNxjuWMP.js";
143
143
  import { _ as _94 } from "./chunks/LpWindow-Bo5qQgVY.js";
144
144
  import { WINDOW_DOCKS, clampRect, dockAt, dockedRect, isWindowRect, resizeRect, snapRect, useWindowLayer } from "./composables/windowing.js";
145
145
  import { THEME_COOKIE, applyTheme, applyThemeWithTransition, bootstrapTheme, cacheSystemThemes, normalizeTheme, parseTheme, prefersDarkScheme, revealTransition, serializeTheme, themeEntries, themeFromCookie, themeToCssRule, themeToCssVars, useTheme } from "./theme/useTheme.js";
@@ -188,7 +188,7 @@ import { useZoomPan } from "./composables/useZoomPan.js";
188
188
  import { useToast } from "./composables/useToast.js";
189
189
  import { _ as _95 } from "./chunks/LpThemeProvider-XK6ZeWAE.js";
190
190
  import { _ as _96 } from "./chunks/LpFrame-Bv5Fphte.js";
191
- import { _ as _97 } from "./chunks/LpThemeStudio-C1xKiv8C.js";
191
+ import { _ as _97 } from "./chunks/LpThemeStudio-RyI-SWiw.js";
192
192
  import { _ as _98 } from "./chunks/LpThemeStudioSection-B7bt8Iyg.js";
193
193
  import { isInternalHref, resolveLink } from "./components/link.js";
194
194
  import { _ as _99 } from "./chunks/LpRepeater-BKq5XPh4.js";
@@ -211,12 +211,12 @@ import { useLoadingGate } from "./composables/useLoadingGate.js";
211
211
  import { useNow } from "./composables/useNow.js";
212
212
  import { _ as _108 } from "./chunks/LpCountdown-DVl7vXI1.js";
213
213
  import { DEFAULT_MILESTONES, DEFAULT_UNIT_LABELS, ceilToSecond, crossedMilestone, formatElapsed, formatTime, msToNextSecond, splitTime, timeParts } from "./components/timeSpan.js";
214
- import { _ as _109 } from "./chunks/LpCopyButton-BrbvIVMG.js";
215
- import { _ as _110 } from "./chunks/LpSecretField-_B2qPfk8.js";
216
- import { _ as _111 } from "./chunks/LpDataList-DhETPjzv.js";
214
+ import { _ as _109 } from "./chunks/LpCopyButton-TGoE43mE.js";
215
+ import { _ as _110 } from "./chunks/LpSecretField-Dp-c7wWc.js";
216
+ import { _ as _111 } from "./chunks/LpDataList-Due909Cy.js";
217
217
  import { _ as _112 } from "./chunks/LpEditable-DCPdDDBb.js";
218
- import { _ as _113 } from "./chunks/LpTruncate-CBUKnr36.js";
219
- import { _ as _114 } from "./chunks/LpCodeDisplay-BlN7U8Z-.js";
218
+ import { _ as _113 } from "./chunks/LpTruncate-DCZjfcE-.js";
219
+ import { _ as _114 } from "./chunks/LpCodeDisplay-DlwzZo9o.js";
220
220
  import { formatRemaining, groupCode, middleTruncate, normalizeCode } from "./components/fieldText.js";
221
221
  import { _ as _115 } from "./chunks/LpHoverCard-CHE9jAJG.js";
222
222
  import { _ as _116 } from "./chunks/LpIdenticon-Dbbr4fvt.js";
@@ -246,7 +246,7 @@ import { _ as _128 } from "./chunks/LpCommandScope-DRUvHiX7.js";
246
246
  import { createPaletteRegistry, mergePaletteCommands, usePaletteCommands, vPalette } from "./composables/usePaletteCommands.js";
247
247
  import { commandShortcuts, commandToHotkey, commandToMenuItem, commandToPalette, useCommands } from "./composables/useCommands.js";
248
248
  import { usePaletteTheme } from "./theme/palette.js";
249
- import { _ as _129 } from "./chunks/LpPaletteTheme-Bi6pD1GL.js";
249
+ import { _ as _129 } from "./chunks/LpPaletteTheme-CWFz4H2D.js";
250
250
  const uk = {
251
251
  actionBar: { dismiss: "Закрити" },
252
252
  activityLine: { busy: "Завантаження" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.85.2",
3
+ "version": "0.86.1",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -72,7 +72,8 @@
72
72
  "check:size": "node scripts/check-size.mjs",
73
73
  "test:coverage": "bun test --coverage",
74
74
  "gen:icons": "node scripts/gen-kit-icons.mjs",
75
- "gen:meta": "node scripts/gen-preview-meta.mjs"
75
+ "gen:meta": "node scripts/gen-preview-meta.mjs",
76
+ "gen:readme": "node scripts/readme-catalogue.mjs"
76
77
  },
77
78
  "peerDependencies": {
78
79
  "@iconify-json/lucide": "^1.2.0",
@@ -39,7 +39,7 @@ export type CopyValue =
39
39
  * `failed` state and the `error` event rather than as an exception.
40
40
  */
41
41
  import { computed, ref, useAttrs } from "vue"
42
- import { useClipboard } from "../composables/useClipboard"
42
+ import { COPY_LABELS, useClipboard } from "../composables/useClipboard"
43
43
  import { useLabels } from "../composables/useLpConfig"
44
44
  import LpButton from "./LpButton.vue"
45
45
  import LpIcon from "./LpIcon.vue"
@@ -92,7 +92,7 @@ const emit = defineEmits<{
92
92
 
93
93
  const text = useLabels<CopyButtonLabels>(
94
94
  "copyButton",
95
- { copy: "Copy", copied: "Copied", failed: "Couldn't copy" },
95
+ COPY_LABELS,
96
96
  () => props.labels,
97
97
  )
98
98
 
@@ -5,7 +5,7 @@ export default { inheritAttrs: false }
5
5
  </script>
6
6
 
7
7
  <script setup lang="ts" generic="T extends Record<string, unknown>">
8
- import { computed, h, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useAttrs, useId, watch } from "vue"
8
+ import { computed, h, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useAttrs, useId, useSlots, watch } from "vue"
9
9
  import { suppressPaletteDiscovery, usePaletteCommands, usePaletteScope } from "../composables/usePaletteCommands"
10
10
  import { menuPaletteCommands } from "./commandPalette"
11
11
  import LpDataList from "./LpDataList.vue"
@@ -1383,8 +1383,14 @@ const countText = computed(() => {
1383
1383
  // As documented on `summary`: the bar shows on its own while filters are
1384
1384
  // active or rows are selected, not merely because rows can be selected, which
1385
1385
  // left a lone "75 rows" over every selectable table.
1386
+ const slots = useSlots()
1386
1387
  const showBar = computed(
1387
- () => props.summary || (!!props.selectable && props.selected.length > 0) || activeFilters.value.length > 0,
1388
+ () =>
1389
+ props.summary ||
1390
+ props.exportable ||
1391
+ !!slots.toolbar ||
1392
+ (!!props.selectable && props.selected.length > 0) ||
1393
+ activeFilters.value.length > 0,
1388
1394
  )
1389
1395
  const filteredOut = computed(() => {
1390
1396
  if (busy.value || failed.value || displayRows.value.length) return false
@@ -1606,12 +1612,6 @@ usePaletteCommands(() => {
1606
1612
  <!-- No top fade: a sticky header sits in the masked band and would be dimmed
1607
1613
  by it. The bottom edge is left crisp with it for symmetry. -->
1608
1614
  <div ref="frameEl" class="flex flex-col gap-3" :class="rootClass">
1609
- <div v-if="exportable" class="flex justify-end">
1610
- <LpButton variant="ghost" size="sm" @click="exportCsv()">
1611
- <LpIcon name="lucide:download" :size="14" />
1612
- {{ text.exportCsv }}
1613
- </LpButton>
1614
- </div>
1615
1615
  <!-- What the table is showing right now: the filters in force, each one
1616
1616
  a chip that clears it, the row count, and what is selected with room
1617
1617
  for the consumer's bulk actions. Eased in and out (LpShift), so the
@@ -1619,7 +1619,14 @@ usePaletteCommands(() => {
1619
1619
  <!-- The gap below the bar lives inside the eased box (pb-3), cancelled
1620
1620
  outside it (-mb-3): an empty box must not leave the root's gap behind. -->
1621
1621
  <LpShift class="-mb-3">
1622
- <div v-if="showBar" class="flex min-h-8 items-center gap-x-3 pb-3 text-xs">
1622
+ <!-- One line above the table: the page's own controls (#toolbar), the
1623
+ filters in force, then the status and Export at the end. Export used
1624
+ to sit on a line of its own and the page's search and filters on a
1625
+ third, so every table opened with two bands of mostly empty space. -->
1626
+ <div v-if="showBar" class="flex min-h-8 flex-wrap items-center gap-x-3 gap-y-2 pb-3 text-xs">
1627
+ <div v-if="$slots.toolbar" class="flex min-w-0 flex-wrap items-center gap-2 text-sm">
1628
+ <slot name="toolbar" />
1629
+ </div>
1623
1630
  <div v-if="activeFilters.length" class="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
1624
1631
  <LpBadge
1625
1632
  v-for="f in activeFilters"
@@ -1689,10 +1696,25 @@ usePaletteCommands(() => {
1689
1696
  <slot name="bulk" :selected="selected" :all-matching="allMatching" :clear="clearSelection" />
1690
1697
  </span>
1691
1698
  </Transition>
1699
+ <LpButton v-if="exportable" variant="ghost" size="sm" @click="exportCsv()">
1700
+ <LpIcon name="lucide:download" :size="14" />
1701
+ {{ text.exportCsv }}
1702
+ </LpButton>
1692
1703
  </div>
1693
1704
  </LpShift>
1694
1705
  </div>
1695
1706
  </LpShift>
1707
+ <!-- The frame eases to its new height as rows come and go. A leaving row
1708
+ stays in the flow while it fades (a positioned <tr> loses the column
1709
+ grid), so when it is finally removed the table lost its height in one
1710
+ frame: the rows slid aside smoothly and the box under them snapped.
1711
+ Always on rather than only while `animating`: LpShift starts over when
1712
+ it is switched on, so the change that switched it on was never eased.
1713
+ Rows the parent replaces wholesale still swap at once; only the frame
1714
+ eases. A virtual table owns its own height and stays out. -->
1715
+ <!-- flex-auto, not flex-1: a zero basis overrides the height LpShift sets
1716
+ while it eases, and the box jumped to its new size regardless. -->
1717
+ <LpShift :disabled="!!virtualRowHeight" class="flex min-h-0 flex-auto flex-col">
1696
1718
  <LpScrollArea
1697
1719
  v-bind="scrollAttrs"
1698
1720
  :fade="false"
@@ -1994,6 +2016,7 @@ usePaletteCommands(() => {
1994
2016
  </table>
1995
2017
  </LpContextMenu>
1996
2018
  </LpScrollArea>
2019
+ </LpShift>
1997
2020
  <LpPagination
1998
2021
  v-if="load && total > pageSize"
1999
2022
  v-model:page="page"
@@ -1,4 +1,5 @@
1
1
  import { onScopeDispose, ref } from "vue"
2
+ import { useLabels } from "./useLpConfig"
2
3
  import { useToast } from "./useToast"
3
4
 
4
5
  /*
@@ -90,19 +91,30 @@ async function writeBlob(blob: Blob): Promise<boolean> {
90
91
  /** How long `copied` stays true — long enough to read, short enough to not linger. */
91
92
  const COPIED_MS = 1400
92
93
 
94
+ /**
95
+ * The kit's words for copying, scope "copyButton": LpCopyButton's own text and
96
+ * what `toast: true` says. One set, so an app that translates the scope once
97
+ * has every copy in it speak the same language.
98
+ */
99
+ export const COPY_LABELS = { copy: "Copy", copied: "Copied", failed: "Couldn't copy" }
100
+
93
101
  export interface CopyOptions {
94
102
  /**
95
103
  * Toast to raise on success. `false` (the default) stays silent, for callers
96
104
  * whose button already shows the tick and would otherwise say it twice.
105
+ * `true` says the kit's "Copied", in the app's language when its config
106
+ * translates the "copyButton" scope, so a menu item that copies does not
107
+ * have to bring its own word for it.
97
108
  */
98
- toast?: string | false
109
+ toast?: string | boolean
99
110
  /**
100
111
  * Toast to raise when the copy fails. Defaults to silence for the same
101
112
  * reason the kit's other best-effort paths are silent: a copy that didn't
102
113
  * happen is usually obvious, and a scary red box over a blocked clipboard
103
- * helps nobody. Pass a string where losing the copy actually costs something.
114
+ * helps nobody. Pass a string where losing the copy actually costs something,
115
+ * or `true` for the kit's own "Couldn't copy".
104
116
  */
105
- errorToast?: string | false
117
+ errorToast?: string | boolean
106
118
  }
107
119
 
108
120
  export interface UseClipboard {
@@ -143,6 +155,7 @@ export interface UseClipboard {
143
155
  export function useClipboard(): UseClipboard {
144
156
  const copied = ref(false)
145
157
  const { success, error } = useToast()
158
+ const labels = useLabels("copyButton", COPY_LABELS)
146
159
  let timer: ReturnType<typeof setTimeout> | undefined
147
160
 
148
161
  async function copy(data: string | Blob, options: CopyOptions = {}): Promise<boolean> {
@@ -155,10 +168,10 @@ export function useClipboard(): UseClipboard {
155
168
  copied.value = true
156
169
  clearTimeout(timer)
157
170
  timer = setTimeout(() => (copied.value = false), COPIED_MS)
158
- if (toast) success(toast)
171
+ if (toast) success(toast === true ? labels.value.copied : toast)
159
172
  return true
160
173
  } catch {
161
- if (errorToast) error(errorToast)
174
+ if (errorToast) error(errorToast === true ? labels.value.failed : errorToast)
162
175
  return false
163
176
  }
164
177
  }