@nextlyhq/ui 0.0.2-alpha.60 → 0.0.2-alpha.62

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.
package/dist/index.cjs CHANGED
@@ -53,6 +53,7 @@ __export(src_exports, {
53
53
  AvatarFallback: () => AvatarFallback,
54
54
  AvatarImage: () => AvatarImage,
55
55
  Badge: () => Badge,
56
+ Bleed: () => Bleed,
56
57
  Button: () => Button,
57
58
  Card: () => Card,
58
59
  CardAction: () => CardAction,
@@ -117,11 +118,12 @@ __export(src_exports, {
117
118
  FieldShell: () => FieldShell,
118
119
  FormActions: () => FormActions,
119
120
  FormLabelWithTooltip: () => FormLabelWithTooltip,
120
- FormLayout: () => FormLayout,
121
121
  FormSection: () => FormSection,
122
122
  Grid: () => Grid,
123
123
  Input: () => Input,
124
124
  Label: () => Label,
125
+ PageHeader: () => PageHeader,
126
+ PageShell: () => PageShell,
125
127
  Popover: () => Popover,
126
128
  PopoverAnchor: () => PopoverAnchor,
127
129
  PopoverContent: () => PopoverContent,
@@ -133,6 +135,7 @@ __export(src_exports, {
133
135
  ResizableHandle: () => ResizableHandle,
134
136
  ResizablePanel: () => ResizablePanel,
135
137
  ResizablePanelGroup: () => ResizablePanelGroup,
138
+ SHELL_MEASURE: () => SHELL_MEASURE,
136
139
  Select: () => Select,
137
140
  SelectContent: () => SelectContent,
138
141
  SelectGroup: () => SelectGroup,
@@ -191,8 +194,10 @@ __export(src_exports, {
191
194
  badgeVariants: () => badgeVariants,
192
195
  buttonVariants: () => buttonVariants,
193
196
  cardVariants: () => cardVariants,
197
+ chordMatches: () => chordMatches,
194
198
  commandDefaultFilter: () => commandDefaultFilter,
195
199
  createShortcutManager: () => createShortcutManager,
200
+ detectApplePlatform: () => detectApplePlatform,
196
201
  dialogContentVariants: () => dialogContentVariants,
197
202
  inputVariants: () => inputVariants,
198
203
  parseKeys: () => parseKeys,
@@ -202,6 +207,7 @@ __export(src_exports, {
202
207
  spinnerVariants: () => spinnerVariants,
203
208
  toast: () => import_sonner.toast,
204
209
  useActiveShortcuts: () => useActiveShortcuts,
210
+ useCommandHighlight: () => useCommandHighlight,
205
211
  usePortalContainer: () => usePortalContainer,
206
212
  useShortcutManager: () => useShortcutManager,
207
213
  useShortcuts: () => useShortcuts
@@ -1193,15 +1199,23 @@ var tabsListVariants = (0, import_class_variance_authority8.cva)(
1193
1199
  defaultVariants: { variant: "default" }
1194
1200
  }
1195
1201
  );
1196
- var TabsList = (0, import_react13.forwardRef)(({ className, variant, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1197
- import_react_tabs.List,
1198
- {
1199
- ref,
1200
- "data-slot": "tabs-list",
1201
- className: cn(tabsListVariants({ variant }), className),
1202
- ...props
1203
- }
1204
- ));
1202
+ var TabsList = (0, import_react13.forwardRef)(({ className, variant, scrollable = false, ...props }, ref) => {
1203
+ const list = /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1204
+ import_react_tabs.List,
1205
+ {
1206
+ ref,
1207
+ "data-slot": "tabs-list",
1208
+ className: cn(
1209
+ tabsListVariants({ variant }),
1210
+ scrollable && "w-max min-w-full",
1211
+ className
1212
+ ),
1213
+ ...props
1214
+ }
1215
+ );
1216
+ if (!scrollable) return list;
1217
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { "data-slot": "tabs-list-scroller", className: "w-full overflow-x-auto", children: list });
1218
+ });
1205
1219
  TabsList.displayName = import_react_tabs.List.displayName;
1206
1220
  var TABS_TRIGGER_SIZES = {
1207
1221
  default: "text-sm",
@@ -2176,6 +2190,7 @@ var CommandShortcut = (0, import_react17.forwardRef)(
2176
2190
  );
2177
2191
  CommandShortcut.displayName = "CommandShortcut";
2178
2192
  var commandDefaultFilter = import_cmdk.defaultFilter;
2193
+ var useCommandHighlight = () => (0, import_cmdk.useCommandState)((state) => state.value);
2179
2194
 
2180
2195
  // src/components/spinner.tsx
2181
2196
  var import_class_variance_authority12 = require("class-variance-authority");
@@ -2444,7 +2459,7 @@ TableSkeleton.displayName = "TableSkeleton";
2444
2459
 
2445
2460
  // src/components/color-picker.tsx
2446
2461
  var import_lucide_react12 = require("lucide-react");
2447
- var React9 = __toESM(require("react"), 1);
2462
+ var React10 = __toESM(require("react"), 1);
2448
2463
 
2449
2464
  // src/lib/color/convert.ts
2450
2465
  var clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
@@ -2549,6 +2564,10 @@ function hueSliderValue(hue, max) {
2549
2564
  return step > max ? max : step;
2550
2565
  }
2551
2566
 
2567
+ // src/lib/isomorphic-layout-effect.ts
2568
+ var React9 = __toESM(require("react"), 1);
2569
+ var useIsomorphicLayoutEffect = typeof document === "undefined" ? React9.useEffect : React9.useLayoutEffect;
2570
+
2552
2571
  // src/components/color-picker.tsx
2553
2572
  var import_jsx_runtime34 = require("react/jsx-runtime");
2554
2573
  function toHsva(hex) {
@@ -2570,6 +2589,9 @@ var CHECKERBOARD = "repeating-conic-gradient(#c8c8c8 0% 25%, #ffffff 0% 50%)";
2570
2589
  function eyeDropperSupported() {
2571
2590
  return typeof window !== "undefined" && "EyeDropper" in window;
2572
2591
  }
2592
+ function isNode(value) {
2593
+ return value !== null && typeof value.nodeType === "number";
2594
+ }
2573
2595
  function ColorPicker({
2574
2596
  color,
2575
2597
  onColorChange,
@@ -2579,20 +2601,100 @@ function ColorPicker({
2579
2601
  showAlpha = false,
2580
2602
  className
2581
2603
  }) {
2582
- const fieldId = React9.useId();
2583
- const surfaceRef = React9.useRef(null);
2584
- const [hsva, setHsva] = React9.useState(() => toHsva(color));
2585
- const [draftHex, setDraftHex] = React9.useState(null);
2604
+ const fieldId = React10.useId();
2605
+ const rootRef = React10.useRef(null);
2606
+ const insidePress = React10.useRef(false);
2607
+ const owedFinish = React10.useRef(false);
2608
+ const pendingTap = React10.useRef(null);
2609
+ const latestCommit = React10.useRef(() => void 0);
2610
+ const surfaceRef = React10.useRef(null);
2611
+ const [hsva, setHsva] = React10.useState(() => toHsva(color));
2612
+ const [draftHex, setDraftHex] = React10.useState(null);
2613
+ const draftRef = React10.useRef(null);
2586
2614
  const rendered = toHexString(hsva, showAlpha);
2587
- React9.useEffect(() => {
2615
+ useIsomorphicLayoutEffect(() => {
2588
2616
  const incoming = parseHex(color);
2589
2617
  if (incoming && toHex(incoming, showAlpha ? incoming.alpha : 1) !== rendered) {
2618
+ dropDraft();
2590
2619
  setHsva((prev) => hsvaFrom(incoming, incoming.alpha, prev.h));
2591
2620
  }
2592
2621
  }, [color, rendered, showAlpha]);
2622
+ const dropDraft = () => {
2623
+ owedFinish.current = false;
2624
+ draftRef.current = null;
2625
+ setDraftHex(null);
2626
+ };
2627
+ const commitDraft = () => {
2628
+ owedFinish.current = false;
2629
+ const text = draftRef.current;
2630
+ draftRef.current = null;
2631
+ if (text === null) return;
2632
+ setDraftHex(null);
2633
+ const parsed = parseHex(text);
2634
+ if (!parsed) return;
2635
+ setHsva((prev) => hsvaFrom(parsed, parsed.alpha, prev.h));
2636
+ onColorChange(toHex(parsed, showAlpha ? parsed.alpha : 1));
2637
+ };
2638
+ useIsomorphicLayoutEffect(() => {
2639
+ const root = rootRef.current;
2640
+ if (root === null) return;
2641
+ latestCommit.current = commitDraft;
2642
+ const onPointerDown = (event) => {
2643
+ pendingTap.current?.();
2644
+ pendingTap.current = null;
2645
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
2646
+ const inside = path.includes(root) || isNode(event.target) && root.contains(event.target);
2647
+ insidePress.current = true;
2648
+ if (inside) return;
2649
+ const pointerType = event.pointerType;
2650
+ if (pointerType === "touch") {
2651
+ const onClick = () => {
2652
+ owner.removeEventListener("click", onClick, true);
2653
+ pendingTap.current = null;
2654
+ latestCommit.current();
2655
+ };
2656
+ owner.addEventListener("click", onClick, true);
2657
+ pendingTap.current = () => {
2658
+ owner.removeEventListener("click", onClick, true);
2659
+ };
2660
+ return;
2661
+ }
2662
+ commitDraft();
2663
+ };
2664
+ const owner = root.ownerDocument;
2665
+ const onPointerCancel = () => {
2666
+ insidePress.current = false;
2667
+ pendingTap.current?.();
2668
+ pendingTap.current = null;
2669
+ };
2670
+ const onPointerUp = () => {
2671
+ insidePress.current = false;
2672
+ };
2673
+ const onClickResolve = () => {
2674
+ if (!owedFinish.current) return;
2675
+ latestCommit.current();
2676
+ };
2677
+ owner.addEventListener("pointerdown", onPointerDown, true);
2678
+ owner.addEventListener("pointercancel", onPointerCancel, true);
2679
+ owner.addEventListener("pointerup", onPointerUp, true);
2680
+ owner.addEventListener("click", onClickResolve, false);
2681
+ return () => {
2682
+ owner.removeEventListener("pointerdown", onPointerDown, true);
2683
+ owner.removeEventListener("pointercancel", onPointerCancel, true);
2684
+ owner.removeEventListener("pointerup", onPointerUp, true);
2685
+ owner.removeEventListener("click", onClickResolve, false);
2686
+ };
2687
+ });
2688
+ useIsomorphicLayoutEffect(
2689
+ () => () => {
2690
+ pendingTap.current?.();
2691
+ pendingTap.current = null;
2692
+ },
2693
+ []
2694
+ );
2593
2695
  const commit = (next) => {
2594
2696
  setHsva(next);
2595
- setDraftHex(null);
2697
+ dropDraft();
2596
2698
  onColorChange(toHexString(next, showAlpha));
2597
2699
  };
2598
2700
  const trackPointer = (event) => {
@@ -2622,8 +2724,8 @@ function ColorPicker({
2622
2724
  event.preventDefault();
2623
2725
  nudge(move[0], move[1]);
2624
2726
  };
2625
- const [canPickFromScreen, setCanPickFromScreen] = React9.useState(false);
2626
- React9.useEffect(() => {
2727
+ const [canPickFromScreen, setCanPickFromScreen] = React10.useState(false);
2728
+ React10.useEffect(() => {
2627
2729
  setCanPickFromScreen(eyeDropperSupported());
2628
2730
  }, []);
2629
2731
  const handle = surfacePointFor(hsva.s, hsva.v);
@@ -2631,163 +2733,187 @@ function ColorPicker({
2631
2733
  const pickFromScreen = async () => {
2632
2734
  const ctor = window.EyeDropper;
2633
2735
  if (!ctor) return;
2736
+ const owed = owedFinish.current;
2737
+ owedFinish.current = false;
2634
2738
  let sampled;
2635
2739
  try {
2636
2740
  sampled = (await new ctor().open()).sRGBHex;
2637
2741
  } catch {
2742
+ if (owed) commitDraft();
2638
2743
  return;
2639
2744
  }
2640
2745
  const parsed = parseHex(sampled);
2641
2746
  if (parsed) commit(hsvaFrom(parsed, hsva.a, hsva.h));
2642
2747
  };
2643
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: cn("w-64 space-y-3", className), children: [
2644
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2645
- "div",
2646
- {
2647
- ref: surfaceRef,
2648
- role: "application",
2649
- tabIndex: 0,
2650
- "aria-label": `Saturation and brightness: ${Math.round(hsva.s * 100)}% saturation, ${Math.round(hsva.v * 100)}% brightness. Arrow keys adjust.`,
2651
- className: "ring-offset-background focus-visible:ring-ring relative h-40 w-full cursor-crosshair touch-none rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
2652
- style: {
2653
- backgroundColor: hueOnly,
2654
- // Value on TOP of saturation. CSS paints the first layer nearest the
2655
- // viewer, so the reverse order lets the opaque white end of the
2656
- // saturation ramp cover the black end of the value ramp: the
2657
- // bottom-left corner displays white while selecting black.
2658
- backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)"
2659
- },
2660
- onKeyDown: handleSurfaceKey,
2661
- onPointerDown: (event) => {
2662
- event.currentTarget.setPointerCapture(event.pointerId);
2663
- trackPointer(event);
2664
- },
2665
- onPointerMove: (event) => {
2666
- if (event.buttons === 1) trackPointer(event);
2667
- },
2668
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2669
- "span",
2670
- {
2671
- "aria-hidden": "true",
2672
- className: "pointer-events-none absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-sm ring-1 ring-black/60",
2673
- style: { left: `${handle.x * 100}%`, top: `${handle.y * 100}%` }
2674
- }
2675
- )
2676
- }
2677
- ),
2678
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "sr-only", htmlFor: `${fieldId}-hue`, children: "Hue" }),
2679
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2680
- "input",
2681
- {
2682
- id: `${fieldId}-hue`,
2683
- type: "range",
2684
- min: 0,
2685
- max: HUE_MAX,
2686
- step: 1,
2687
- value: hueSliderValue(hsva.h, HUE_MAX),
2688
- onChange: (event) => commit({ ...hsva, h: hueAt(+event.target.value / (HUE_MAX + 1)) }),
2689
- className: SLIDER,
2690
- style: {
2691
- backgroundImage: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
2748
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2749
+ "div",
2750
+ {
2751
+ ref: rootRef,
2752
+ className: cn("w-64 space-y-3", className),
2753
+ onBlur: (event) => {
2754
+ const next = event.relatedTarget;
2755
+ const root = rootRef.current;
2756
+ if (root !== null && isNode(next) && root.contains(next)) return;
2757
+ if (next === null && insidePress.current) {
2758
+ owedFinish.current = true;
2759
+ return;
2692
2760
  }
2693
- }
2694
- ),
2695
- showAlpha && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2696
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "sr-only", htmlFor: `${fieldId}-alpha`, children: "Opacity" }),
2697
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2698
- "input",
2699
- {
2700
- id: `${fieldId}-alpha`,
2701
- type: "range",
2702
- min: 0,
2703
- max: 100,
2704
- step: 1,
2705
- value: Math.round(hsva.a * 100),
2706
- onChange: (event) => commit({ ...hsva, a: +event.target.value / 100 }),
2707
- className: SLIDER,
2708
- style: {
2709
- // The ramp runs to the colour being edited, over a chequerboard,
2710
- // so the track shows what the slider actually controls. Without
2711
- // any background this rendered as a blank 12px strip whose only
2712
- // label was screen-reader-only.
2713
- backgroundImage: `linear-gradient(to right, transparent, ${toHexString({ ...hsva, a: 1 }, false)}), ${CHECKERBOARD}`,
2714
- backgroundSize: "100% 100%, 8px 8px"
2761
+ commitDraft();
2762
+ },
2763
+ children: [
2764
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2765
+ "div",
2766
+ {
2767
+ ref: surfaceRef,
2768
+ role: "application",
2769
+ tabIndex: 0,
2770
+ "aria-label": `Saturation and brightness: ${Math.round(hsva.s * 100)}% saturation, ${Math.round(hsva.v * 100)}% brightness. Arrow keys adjust.`,
2771
+ className: "ring-offset-background focus-visible:ring-ring relative h-40 w-full cursor-crosshair touch-none rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
2772
+ style: {
2773
+ backgroundColor: hueOnly,
2774
+ // Value on TOP of saturation. CSS paints the first layer nearest the
2775
+ // viewer, so the reverse order lets the opaque white end of the
2776
+ // saturation ramp cover the black end of the value ramp: the
2777
+ // bottom-left corner displays white while selecting black.
2778
+ backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)"
2779
+ },
2780
+ onKeyDown: handleSurfaceKey,
2781
+ onPointerDown: (event) => {
2782
+ event.currentTarget.setPointerCapture(event.pointerId);
2783
+ trackPointer(event);
2784
+ },
2785
+ onPointerMove: (event) => {
2786
+ if (event.buttons === 1) trackPointer(event);
2787
+ },
2788
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2789
+ "span",
2790
+ {
2791
+ "aria-hidden": "true",
2792
+ className: "pointer-events-none absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-sm ring-1 ring-black/60",
2793
+ style: { left: `${handle.x * 100}%`, top: `${handle.y * 100}%` }
2794
+ }
2795
+ )
2715
2796
  }
2716
- }
2717
- )
2718
- ] }),
2719
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2720
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "sr-only", htmlFor: `${fieldId}-hex`, children: "Hex colour" }),
2721
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2722
- Input,
2723
- {
2724
- id: `${fieldId}-hex`,
2725
- className: "font-mono",
2726
- value: draftHex ?? rendered,
2727
- onChange: (event) => {
2728
- const text = event.target.value;
2729
- setDraftHex(text);
2730
- const parsed = parseHex(text);
2731
- if (parsed) {
2732
- setHsva(hsvaFrom(parsed, parsed.alpha, hsva.h));
2733
- onColorChange(toHex(parsed, showAlpha ? parsed.alpha : 1));
2797
+ ),
2798
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "sr-only", htmlFor: `${fieldId}-hue`, children: "Hue" }),
2799
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2800
+ "input",
2801
+ {
2802
+ id: `${fieldId}-hue`,
2803
+ type: "range",
2804
+ min: 0,
2805
+ max: HUE_MAX,
2806
+ step: 1,
2807
+ value: hueSliderValue(hsva.h, HUE_MAX),
2808
+ onChange: (event) => commit({ ...hsva, h: hueAt(+event.target.value / (HUE_MAX + 1)) }),
2809
+ className: SLIDER,
2810
+ style: {
2811
+ backgroundImage: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
2734
2812
  }
2735
- },
2736
- onBlur: () => setDraftHex(null)
2737
- }
2738
- ),
2739
- canPickFromScreen && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2740
- Button,
2741
- {
2742
- type: "button",
2743
- variant: "outline",
2744
- size: "icon",
2745
- "aria-label": "Pick a colour from the screen",
2746
- onClick: () => void pickFromScreen(),
2747
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Pipette, { className: "size-4" })
2748
- }
2749
- )
2750
- ] }),
2751
- swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { children: [
2752
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-muted-foreground mb-1 text-xs", children: "Presets" }),
2753
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex flex-wrap gap-1", children: swatches.map((swatch) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2754
- "button",
2755
- {
2756
- type: "button",
2757
- title: swatch.label,
2758
- "aria-label": swatch.label,
2759
- className: "size-6 rounded border shadow-sm",
2760
- style: { backgroundColor: swatch.color },
2761
- onClick: () => onSwatchSelect?.(swatch)
2762
- },
2763
- swatch.id
2764
- )) })
2765
- ] }),
2766
- recentColors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { children: [
2767
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-muted-foreground mb-1 text-xs", children: "Recent" }),
2768
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex flex-wrap gap-1", children: recentColors.map((recent) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2769
- "button",
2770
- {
2771
- type: "button",
2772
- title: recent,
2773
- "aria-label": recent,
2774
- className: "size-6 rounded border shadow-sm",
2775
- style: { backgroundColor: recent },
2776
- onClick: () => {
2777
- const parsed = parseHex(recent);
2778
- if (parsed) commit(hsvaFrom(parsed, parsed.alpha, hsva.h));
2779
2813
  }
2780
- },
2781
- recent
2782
- )) })
2783
- ] })
2784
- ] });
2814
+ ),
2815
+ showAlpha && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2816
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "sr-only", htmlFor: `${fieldId}-alpha`, children: "Opacity" }),
2817
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2818
+ "input",
2819
+ {
2820
+ id: `${fieldId}-alpha`,
2821
+ type: "range",
2822
+ min: 0,
2823
+ max: 100,
2824
+ step: 1,
2825
+ value: Math.round(hsva.a * 100),
2826
+ onChange: (event) => commit({ ...hsva, a: +event.target.value / 100 }),
2827
+ className: SLIDER,
2828
+ style: {
2829
+ // The ramp runs to the colour being edited, over a chequerboard,
2830
+ // so the track shows what the slider actually controls. Without
2831
+ // any background this rendered as a blank 12px strip whose only
2832
+ // label was screen-reader-only.
2833
+ backgroundImage: `linear-gradient(to right, transparent, ${toHexString({ ...hsva, a: 1 }, false)}), ${CHECKERBOARD}`,
2834
+ backgroundSize: "100% 100%, 8px 8px"
2835
+ }
2836
+ }
2837
+ )
2838
+ ] }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
2840
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "sr-only", htmlFor: `${fieldId}-hex`, children: "Hex colour" }),
2841
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2842
+ Input,
2843
+ {
2844
+ id: `${fieldId}-hex`,
2845
+ className: "font-mono",
2846
+ value: draftHex ?? rendered,
2847
+ onChange: (event) => {
2848
+ draftRef.current = event.target.value;
2849
+ setDraftHex(event.target.value);
2850
+ },
2851
+ onKeyDown: (event) => {
2852
+ if (event.key !== "Enter") return;
2853
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
2854
+ event.preventDefault();
2855
+ commitDraft();
2856
+ }
2857
+ }
2858
+ ),
2859
+ canPickFromScreen && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2860
+ Button,
2861
+ {
2862
+ type: "button",
2863
+ variant: "outline",
2864
+ size: "icon",
2865
+ "aria-label": "Pick a colour from the screen",
2866
+ onClick: () => void pickFromScreen(),
2867
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Pipette, { className: "size-4" })
2868
+ }
2869
+ )
2870
+ ] }),
2871
+ swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { children: [
2872
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-muted-foreground mb-1 text-xs", children: "Presets" }),
2873
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex flex-wrap gap-1", children: swatches.map((swatch) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2874
+ "button",
2875
+ {
2876
+ type: "button",
2877
+ title: swatch.label,
2878
+ "aria-label": swatch.label,
2879
+ className: "size-6 rounded border shadow-sm",
2880
+ style: { backgroundColor: swatch.color },
2881
+ onClick: () => {
2882
+ if (onSwatchSelect === void 0) return;
2883
+ dropDraft();
2884
+ onSwatchSelect(swatch);
2885
+ }
2886
+ },
2887
+ swatch.id
2888
+ )) })
2889
+ ] }),
2890
+ recentColors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { children: [
2891
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-muted-foreground mb-1 text-xs", children: "Recent" }),
2892
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex flex-wrap gap-1", children: recentColors.map((recent) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2893
+ "button",
2894
+ {
2895
+ type: "button",
2896
+ title: recent,
2897
+ "aria-label": recent,
2898
+ className: "size-6 rounded border shadow-sm",
2899
+ style: { backgroundColor: recent },
2900
+ onClick: () => {
2901
+ const parsed = parseHex(recent);
2902
+ if (parsed) commit(hsvaFrom(parsed, parsed.alpha, hsva.h));
2903
+ }
2904
+ },
2905
+ recent
2906
+ )) })
2907
+ ] })
2908
+ ]
2909
+ }
2910
+ );
2785
2911
  }
