@nadicodeai/ui 6.0.1 → 6.1.0

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 (63) hide show
  1. package/dist/components/action-menu.d.ts +8 -0
  2. package/dist/components/action-menu.d.ts.map +1 -0
  3. package/dist/components/action-menu.js +8 -0
  4. package/dist/components/card.d.ts +1 -1
  5. package/dist/components/card.d.ts.map +1 -1
  6. package/dist/components/card.js +5 -3
  7. package/dist/components/chart-breakdown-table.d.ts +3 -2
  8. package/dist/components/chart-breakdown-table.d.ts.map +1 -1
  9. package/dist/components/chart-breakdown-table.js +13 -9
  10. package/dist/components/chart-bullet-bar.d.ts +13 -12
  11. package/dist/components/chart-bullet-bar.d.ts.map +1 -1
  12. package/dist/components/chart-bullet-bar.js +16 -36
  13. package/dist/components/chart-kpi-stat.d.ts +26 -15
  14. package/dist/components/chart-kpi-stat.d.ts.map +1 -1
  15. package/dist/components/chart-kpi-stat.js +28 -4
  16. package/dist/components/chart-time-series-layout.d.ts +6 -0
  17. package/dist/components/chart-time-series-layout.d.ts.map +1 -0
  18. package/dist/components/chart-time-series-layout.js +6 -0
  19. package/dist/components/chart-time-series.d.ts +6 -2
  20. package/dist/components/chart-time-series.d.ts.map +1 -1
  21. package/dist/components/chart-time-series.js +27 -9
  22. package/dist/components/chart.js +1 -1
  23. package/dist/components/collection-filters.d.ts +28 -0
  24. package/dist/components/collection-filters.d.ts.map +1 -0
  25. package/dist/components/collection-filters.js +54 -0
  26. package/dist/components/collection-scope.d.ts +15 -0
  27. package/dist/components/collection-scope.d.ts.map +1 -0
  28. package/dist/components/collection-scope.js +7 -0
  29. package/dist/components/collection-toolbar.d.ts +10 -0
  30. package/dist/components/collection-toolbar.d.ts.map +1 -0
  31. package/dist/components/collection-toolbar.js +4 -0
  32. package/dist/components/settings-facts.d.ts +28 -0
  33. package/dist/components/settings-facts.d.ts.map +1 -0
  34. package/dist/components/settings-facts.js +7 -0
  35. package/dist/components/settings-section.d.ts +5 -5
  36. package/dist/components/settings-section.d.ts.map +1 -1
  37. package/dist/components/settings-section.js +6 -11
  38. package/dist/components/sidebar.d.ts +4 -1
  39. package/dist/components/sidebar.d.ts.map +1 -1
  40. package/dist/components/sidebar.js +3 -3
  41. package/dist/components/table-layouts.d.ts +22 -0
  42. package/dist/components/table-layouts.d.ts.map +1 -0
  43. package/dist/components/table-layouts.js +20 -0
  44. package/dist/components/table.d.ts +15 -2
  45. package/dist/components/table.d.ts.map +1 -1
  46. package/dist/components/table.js +8 -4
  47. package/dist/eslint/rules/jsx-class-utility.js +44 -12
  48. package/dist/eslint/rules/no-resting-settings-control.js +15 -13
  49. package/dist/eslint/rules/no-shadcn-appearance-override.js +189 -36
  50. package/dist/eslint/rules/shadcn-composition.js +48 -3
  51. package/dist/hooks/use-confirm-action.d.ts +0 -6
  52. package/dist/hooks/use-confirm-action.d.ts.map +1 -1
  53. package/dist/hooks/use-confirm-action.js +5 -12
  54. package/dist/index.d.ts +5 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +5 -0
  57. package/dist/lib/use-copy-to-clipboard.d.ts +0 -41
  58. package/dist/lib/use-copy-to-clipboard.d.ts.map +1 -1
  59. package/dist/lib/use-copy-to-clipboard.js +6 -15
  60. package/docs/component-classification.md +3 -2
  61. package/docs/contract.md +79 -8
  62. package/docs/product-compositions.md +14 -11
  63. package/package.json +3 -2
@@ -6,8 +6,8 @@
6
6
  * editor that changes them. The regression this catches is a form control
7
7
  * rendered beside those facts rather than inside the editor they summon.
8
8
  *
