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

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