2786
2912
 
2787
2913
  // src/components/context-menu.tsx
2788
2914
  var ContextMenuPrimitive = __toESM(require("@radix-ui/react-context-menu"), 1);
2789
2915
  var import_lucide_react13 = require("lucide-react");
2790
- var React10 = __toESM(require("react"), 1);
2916
+ var React11 = __toESM(require("react"), 1);
2791
2917
  var import_jsx_runtime35 = require("react/jsx-runtime");
2792
2918
  var menuItemBase2 = "cursor-pointer transition-colors data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
2793
2919
  var menuSurface = "z-50 max-h-[var(--radix-context-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 origin-[--radix-context-menu-content-transform-origin]";
@@ -2796,7 +2922,7 @@ var ContextMenuTrigger = ContextMenuPrimitive.Trigger;
2796
2922
  var ContextMenuGroup = ContextMenuPrimitive.Group;
2797
2923
  var ContextMenuSub = ContextMenuPrimitive.Sub;
2798
2924
  var ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
2799
- var ContextMenuSubTrigger = React10.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2925
+ var ContextMenuSubTrigger = React11.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2800
2926
  ContextMenuPrimitive.SubTrigger,
2801
2927
  {
2802
2928
  ref,
@@ -2814,7 +2940,7 @@ var ContextMenuSubTrigger = React10.forwardRef(({ className, inset, children, ..
2814
2940
  }
2815
2941
  ));
2816
2942
  ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
2817
- var ContextMenuSubContent = React10.forwardRef(({ className, ...props }, ref) => {
2943
+ var ContextMenuSubContent = React11.forwardRef(({ className, ...props }, ref) => {
2818
2944
  const portalContainer = usePortalContainer();
2819
2945
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2820
2946
  ContextMenuPrimitive.SubContent,
@@ -2826,7 +2952,7 @@ var ContextMenuSubContent = React10.forwardRef(({ className, ...props }, ref) =>
2826
2952
  ) });
2827
2953
  });
2828
2954
  ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
2829
- var ContextMenuContent = React10.forwardRef(({ className, ...props }, ref) => {
2955
+ var ContextMenuContent = React11.forwardRef(({ className, ...props }, ref) => {
2830
2956
  const portalContainer = usePortalContainer();
2831
2957
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2832
2958
  ContextMenuPrimitive.Content,
@@ -2838,7 +2964,7 @@ var ContextMenuContent = React10.forwardRef(({ className, ...props }, ref) => {
2838
2964
  ) });
2839
2965
  });
2840
2966
  ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
2841
- var ContextMenuItem = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2967
+ var ContextMenuItem = React11.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2842
2968
  ContextMenuPrimitive.Item,
2843
2969
  {
2844
2970
  ref,
@@ -2852,7 +2978,7 @@ var ContextMenuItem = React10.forwardRef(({ className, inset, ...props }, ref) =
2852
2978
  }
2853
2979
  ));
2854
2980
  ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
2855
- var ContextMenuCheckboxItem = React10.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2981
+ var ContextMenuCheckboxItem = React11.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2856
2982
  ContextMenuPrimitive.CheckboxItem,
2857
2983
  {
2858
2984
  ref,
@@ -2870,7 +2996,7 @@ var ContextMenuCheckboxItem = React10.forwardRef(({ className, children, checked
2870
2996
  }
2871
2997
  ));
2872
2998
  ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
2873
- var ContextMenuRadioItem = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2999
+ var ContextMenuRadioItem = React11.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2874
3000
  ContextMenuPrimitive.RadioItem,
2875
3001
  {
2876
3002
  ref,
@@ -2887,7 +3013,7 @@ var ContextMenuRadioItem = React10.forwardRef(({ className, children, ...props }
2887
3013
  }
2888
3014
  ));
2889
3015
  ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
2890
- var ContextMenuLabel = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3016
+ var ContextMenuLabel = React11.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2891
3017
  ContextMenuPrimitive.Label,
2892
3018
  {
2893
3019
  ref,
@@ -2900,7 +3026,7 @@ var ContextMenuLabel = React10.forwardRef(({ className, inset, ...props }, ref)
2900
3026
  }
2901
3027
  ));
2902
3028
  ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
2903
- var ContextMenuSeparator = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3029
+ var ContextMenuSeparator = React11.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2904
3030
  ContextMenuPrimitive.Separator,
2905
3031
  {
2906
3032
  ref,
@@ -2973,7 +3099,7 @@ var ResizableHandle = ({
2973
3099
  // src/components/tree-view.tsx
2974
3100
  var import_react_virtual = require("@tanstack/react-virtual");
2975
3101
  var import_lucide_react15 = require("lucide-react");
2976
- var React11 = __toESM(require("react"), 1);
3102
+ var React12 = __toESM(require("react"), 1);
2977
3103
  var import_jsx_runtime37 = require("react/jsx-runtime");
2978
3104
  var ROW_HEIGHT = 28;
2979
3105
  var INDENT_PER_LEVEL = 12;
@@ -3015,19 +3141,28 @@ function flatten(nodes, expanded) {
3015
3141
  return rows;
3016
3142
  }
3017
3143
  function useControllable(controlled, fallback) {
3018
- const [uncontrolled, setUncontrolled] = React11.useState(fallback);
3144
+ const [uncontrolled, setUncontrolled] = React12.useState(fallback);
3019
3145
  return [
3020
3146
  controlled === void 0 ? uncontrolled : controlled,
3021
3147
  setUncontrolled
3022
3148
  ];
3023
3149
  }
3024
- var TreeView = React11.forwardRef(
3150
+ function treeSelectionMode(modifiers) {
3151
+ if (modifiers.shiftKey) return "extend";
3152
+ if (modifiers.metaKey || modifiers.ctrlKey) return "toggle";
3153
+ return "replace";
3154
+ }
3155
+ function isRowSelected(id, primary, selectedIds) {
3156
+ return selectedIds === void 0 ? primary === id : selectedIds.includes(id);
3157
+ }
3158
+ var TreeView = React12.forwardRef(
3025
3159
  ({
3026
3160
  nodes,
3027
3161
  expandedIds,
3028
3162
  defaultExpandedIds,
3029
3163
  onExpandedChange,
3030
3164
  selectedId,
3165
+ selectedIds,
3031
3166
  defaultSelectedId,
3032
3167
  onSelectedChange,
3033
3168
  className,
@@ -3036,8 +3171,8 @@ var TreeView = React11.forwardRef(
3036
3171
  "aria-describedby": ariaDescribedBy,
3037
3172
  ...props
3038
3173
  }, forwardedRef) => {
3039
- const scrollRef = React11.useRef(null);
3040
- const attachScroll = React11.useCallback(
3174
+ const scrollRef = React12.useRef(null);
3175
+ const attachScroll = React12.useCallback(
3041
3176
  (node) => {
3042
3177
  scrollRef.current = node;
3043
3178
  if (typeof forwardedRef === "function") forwardedRef(node);
@@ -3051,7 +3186,7 @@ var TreeView = React11.forwardRef(
3051
3186
  expandedIds === void 0 ? void 0 : [...expandedIds],
3052
3187
  [...defaultExpandedIds ?? []]
3053
3188
  );
3054
- const expanded = React11.useMemo(
3189
+ const expanded = React12.useMemo(
3055
3190
  () => new Set(expandedIds ?? expandedState),
3056
3191
  [expandedIds, expandedState]
3057
3192
  );
@@ -3059,11 +3194,11 @@ var TreeView = React11.forwardRef(
3059
3194
  selectedId === void 0 ? void 0 : selectedId,
3060
3195
  defaultSelectedId ?? null
3061
3196
  );
3062
- const rows = React11.useMemo(
3197
+ const rows = React12.useMemo(
3063
3198
  () => flatten(nodes, expanded),
3064
3199
  [nodes, expanded]
3065
3200
  );
3066
- const [activeId, setActiveId] = React11.useState(null);
3201
+ const [activeId, setActiveId] = React12.useState(null);
3067
3202
  const activeIndex = Math.max(
3068
3203
  0,
3069
3204
  rows.findIndex((row) => row.node.id === (activeId ?? selected))
@@ -3085,9 +3220,9 @@ var TreeView = React11.forwardRef(
3085
3220
  else next.delete(id);
3086
3221
  commitExpanded(next);
3087
3222
  };
3088
- const choose = (id) => {
3223
+ const choose = (id, mode = "replace") => {
3089
3224
  if (selectedId === void 0) setSelected(id);
3090
- onSelectedChange?.(id);
3225
+ onSelectedChange?.(id, mode);
3091
3226
  };
3092
3227
  const focusRow = (index) => {
3093
3228
  const row = rows[index];
@@ -3107,11 +3242,12 @@ var TreeView = React11.forwardRef(
3107
3242
  }
3108
3243
  return from;
3109
3244
  };
3110
- const typeahead = React11.useRef({ query: "", at: 0 });
3245
+ const typeahead = React12.useRef({ query: "", at: 0 });
3111
3246
  const onKeyDown = (event) => {
3112
3247
  const index = activeIndex;
3113
3248
  const row = rows[index];
3114
3249
  if (row === void 0) return;
3250
+ if (event.altKey) return;
3115
3251
  switch (event.key) {
3116
3252
  case "ArrowDown":
3117
3253
  event.preventDefault();
@@ -3166,7 +3302,9 @@ var TreeView = React11.forwardRef(
3166
3302
  case "Enter":
3167
3303
  case " ":
3168
3304
  event.preventDefault();
3169
- if (row.node.disabled !== true) choose(row.node.id);
3305
+ if (row.node.disabled !== true) {
3306
+ choose(row.node.id, treeSelectionMode(event));
3307
+ }
3170
3308
  return;
3171
3309
  case "*": {
3172
3310
  event.preventDefault();
@@ -3214,6 +3352,7 @@ var TreeView = React11.forwardRef(
3214
3352
  "div",
3215
3353
  {
3216
3354
  role: "tree",
3355
+ "aria-multiselectable": selectedIds === void 0 ? void 0 : true,
3217
3356
  "aria-label": ariaLabel,
3218
3357
  "aria-labelledby": ariaLabelledBy,
3219
3358
  "aria-describedby": ariaDescribedBy,
@@ -3222,7 +3361,12 @@ var TreeView = React11.forwardRef(
3222
3361
  children: virtualItems.map((item) => {
3223
3362
  const row = rows[item.index];
3224
3363
  if (row === void 0) return null;
3225
- const isSelected = selected === row.node.id;
3364
+ const isSelected = isRowSelected(
3365
+ row.node.id,
3366
+ selected,
3367
+ selectedIds
3368
+ );
3369
+ const isPrimary = selected === row.node.id;
3226
3370
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
3227
3371
  "div",
3228
3372
  {
@@ -3236,16 +3380,21 @@ var TreeView = React11.forwardRef(
3236
3380
  "aria-disabled": row.node.disabled === true ? true : void 0,
3237
3381
  tabIndex: item.index === tabStopIndex ? 0 : -1,
3238
3382
  onFocus: () => setActiveId(row.node.id),
3239
- onClick: () => {
3383
+ onClick: (event) => {
3240
3384
  if (row.node.disabled === true) return;
3241
3385
  setActiveId(row.node.id);
3242
- choose(row.node.id);
3386
+ choose(row.node.id, treeSelectionMode(event));
3243
3387
  },
3244
3388
  className: cn(
3245
3389
  "absolute left-0 flex w-full select-none items-center gap-1 rounded-sm pr-2 text-sm outline-none",
3246
3390
  "focus-visible:ring-1 focus-visible:ring-ring",
3247
3391
  row.node.disabled === true ? "pointer-events-none opacity-50" : "cursor-pointer",
3248
- isSelected ? "bg-muted text-foreground" : "hover:bg-muted/50"
3392
+ isSelected ? "bg-muted text-foreground" : "hover:bg-muted/50",
3393
+ // WEIGHT, not a second colour. Every row here is selected and
3394
+ // an action applies to all of them; what a reader needs is
3395
+ // which one the detail panel describes, and a second colour
3396
+ // would have to mean a second kind of selected.
3397
+ isSelected && isPrimary && "font-medium"
3249
3398
  ),
3250
3399
  style: {
3251
3400
  height: item.size,
@@ -3291,7 +3440,7 @@ TreeView.displayName = "TreeView";
3291
3440
 
3292
3441
  // src/components/slider.tsx
3293
3442
  var SliderPrimitive = __toESM(require("@radix-ui/react-slider"), 1);
3294
- var React12 = __toESM(require("react"), 1);
3443
+ var React13 = __toESM(require("react"), 1);
3295
3444
 
3296
3445
  // src/lib/dev-warn.ts
3297
3446
  var emitted = /* @__PURE__ */ new Set();
@@ -3322,7 +3471,7 @@ function thumbCount(value, defaultValue) {
3322
3471
  function hasAccessibleName(value) {
3323
3472
  return value !== void 0 && value.trim() !== "";
3324
3473
  }
3325
- var Slider = React12.forwardRef(
3474
+ var Slider = React13.forwardRef(
3326
3475
  ({
3327
3476
  className,
3328
3477
  value,
@@ -3333,7 +3482,7 @@ var Slider = React12.forwardRef(
3333
3482
  "aria-labelledby": ariaLabelledBy,
3334
3483
  ...props
3335
3484
  }, ref) => {
3336
- const initialUncontrolledCount = React12.useRef(
3485
+ const initialUncontrolledCount = React13.useRef(
3337
3486
  thumbCount(void 0, defaultValue)
3338
3487
  ).current;
3339
3488
  const count = value?.length ?? initialUncontrolledCount;
@@ -3442,7 +3591,7 @@ var Slider = React12.forwardRef(
3442
3591
  Slider.displayName = SliderPrimitive.Root.displayName;
3443
3592
 
3444
3593
  // src/lib/shortcuts/react.tsx
3445
- var React13 = __toESM(require("react"), 1);
3594
+ var React14 = __toESM(require("react"), 1);
3446
3595
 
3447
3596
  // src/lib/shortcuts/key-spec.ts
3448
3597
  function normalizeKey(key) {
@@ -3971,9 +4120,8 @@ var FIELD_KEYS = /* @__PURE__ */ new Set([
3971
4120
 
3972
4121
  // src/lib/shortcuts/react.tsx
3973
4122
  var import_jsx_runtime39 = require("react/jsx-runtime");
3974
- var ShortcutContext = React13.createContext(null);
4123
+ var ShortcutContext = React14.createContext(null);
3975
4124
  var ownersByTarget = /* @__PURE__ */ new WeakMap();
3976
- var useIsomorphicLayoutEffect = typeof document === "undefined" ? React13.useEffect : React13.useLayoutEffect;
3977
4125
  function optionsFingerprint(options) {
3978
4126
  return [
3979
4127
  options.isApple ?? "auto",
@@ -3986,13 +4134,13 @@ function ShortcutProvider({
3986
4134
  target,
3987
4135
  ...managerOptions
3988
4136
  }) {
3989
- const parent = React13.useContext(ShortcutContext);
4137
+ const parent = React14.useContext(ShortcutContext);
3990
4138
  const resolvedTarget = target === null ? null : target ?? (typeof document === "undefined" ? null : document);
3991
4139
  const nestedOnSameTarget = parent !== null && parent.target === resolvedTarget;
3992
- const optionsRef = React13.useRef(managerOptions);
3993
- const ownManagers = React13.useRef(/* @__PURE__ */ new WeakMap());
3994
- const ownDetached = React13.useRef(null);
3995
- const detached = React13.useMemo(() => {
4140
+ const optionsRef = React14.useRef(managerOptions);
4141
+ const ownManagers = React14.useRef(/* @__PURE__ */ new WeakMap());
4142
+ const ownDetached = React14.useRef(null);
4143
+ const detached = React14.useMemo(() => {
3996
4144
  if (resolvedTarget === null) {
3997
4145
  ownDetached.current ??= createShortcutManager(optionsRef.current);
3998
4146
  return ownDetached.current;
@@ -4048,7 +4196,7 @@ function ShortcutProvider({
4048
4196
  };
4049
4197
  }, [resolvedTarget, manager]);
4050
4198
  const depth = nestedOnSameTarget && parent ? parent.depth : 0;
4051
- const value = React13.useMemo(
4199
+ const value = React14.useMemo(
4052
4200
  () => ({ manager, depth, target: resolvedTarget, options: fingerprint }),
4053
4201
  [manager, depth, resolvedTarget, fingerprint]
4054
4202
  );
@@ -4057,11 +4205,11 @@ function ShortcutProvider({
4057
4205
  function ShortcutScope({
4058
4206
  children
4059
4207
  }) {
4060
- const parent = React13.useContext(ShortcutContext);
4208
+ const parent = React14.useContext(ShortcutContext);
4061
4209
  if (!parent) {
4062
4210
  throw new Error("ShortcutScope must be rendered inside a ShortcutProvider");
4063
4211
  }
4064
- const value = React13.useMemo(
4212
+ const value = React14.useMemo(
4065
4213
  () => ({
4066
4214
  manager: parent.manager,
4067
4215
  depth: parent.depth + 1,
@@ -4075,13 +4223,13 @@ function ShortcutScope({
4075
4223
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ShortcutContext.Provider, { value, children });
4076
4224
  }
4077
4225
  function useShortcuts(bindings, options) {
4078
- const context = React13.useContext(ShortcutContext);
4226
+ const context = React14.useContext(ShortcutContext);
4079
4227
  if (!context) {
4080
4228
  throw new Error("useShortcuts must be called inside a ShortcutProvider");
4081
4229
  }
4082
4230
  const { manager, depth } = context;
4083
- const registration = React13.useRef(null);
4084
- const latest = React13.useRef({ bindings, options });
4231
+ const registration = React14.useRef(null);
4232
+ const latest = React14.useRef({ bindings, options });
4085
4233
  useIsomorphicLayoutEffect(() => {
4086
4234
  registration.current = manager.register([], {
4087
4235
  name: latest.current.options.name,
@@ -4104,7 +4252,7 @@ function useShortcuts(bindings, options) {
4104
4252
  });
4105
4253
  }
4106
4254
  function useShortcutManager() {
4107
- const context = React13.useContext(ShortcutContext);
4255
+ const context = React14.useContext(ShortcutContext);
4108
4256
  if (!context) {
4109
4257
  throw new Error(
4110
4258
  "useShortcutManager must be called inside a ShortcutProvider"
@@ -4114,7 +4262,7 @@ function useShortcutManager() {
4114
4262
  }
4115
4263
  function useActiveShortcuts() {
4116
4264
  const manager = useShortcutManager();
4117
- return React13.useSyncExternalStore(
4265
+ return React14.useSyncExternalStore(
4118
4266
  manager.subscribe,
4119
4267
  manager.activeBindings,
4120
4268
  manager.activeBindings
@@ -4241,29 +4389,12 @@ function FormSection({
4241
4389
  ),
4242
4390
  description ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-sm text-muted-foreground", children: description }) : null
4243
4391
  ] }),
4244
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Card, { className: "overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "divide-y divide-foreground/10 px-6", children }) })
4392
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Card, { className: "overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "nx-form-section-rows divide-y divide-foreground/10 px-6", children }) })
4245
4393
  ] });
4246
4394
  }
4247
4395
 
4248
- // src/components/form-layout.tsx
4249
- var import_class_variance_authority14 = require("class-variance-authority");
4396
+ // src/components/form-actions.tsx
4250
4397
  var import_jsx_runtime42 = require("react/jsx-runtime");
4251
- var measure = (0, import_class_variance_authority14.cva)("mx-auto w-full px-6 py-8", {
4252
- variants: {
4253
- width: {
4254
- form: "max-w-[56rem]",
4255
- wide: "max-w-[72rem]"
4256
- }
4257
- },
4258
- defaultVariants: { width: "form" }
4259
- });
4260
- function FormLayout({
4261
- width = "form",
4262
- className,
4263
- children
4264
- }) {
4265
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: cn(measure({ width }), className), children });
4266
- }
4267
4398
  function FormActions({ dirty, className, children }) {
4268
4399
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
4269
4400
  "div",
@@ -4286,6 +4417,120 @@ function FormActions({ dirty, className, children }) {
4286
4417
  }
4287
4418
  );
4288
4419
  }
4420
+
4421
+ // src/components/page-shell.tsx
4422
+ var import_react21 = require("react");
4423
+ var import_jsx_runtime43 = require("react/jsx-runtime");
4424
+ function hasBareTextChild(shell) {
4425
+ return Array.from(shell.childNodes).some(
4426
+ (node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== ""
4427
+ );
4428
+ }
4429
+ var InsideShell = (0, import_react21.createContext)(false);
4430
+ function hasDisplayContentsChild(shell) {
4431
+ return Array.from(shell.children).some(
4432
+ (child) => getComputedStyle(child).display === "contents"
4433
+ );
4434
+ }
4435
+ var SHELL_MEASURE = {
4436
+ form: "var(--nx-measure-form)",
4437
+ wide: "var(--nx-measure-wide)",
4438
+ full: "100%"
4439
+ };
4440
+ var PageShell = (0, import_react21.forwardRef)(
4441
+ ({ width = "form", className, children, style, ...props }, ref) => {
4442
+ const shellRef = (0, import_react21.useRef)(null);
4443
+ const attachRef = (0, import_react21.useCallback)(
4444
+ (node) => {
4445
+ shellRef.current = node;
4446
+ if (typeof ref === "function") return ref(node);
4447
+ if (ref) ref.current = node;
4448
+ },
4449
+ [ref]
4450
+ );
4451
+ const nested = (0, import_react21.useContext)(InsideShell);
4452
+ (0, import_react21.useEffect)(() => {
4453
+ if (!isDevelopmentRuntime()) return;
4454
+ const shell = shellRef.current;
4455
+ if (!shell) return;
4456
+ devWarnOnce(
4457
+ !nested,
4458
+ "PageShell: an ancestor already renders a PageShell. The two grids each add a pair of gutter tracks, so this page's content is inset twice. Remove this shell, or move the outer one."
4459
+ );
4460
+ const check = () => {
4461
+ devWarnOnce(
4462
+ !hasBareTextChild(shell),
4463
+ "PageShell: a direct child renders as bare text. CSS Grid wraps it in an anonymous grid item, which no selector can reach, so it is placed in the gutter rather than the content column and sits outside the page's measure. Wrap it in an element \u2014 a `<p>`, or the section it belongs to."
4464
+ );
4465
+ devWarnOnce(
4466
+ !hasDisplayContentsChild(shell),
4467
+ "PageShell: a direct child has `display: contents`, so it generates no grid item and the content-column rule has nothing to apply to. Its own children are promoted into this grid, match no selector, and are placed from the gutter onward. Give the child a box, or move `display: contents` inside it."
4468
+ );
4469
+ };
4470
+ check();
4471
+ const observer = new MutationObserver(check);
4472
+ observer.observe(shell, { childList: true });
4473
+ return () => {
4474
+ observer.disconnect();
4475
+ };
4476
+ });
4477
+ const shellStyle = {
4478
+ ...style,
4479
+ "--nx-shell-measure": SHELL_MEASURE[width]
4480
+ };
4481
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(InsideShell.Provider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
4482
+ "div",
4483
+ {
4484
+ ref: attachRef,
4485
+ "data-slot": "page-shell",
4486
+ style: shellStyle,
4487
+ className: cn("nx-page-shell py-8", className),
4488
+ ...props,
4489
+ children
4490
+ }
4491
+ ) });
4492
+ }
4493
+ );
4494
+ PageShell.displayName = "PageShell";
4495
+ var Bleed = (0, import_react21.forwardRef)(
4496
+ ({ className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
4497
+ "div",
4498
+ {
4499
+ ref,
4500
+ "data-slot": "bleed",
4501
+ className: cn("nx-bleed", className),
4502
+ ...props,
4503
+ children
4504
+ }
4505
+ )
4506
+ );
4507
+ Bleed.displayName = "Bleed";
4508
+
4509
+ // src/components/page-header.tsx
4510
+ var import_react22 = require("react");
4511
+ var import_jsx_runtime44 = require("react/jsx-runtime");
4512
+ var PageHeader = (0, import_react22.forwardRef)(
4513
+ ({ title, description, breadcrumbs, actions, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
4514
+ "header",
4515
+ {
4516
+ ref,
4517
+ "data-slot": "page-header",
4518
+ className: cn("mb-8", className),
4519
+ ...props,
4520
+ children: [
4521
+ breadcrumbs ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "mb-6", children: breadcrumbs }) : null,
4522
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex flex-col justify-between gap-4 sm:flex-row sm:items-center", children: [
4523
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "space-y-1", children: [
4524
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("h1", { className: "text-xl font-semibold tracking-tight", children: title }),
4525
+ description ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "mt-1 text-sm font-normal text-muted-foreground", children: description }) : null
4526
+ ] }),
4527
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex items-center gap-3", children: actions }) : null
4528
+ ] })
4529
+ ]
4530
+ }
4531
+ )
4532
+ );
4533
+ PageHeader.displayName = "PageHeader";
4289
4534
  // Annotate the CommonJS export names for ESM import in node:
4290
4535
  0 && (module.exports = {
4291
4536
  Accordion,
@@ -4310,6 +4555,7 @@ function FormActions({ dirty, className, children }) {
4310
4555
  AvatarFallback,
4311
4556
  AvatarImage,
4312
4557
  Badge,
4558
+ Bleed,
4313
4559
  Button,
4314
4560
  Card,
4315
4561
  CardAction,
@@ -4374,11 +4620,12 @@ function FormActions({ dirty, className, children }) {
4374
4620
  FieldShell,
4375
4621
  FormActions,
4376
4622
  FormLabelWithTooltip,
4377
- FormLayout,
4378
4623
  FormSection,
4379
4624
  Grid,
4380
4625
  Input,
4381
4626
  Label,
4627
+ PageHeader,
4628
+ PageShell,
4382
4629
  Popover,
4383
4630
  PopoverAnchor,
4384
4631
  PopoverContent,
@@ -4390,6 +4637,7 @@ function FormActions({ dirty, className, children }) {
4390
4637
  ResizableHandle,
4391
4638
  ResizablePanel,
4392
4639
  ResizablePanelGroup,
4640
+ SHELL_MEASURE,
4393
4641
  Select,
4394
4642
  SelectContent,
4395
4643
  SelectGroup,
@@ -4448,8 +4696,10 @@ function FormActions({ dirty, className, children }) {
4448
4696
  badgeVariants,
4449
4697
  buttonVariants,
4450
4698
  cardVariants,
4699
+ chordMatches,
4451
4700
  commandDefaultFilter,
4452
4701
  createShortcutManager,
4702
+ detectApplePlatform,
4453
4703
  dialogContentVariants,
4454
4704
  inputVariants,
4455
4705
  parseKeys,
@@ -4459,6 +4709,7 @@ function FormActions({ dirty, className, children }) {
4459
4709
  spinnerVariants,
4460
4710
  toast,
4461
4711
  useActiveShortcuts,
4712
+ useCommandHighlight,
4462
4713
  usePortalContainer,
4463
4714
  useShortcutManager,
4464
4715
  useShortcuts