9
- * "Inside the editor" means inside an `ActionDialogForm`'s `fields` prop, or
10
- * inside a `Dialog`/`Drawer` subtree for a surface that owns its own. Being a
9
+ * "Inside the editor" means inside a `SettingsSection`, an `ActionDialogForm`'s
10
+ * `fields` prop, or a `Dialog`/`Drawer` subtree for a surface that owns its own. Being a
11
11
  * CHILD of `ActionDialogForm` is the opposite: children are the resting surface
12
12
  * the dialog is opened from, which is exactly where a control must not be.
13
13
  *
@@ -21,6 +21,7 @@
21
21
  * here. Replaces `restingSettingsControlViolations` in the retired portal
22
22
  * page-grammar analyzers (#821).
23
23
  */
24
+ import { jsxComponentName } from "./jsx-class-utility.js";
24
25
 
25
26
  // The controls a person types or picks into. Lowercase `input` is deliberately
26
27
  // absent: a hidden field carries context a summoned editor already decided, and
@@ -37,15 +38,15 @@ const RESTING_CONTROL_TAGS = new Set([
37
38
 
38
39
  const FACTS_SECTION_TAG = "SettingsFactsSection";
39
40
 
40
- function elementName(node) {
41
- return node?.name?.type === "JSXIdentifier" ? node.name.name : undefined;
42
- }
43
-
44
- function summons(node) {
45
- if (node.type === "JSXAttribute") return node.name.name === "fields";
41
+ function summons(node, context) {
42
+ if (node.type === "JSXAttribute") {
43
+ return (
44
+ node.name.name === "fields" && jsxComponentName(node.parent, context) === "ActionDialogForm"
45
+ );
46
+ }
46
47
  if (node.type !== "JSXElement") return false;
47
- const name = elementName(node.openingElement);
48
- return Boolean(name && (name.startsWith("Dialog") || name.startsWith("Drawer")));
48
+ const name = jsxComponentName(node.openingElement, context);
49
+ return ["SettingsSection", "Dialog", "DialogContent", "Drawer", "DrawerContent"].includes(name);
49
50
  }
50
51
 
51
52
  export const noRestingSettingsControl = {
@@ -58,7 +59,7 @@ export const noRestingSettingsControl = {
58
59
  schema: [],
59
60
  messages: {
60
61
  restingControl:
61
- "<{{tag}}> rests beside its facts: put it in the editor the facts summon (an ActionDialogForm `fields` prop, or a Dialog/Drawer subtree).",
62
+ "<{{tag}}> rests beside its facts: use SettingsSection for a form, or put it in the summoned editor (ActionDialogForm `fields`, Dialog, or Drawer).",
62
63
  },
63
64
  },
64
65
  create(context) {
@@ -67,13 +68,14 @@ export const noRestingSettingsControl = {
67
68
 
68
69
  return {
69
70
  JSXOpeningElement(node) {
70
- const name = elementName(node);
71
+ const name = jsxComponentName(node, context);
71
72
  if (name === FACTS_SECTION_TAG) {
72
73
  declaresFacts = true;
73
74
  return;
74
75
  }
75
76
  if (!name || !RESTING_CONTROL_TAGS.has(name)) return;
76
- if (context.sourceCode.getAncestors(node).some(summons)) return;
77
+ if (context.sourceCode.getAncestors(node).some((ancestor) => summons(ancestor, context)))
78
+ return;
77
79
  resting.push({ node, name });
78
80
  },
79
81
  "Program:exit"() {
@@ -12,6 +12,8 @@
12
12
  * of which still compile and still fork the primitive. Those are what survives
13
13
  * here; the raw-palette names are gone from the sets below with the palette.
14
14
  */
15
+ import { posix } from "node:path";
16
+
15
17
  import {
16
18
  classUtilities,
17
19
  openingElementClassUtilities,
@@ -19,6 +21,127 @@ import {
19
21
  skipsPackageOwnedPrimitive,
20
22
  } from "./jsx-class-utility.js";
21
23
 
24
+ // These exact Portal adapters forward props to package primitives. Card
25
+ // chooses its typed surface default; Table owns only structural geometry.
26
+ // Their other exports are direct reexports.
27
+ const portalCardExports = new Set([
28
+ "Card",
29
+ "CardHeader",
30
+ "CardTitle",
31
+ "CardDescription",
32
+ "CardAction",
33
+ "CardContent",
34
+ ]);
35
+ const portalTableExports = new Set([
36
+ "Table",
37
+ "TableHead",
38
+ "TableCell",
39
+ "TableBody",
40
+ "TableCaption",
41
+ "TableFooter",
42
+ "TableHeader",
43
+ "TableRow",
44
+ ]);
45
+
46
+ function primitiveModule(source, filename) {
47
+ if (source.startsWith("@nadicodeai/ui/components/")) {
48
+ return { card: source === "@nadicodeai/ui/components/card", exports: undefined };
49
+ }
50
+ const normalized = filename.replaceAll("\\", "/");
51
+ const portalStart = normalized.lastIndexOf("/apps/portal/src/");
52
+ if (portalStart === -1) return undefined;
53
+ const portalSource = `${normalized.slice(0, portalStart)}/apps/portal/src`;
54
+ const resolved = source.startsWith(".")
55
+ ? posix.normalize(posix.join(posix.dirname(normalized), source)).replace(/\.[jt]sx?$/, "")
56
+ : undefined;
57
+ if (source === "@/components/kit/card" || resolved === `${portalSource}/components/kit/card`) {
58
+ return { card: true, exports: portalCardExports };
59
+ }
60
+ if (source === "@/components/kit/table" || resolved === `${portalSource}/components/kit/table`) {
61
+ return { card: false, exports: portalTableExports };
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ function primitiveExport(module, name) {
67
+ if (module.exports && !module.exports.has(name)) return undefined;
68
+ return { cardRoot: module.card && name === "Card" };
69
+ }
70
+
71
+ const appearanceStyleProperties = new Set([
72
+ "appearance",
73
+ "WebkitAppearance",
74
+ "MozAppearance",
75
+ "color",
76
+ "background",
77
+ "backgroundColor",
78
+ "backgroundImage",
79
+ "boxShadow",
80
+ "textShadow",
81
+ "opacity",
82
+ "outlineColor",
83
+ "filter",
84
+ "backdropFilter",
85
+ "WebkitBackdropFilter",
86
+ "fill",
87
+ "fillOpacity",
88
+ "stroke",
89
+ "strokeOpacity",
90
+ ...[
91
+ "",
92
+ "Top",
93
+ "Right",
94
+ "Bottom",
95
+ "Left",
96
+ "Block",
97
+ "Inline",
98
+ "BlockStart",
99
+ "BlockEnd",
100
+ "InlineStart",
101
+ "InlineEnd",
102
+ ].map((side) => `border${side}Color`),
103
+ ...[
104
+ "",
105
+ "TopLeft",
106
+ "TopRight",
107
+ "BottomLeft",
108
+ "BottomRight",
109
+ "StartStart",
110
+ "StartEnd",
111
+ "EndStart",
112
+ "EndEnd",
113
+ ].map((corner) => `border${corner}Radius`),
114
+ ]);
115
+
116
+ function staticStyleValue(node) {
117
+ if (node.type === "Literal") return node.value;
118
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
119
+ return node.quasis[0]?.value.cooked;
120
+ }
121
+ return undefined;
122
+ }
123
+
124
+ // Inline object literals only. Unknown values, computed keys, imported style
125
+ // objects and spreads remain review work; layout properties are not paint.
126
+ function inlineAppearanceProperties(attribute) {
127
+ if (attribute.type !== "JSXAttribute" || attribute.name.name !== "style") return [];
128
+ const expression =
129
+ attribute.value?.type === "JSXExpressionContainer" ? attribute.value.expression : undefined;
130
+ if (expression?.type !== "ObjectExpression") return [];
131
+ return expression.properties.filter((property) => {
132
+ if (property.type !== "Property") return false;
133
+ const name =
134
+ property.key.type === "Literal"
135
+ ? property.key.value
136
+ : !property.computed && property.key.type === "Identifier"
137
+ ? property.key.name
138
+ : undefined;
139
+ if (!appearanceStyleProperties.has(name)) return false;
140
+ const value = staticStyleValue(property.value);
141
+ return typeof value === "number" || (typeof value === "string" && value.trim() !== "");
142
+ });
143
+ }
144
+
22
145
  function withoutVariants(utility) {
23
146
  return utility.slice(utility.lastIndexOf(":") + 1);
24
147
  }
@@ -74,7 +197,10 @@ function isAppearanceUtility(utility) {
74
197
 
75
198
  function isProhibitedAppearanceUtility(utility, { cardRoot = false } = {}) {
76
199
  const base = withoutVariants(utility);
77
- return isAppearanceUtility(utility) && (!isBorderRemoval(base) || cardRoot);
200
+ return (
201
+ targetsSharedSlot(utility) ||
202
+ (isAppearanceUtility(utility) && (!isBorderRemoval(base) || cardRoot))
203
+ );
78
204
  }
79
205
 
80
206
  function targetsSharedSlot(utility) {
@@ -92,15 +218,14 @@ export const noShadcnAppearanceOverride = {
92
218
  meta: {
93
219
  type: "problem",
94
220
  docs: {
95
- description:
96
- "Disallow app callers repainting imported shadcn primitives; compose their structural layout only",
221
+ description: "Keep shared component appearance and internal slot layout at their owner",
97
222
  },
98
223
  // Default "exempt": inside @nadicodeai/ui the primitives ARE the appearance,
99
224
  // so every utility here would be a false positive.
100
225
  schema: [packageOwnedPrimitivesSchema],
101
226
  messages: {
102
227
  appearanceOverride:
103
- "Do not repaint an imported @nadicodeai/ui primitive with {{utility}}; use its semantic variant or package primitive.",
228
+ "Do not override shared component appearance or internal slot layout with {{utility}}; use a component prop or fix its shared owner.",
104
229
  },
105
230
  },
106
231
  create(context) {
@@ -108,48 +233,76 @@ export const noShadcnAppearanceOverride = {
108
233
  return {};
109
234
  }
110
235
 
111
- const sharedPrimitiveNames = new Set();
112
- const sharedCardRootNames = new Set();
113
- const isSharedPrimitive = (node) =>
114
- node?.type === "JSXOpeningElement" &&
115
- node.name.type === "JSXIdentifier" &&
116
- sharedPrimitiveNames.has(node.name.name);
117
- const isSharedCardRoot = (node) =>
118
- node?.type === "JSXOpeningElement" &&
119
- node.name.type === "JSXIdentifier" &&
120
- sharedCardRootNames.has(node.name.name);
236
+ const sourceCode = context.sourceCode;
237
+ const sharedBindings = new Map();
238
+
239
+ function importedBinding(identifier) {
240
+ for (let scope = sourceCode.getScope(identifier); scope; scope = scope.upper) {
241
+ const variable = scope.set.get(identifier.name);
242
+ if (variable) return sharedBindings.get(variable);
243
+ }
244
+ return undefined;
245
+ }
246
+
247
+ function sharedPrimitive(node) {
248
+ if (node?.type !== "JSXOpeningElement") return undefined;
249
+ if (node.name.type === "JSXIdentifier") {
250
+ const binding = importedBinding(node.name);
251
+ return binding && binding.name !== "*"
252
+ ? primitiveExport(binding.module, binding.name)
253
+ : undefined;
254
+ }
255
+ if (node.name.type === "JSXMemberExpression" && node.name.object.type === "JSXIdentifier") {
256
+ const binding = importedBinding(node.name.object);
257
+ return binding?.name === "*"
258
+ ? primitiveExport(binding.module, node.name.property.name)
259
+ : undefined;
260
+ }
261
+ return undefined;
262
+ }
263
+
121
264
  return {
122
- ImportDeclaration(node) {
123
- if (
124
- typeof node.source.value !== "string" ||
125
- !node.source.value.startsWith("@nadicodeai/ui/components/")
126
- ) {
127
- return;
128
- }
129
- for (const specifier of node.specifiers) {
130
- sharedPrimitiveNames.add(specifier.local.name);
131
- if (
132
- node.source.value === "@nadicodeai/ui/components/card" &&
133
- specifier.type === "ImportSpecifier" &&
134
- specifier.imported.name === "Card"
135
- ) {
136
- sharedCardRootNames.add(specifier.local.name);
265
+ Program(node) {
266
+ // Imports are hoisted; collect their declared variables before visiting
267
+ // JSX. Binding identity also distinguishes shadowing parameters/locals.
268
+ for (const declaration of node.body) {
269
+ if (declaration.type !== "ImportDeclaration" || declaration.importKind === "type")
270
+ continue;
271
+ const module = primitiveModule(declaration.source.value, context.filename);
272
+ if (!module) continue;
273
+ for (const specifier of declaration.specifiers) {
274
+ if (specifier.importKind === "type") continue;
275
+ const name =
276
+ specifier.type === "ImportNamespaceSpecifier"
277
+ ? "*"
278
+ : specifier.type === "ImportSpecifier"
279
+ ? (specifier.imported.name ?? specifier.imported.value)
280
+ : "default";
281
+ for (const variable of sourceCode.getDeclaredVariables(specifier)) {
282
+ sharedBindings.set(variable, { module, name });
283
+ }
137
284
  }
138
285
  }
139
286
  },
140
287
  JSXOpeningElement(node) {
141
- if (!isSharedPrimitive(node)) return;
142
- for (const utility of openingElementClassUtilities(node, context)) {
143
- if (
144
- utility &&
145
- isProhibitedAppearanceUtility(utility, { cardRoot: isSharedCardRoot(node) })
146
- ) {
288
+ const primitive = sharedPrimitive(node);
289
+ if (!primitive) return;
290
+ for (const utility of openingElementClassUtilities(node, context, true)) {
291
+ if (utility && isProhibitedAppearanceUtility(utility, primitive)) {
147
292
  context.report({ node, messageId: "appearanceOverride", data: { utility } });
148
293
  }
149
294
  }
295
+ for (const property of node.attributes.flatMap(inlineAppearanceProperties)) {
296
+ const name = property.key.name ?? property.key.value;
297
+ context.report({
298
+ node: property,
299
+ messageId: "appearanceOverride",
300
+ data: { utility: `style.${name}` },
301
+ });
302
+ }
150
303
  },
151
304
  JSXAttribute(node) {
152
- if (isSharedPrimitive(node.parent)) return;
305
+ if (sharedPrimitive(node.parent)) return;
153
306
  for (const utility of classUtilities(node, context)) {
154
307
  if (
155
308
  !targetsSharedSlot(utility) ||
@@ -1,5 +1,5 @@
1
1
  /*
2
- * The four composition conventions the shadcn primitives are built to, checked
2
+ * The composition conventions the shared components are built to, checked
3
3
  * where they are composed:
4
4
  *
5
5
  * - an overlay carries its title, so a screen reader announces what opened
@@ -9,7 +9,9 @@
9
9
  * - an `AvatarImage` has an `AvatarFallback` beside it, or the avatar is blank
10
10
  * until the image loads;
11
11
  * - a lucide icon inside a `Button` carries `data-icon`, the hook the button's
12
- * own spacing rules key on.
12
+ * own spacing rules key on;
13
+ * - a settings submit action uses SettingsSection's shared footer, so each
14
+ * caller cannot rebuild its own divider, padding, and action position.
13
15
  *
14
16
  * Standard tool checked first: `jsx-a11y` covers roles and ARIA on
15
17
  * intrinsic elements and says nothing about these composed components;
@@ -18,7 +20,11 @@
18
20
  * of the retired @nadicodeai/ui composition-conventions scanner (#822); stock
19
21
  * upstream shapes carry an inline disable with the reason at the site.
20
22
  */
21
- import { packageOwnedPrimitivesSchema, skipsPackageOwnedPrimitive } from "./jsx-class-utility.js";
23
+ import {
24
+ jsxComponentName,
25
+ packageOwnedPrimitivesSchema,
26
+ skipsPackageOwnedPrimitive,
27
+ } from "./jsx-class-utility.js";
22
28
 
23
29
  const OVERLAY_TITLES = {
24
30
  AlertDialogContent: "AlertDialogTitle",
@@ -75,6 +81,39 @@ function isIconTag(tagName) {
75
81
  return tagName === "Icon" || tagName.endsWith("Icon");
76
82
  }
77
83
 
84
+ function misplacedSettingsSubmit(node, context) {
85
+ if (jsxComponentName(node, context) !== "Button") return false;
86
+ const attribute = node.attributes.find(
87
+ (entry) => entry.type === "JSXAttribute" && entry.name.name === "type",
88
+ );
89
+ const value =
90
+ attribute?.value?.type === "JSXExpressionContainer"
91
+ ? attribute.value.expression
92
+ : attribute?.value;
93
+ const type =
94
+ value?.type === "TemplateLiteral" && value.expressions.length === 0
95
+ ? value.quasis[0]?.value.cooked
96
+ : value?.value;
97
+ if (type !== "submit") return false;
98
+ const ancestors = context.sourceCode.getAncestors(node);
99
+ const section = ancestors.findLast(
100
+ (ancestor) =>
101
+ ancestor.type === "JSXElement" &&
102
+ jsxComponentName(ancestor.openingElement, context) === "SettingsSection",
103
+ );
104
+ if (!section) return false;
105
+ return !ancestors.slice(ancestors.indexOf(section) + 1).some((ancestor) => {
106
+ if (ancestor.type === "JSXAttribute")
107
+ return ancestor.name.name === "footer" && ancestor.parent === section.openingElement;
108
+ return (
109
+ ancestor.type === "JSXElement" &&
110
+ ["DialogContent", "DrawerContent", "AlertDialogContent"].includes(
111
+ jsxComponentName(ancestor.openingElement, context),
112
+ )
113
+ );
114
+ });
115
+ }
116
+
78
117
  export const shadcnComposition = {
79
118
  meta: {
80
119
  type: "problem",
@@ -88,6 +127,8 @@ export const shadcnComposition = {
88
127
  selectGroup: "SelectItem must be rendered inside SelectGroup.",
89
128
  avatarFallback: "AvatarImage requires an AvatarFallback in the same Avatar.",
90
129
  buttonIcon: "An icon inside Button must carry data-icon.",
130
+ settingsFooter:
131
+ "Put a SettingsSection submit action in its footer prop; the shared component owns the divider, spacing, and action position.",
91
132
  },
92
133
  },
93
134
  create(context) {
@@ -101,6 +142,10 @@ export const shadcnComposition = {
101
142
  if (!tag) return;
102
143
  const element = node.parent;
103
144
 
145
+ if (misplacedSettingsSubmit(node, context)) {
146
+ context.report({ node, messageId: "settingsFooter" });
147
+ }
148
+
104
149
  const title = OVERLAY_TITLES[tag];
105
150
  if (title && !hasDescendant(element, title)) {
106
151
  context.report({ node, messageId: "overlayTitle", data: { tag, title } });
@@ -1,9 +1,3 @@
1
- export interface ConfirmActionGate {
2
- busy: boolean;
3
- }
4
- export declare function requestConfirmAction<T>(gate: ConfirmActionGate, value: T, setPending: (value: T | null) => void): void;
5
- export declare function cancelConfirmAction<T>(gate: ConfirmActionGate, setPending: (value: T | null) => void): void;
6
- export declare function runConfirmAction<T>(gate: ConfirmActionGate, pending: T | null, onConfirm: (value: T) => void | Promise<void>, setPending: (value: T | null) => void, setBusy: (busy: boolean) => void): Promise<void>;
7
1
  export declare function useConfirmAction<T>({ onConfirm, }: {
8
2
  onConfirm: (value: T) => void | Promise<void>;
9
3
  }): {
@@ -1 +1 @@
1
- {"version":3,"file":"use-confirm-action.d.ts","sourceRoot":"","sources":["../../src/hooks/use-confirm-action.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC;CACf;AAID,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,GACpC,IAAI,CAKN;AAID,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE,iBAAiB,EACvB,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,GACpC,IAAI,CAKN;AAKD,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,CAAC,GAAG,IAAI,EACjB,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC7C,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,EACrC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAClC,SAAS,GACV,EAAE;IACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;8BAK2C,CAAC;;;;;;EAqB5C"}
1
+ {"version":3,"file":"use-confirm-action.d.ts","sourceRoot":"","sources":["../../src/hooks/use-confirm-action.ts"],"names":[],"mappings":"AAwDA,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAClC,SAAS,GACV,EAAE;IACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;8BAK2C,CAAC;;;;;;EAqB5C"}
@@ -1,25 +1,18 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
- // Request stages a value for confirmation; ignored while a confirm is in
4
- // flight so the pending value cannot change under a running action.
5
- export function requestConfirmAction(gate, value, setPending) {
3
+ function requestConfirmAction(gate, value, setPending) {
6
4
  if (gate.busy) {
7
5
  return;
8
6
  }
9
7
  setPending(value);
10
8
  }
11
- // Cancel clears the pending value, but is a no-op while a confirm is in flight
12
- // so the dialog cannot be dismissed mid-action.
13
- export function cancelConfirmAction(gate, setPending) {
9
+ function cancelConfirmAction(gate, setPending) {
14
10
  if (gate.busy) {
15
11
  return;
16
12
  }
17
13
  setPending(null);
18
14
  }
19
- // Confirm marks busy, awaits `onConfirm`, and clears the pending value on
20
- // success. A thrown error is swallowed so the dialog stays open (the caller
21
- // surfaces the error inside `onConfirm`); busy always resets in `finally`.
22
- export async function runConfirmAction(gate, pending, onConfirm, setPending, setBusy) {
15
+ async function confirmOrKeepOpen(gate, pending, onConfirm, setPending, setBusy) {
23
16
  if (gate.busy || pending === null) {
24
17
  return;
25
18
  }
@@ -30,7 +23,7 @@ export async function runConfirmAction(gate, pending, onConfirm, setPending, set
30
23
  setPending(null);
31
24
  }
32
25
  catch {
33
- // Keep the pending value so the dialog stays open.
26
+ return;
34
27
  }
35
28
  finally {
36
29
  gate.busy = false;
@@ -47,7 +40,7 @@ export function useConfirmAction({ onConfirm, }) {
47
40
  const cancel = React.useCallback(() => {
48
41
  cancelConfirmAction(gateRef.current, setPending);
49
42
  }, []);
50
- const confirm = React.useCallback(() => runConfirmAction(gateRef.current, pending, onConfirm, setPending, setIsBusy), [pending, onConfirm]);
43
+ const confirm = React.useCallback(() => confirmOrKeepOpen(gateRef.current, pending, onConfirm, setPending, setIsBusy), [pending, onConfirm]);
51
44
  return {
52
45
  request,
53
46
  cancel,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components/accordion.js";
2
2
  export * from "./components/action-band.js";
3
+ export * from "./components/action-menu.js";
3
4
  export * from "./components/action-status.js";
4
5
  export * from "./components/activity-orb.js";
5
6
  export * from "./components/alert.js";
@@ -20,6 +21,7 @@ export * from "./components/button.js";
20
21
  export * from "./components/button-group.js";
21
22
  export * from "./components/calendar.js";
22
23
  export * from "./components/card.js";
24
+ export * from "./components/collection-scope.js";
23
25
  export * from "./components/card-marketing.js";
24
26
  export * from "./components/carousel.js";
25
27
  export * from "./components/chart.js";
@@ -105,6 +107,9 @@ export * from "./components/security-grid.js";
105
107
  export * from "./components/select.js";
106
108
  export * from "./components/separator.js";
107
109
  export * from "./components/settings-section.js";
110
+ export * from "./components/settings-facts.js";
111
+ export * from "./components/collection-toolbar.js";
112
+ export * from "./components/collection-filters.js";
108
113
  export * from "./components/sheet.js";
109
114
  export * from "./components/shift-band.js";
110
115
  export * from "./components/sidebar.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,0BAA0B,CAAC;AACzC,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC;AACtD,cAAc,0BAA0B,CAAC;AACzC,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components/accordion.js";
2
2
  export * from "./components/action-band.js";
3
+ export * from "./components/action-menu.js";
3
4
  export * from "./components/action-status.js";
4
5
  export * from "./components/activity-orb.js";
5
6
  export * from "./components/alert.js";
@@ -20,6 +21,7 @@ export * from "./components/button.js";
20
21
  export * from "./components/button-group.js";
21
22
  export * from "./components/calendar.js";
22
23
  export * from "./components/card.js";
24
+ export * from "./components/collection-scope.js";
23
25
  export * from "./components/card-marketing.js";
24
26
  export * from "./components/carousel.js";
25
27
  export * from "./components/chart.js";
@@ -105,6 +107,9 @@ export * from "./components/security-grid.js";
105
107
  export * from "./components/select.js";
106
108
  export * from "./components/separator.js";
107
109
  export * from "./components/settings-section.js";
110
+ export * from "./components/settings-facts.js";
111
+ export * from "./components/collection-toolbar.js";
112
+ export * from "./components/collection-filters.js";
108
113
  export * from "./components/sheet.js";
109
114
  export * from "./components/shift-band.js";
110
115
  export * from "./components/sidebar.js";
@@ -1,44 +1,3 @@
1
- /**
2
- * The copy affordance behind every "copy" button in the package.
3
- *
4
- * The decision half and the timer half are exported as plain functions, with the
5
- * hook a thin binding over them (the shape `use-confirm-action` established), so
6
- * both are testable in the package's node-only default suite instead of needing a
7
- * DOM render harness.
8
- */
9
- export type CopyAttempt =
10
- /** No clipboard to write to — an insecure origin, typically. */
11
- {
12
- kind: "unavailable";
13
- error: Error;
14
- }
15
- /** Already showing "copied" and the caller asked not to repeat. */
16
- | {
17
- kind: "skipped";
18
- } | {
19
- kind: "copied";
20
- } | {
21
- kind: "failed";
22
- error: Error;
23
- };
24
- /** Attempts the write and classifies the result; performs no state or timer work. */
25
- export declare function attemptCopy(input: {
26
- text: string;
27
- isCopied: boolean;
28
- skipWhileCopied: boolean;
29
- writeText: ((text: string) => Promise<void>) | undefined;
30
- }): Promise<CopyAttempt>;
31
- export type ResetTimers = {
32
- clear: (handle: number) => void;
33
- set: (onReset: () => void, timeout: number) => number;
34
- };
35
- /**
36
- * Starts the "copied" reset window, dropping whichever reset was already
37
- * pending. Without the clear, an earlier press's timer keeps its own schedule and
38
- * fires part-way through this press's window, so the feedback disappears early.
39
- * Returns the new handle for the caller to hold.
40
- */
41
- export declare function restartResetTimer(timers: ResetTimers, pendingHandle: number, onReset: () => void, timeout: number): number;
42
1
  export declare function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCopied, }: {
43
2
  text: string;
44
3
  onCopy?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/lib/use-copy-to-clipboard.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW;AACrB,gEAAgE;AAC9D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE;AACvC,mEAAmE;GACjE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAErC,qFAAqF;AACrF,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;CAC1D,GAAG,OAAO,CAAC,WAAW,CAAC,CAavB;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACvD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,EAAE,MAAM,GACd,MAAM,CAGR;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,eAAuB,GACxB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;EAyCA"}
1
+ {"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/lib/use-copy-to-clipboard.ts"],"names":[],"mappings":"AA4BA,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,eAAuB,GACxB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;EAsCA"}
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
- /** Attempts the write and classifies the result; performs no state or timer work. */
3
- export async function attemptCopy(input) {
2
+ async function attemptCopy(input) {
4
3
  if (!input.writeText) {
5
4
  return { kind: "unavailable", error: new Error("Clipboard API not available") };
6
5
  }
@@ -15,18 +14,9 @@ export async function attemptCopy(input) {
15
14
  return { kind: "failed", error: error };
16
15
  }
17
16
  }
18
- /**
19
- * Starts the "copied" reset window, dropping whichever reset was already
20
- * pending. Without the clear, an earlier press's timer keeps its own schedule and
21
- * fires part-way through this press's window, so the feedback disappears early.
22
- * Returns the new handle for the caller to hold.
23
- */
24
- export function restartResetTimer(timers, pendingHandle, onReset, timeout) {
25
- timers.clear(pendingHandle);
26
- return timers.set(onReset, timeout);
27
- }
28
17
  export function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCopied = false, }) {
29
- const [isCopied, setIsCopied] = useState(false);
18
+ const [copiedText, setCopiedText] = useState(null);
19
+ const isCopied = copiedText === text;
30
20
  const timeoutRef = useRef(0);
31
21
  const copyToClipboard = useCallback(async () => {
32
22
  const attempt = await attemptCopy({
@@ -44,9 +34,10 @@ export function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCo
44
34
  if (attempt.kind === "skipped") {
45
35
  return;
46
36
  }
47
- setIsCopied(true);
37
+ setCopiedText(text);
48
38
  onCopy?.();
49
- timeoutRef.current = restartResetTimer({ clear: window.clearTimeout.bind(window), set: window.setTimeout.bind(window) }, timeoutRef.current, () => setIsCopied(false), timeout);
39
+ window.clearTimeout(timeoutRef.current);
40
+ timeoutRef.current = window.setTimeout(() => setCopiedText(null), timeout);
50
41
  }, [isCopied, onCopy, onError, skipWhileCopied, text, timeout]);
51
42
  useEffect(() => () => {
52
43
  window.clearTimeout(timeoutRef.current);