foss-design 0.9.11 → 0.9.12

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.
@@ -14,8 +14,8 @@
14
14
  document.documentElement.dataset.theme = dark ? 'dark' : 'light'
15
15
  } catch (e) {}
16
16
  </script>
17
- <script type="module" crossorigin src="./assets/index-mvGp1q3N.js"></script>
18
- <link rel="stylesheet" crossorigin href="./assets/index-Cy6JlR85.css">
17
+ <script type="module" crossorigin src="./assets/index-Cm4CEv8V.js"></script>
18
+ <link rel="stylesheet" crossorigin href="./assets/index-BUDcnDgZ.css">
19
19
  </head>
20
20
  <body>
21
21
  <div id="root"></div>
@@ -2730,6 +2730,7 @@ var ICON_PATHS = {
2730
2730
  tablet: "<rect width=\"16\" height=\"20\" x=\"4\" y=\"2\" rx=\"2\"/><path d=\"M12 18h.01\"/>",
2731
2731
  note: "<path d=\"M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z\"/><path d=\"M15 3v4a2 2 0 0 0 2 2h4\"/>",
2732
2732
  globe: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\"/><path d=\"M2 12h20\"/>",
2733
+ share: "<path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><path d=\"m16 6-4-4-4 4\"/><path d=\"M12 2v13\"/>",
2733
2734
  pointer: "<path d=\"M4.04 4.69a.5.5 0 0 1 .65-.65l16 6.5a.5.5 0 0 1-.06.95l-6.12 1.58a2 2 0 0 0-1.44 1.44l-1.58 6.12a.5.5 0 0 1-.95.06z\"/>",
2734
2735
  component: "<path d=\"M5.5 8.5 9 12l-3.5 3.5L2 12l3.5-3.5Z\"/><path d=\"m12 2 3.5 3.5L12 9 8.5 5.5 12 2Z\"/><path d=\"M18.5 8.5 22 12l-3.5 3.5L15 12l3.5-3.5Z\"/><path d=\"m12 15 3.5 3.5L12 22l-3.5-3.5L12 15Z\"/>",
2735
2736
  keyboard: "<rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/><path d=\"M6 8h.01\"/><path d=\"M10 8h.01\"/><path d=\"M14 8h.01\"/><path d=\"M18 8h.01\"/><path d=\"M8 12h.01\"/><path d=\"M12 12h.01\"/><path d=\"M16 12h.01\"/><path d=\"M7 16h10\"/>",
@@ -5222,10 +5223,10 @@ var Popper = /* @__PURE__ */ __name$9((props) => {
5222
5223
  children
5223
5224
  });
5224
5225
  }, "Popper");
5225
- var ANCHOR_NAME = "PopperAnchor";
5226
+ var ANCHOR_NAME$1 = "PopperAnchor";
5226
5227
  var PopperAnchor = /* @__PURE__ */ React.forwardRef(/* @__PURE__ */ __name$9(function PopperAnchor2(props, forwardedRef) {
5227
5228
  const { __scopePopper, virtualRef, ...anchorProps } = props;
5228
- const context = usePopperContext(ANCHOR_NAME, __scopePopper);
5229
+ const context = usePopperContext(ANCHOR_NAME$1, __scopePopper);
5229
5230
  const ref = React.useRef(null);
5230
5231
  const onAnchorChange = context.onAnchorChange;
5231
5232
  const composedRefs = useComposedRefs(forwardedRef, React.useCallback((node) => {
@@ -7263,7 +7264,7 @@ function whenMouse(handler) {
7263
7264
  }
7264
7265
  __name$2(whenMouse, "whenMouse");
7265
7266
  var Root3 = Menu$1;
7266
- var Anchor2 = MenuAnchor;
7267
+ var Anchor2$1 = MenuAnchor;
7267
7268
  var Portal$1 = MenuPortal;
7268
7269
  var Content2$2 = MenuContent;
7269
7270
  var Label$1 = MenuLabel$1;
@@ -7315,7 +7316,7 @@ var DropdownMenuTrigger = /* @__PURE__ */ React.forwardRef(/* @__PURE__ */ __nam
7315
7316
  const context = useDropdownMenuContext(TRIGGER_NAME$1, __scopeDropdownMenu);
7316
7317
  const menuScope = useMenuScope(__scopeDropdownMenu);
7317
7318
  const composedRefs = useComposedRefs(forwardedRef, context.triggerRef);
7318
- return /* @__PURE__ */ jsx(Anchor2, {
7319
+ return /* @__PURE__ */ jsx(Anchor2$1, {
7319
7320
  asChild: true,
7320
7321
  ...menuScope,
7321
7322
  children: /* @__PURE__ */ jsx(Primitive.button, {
@@ -7828,6 +7829,22 @@ var Popover = /* @__PURE__ */ __name((props) => {
7828
7829
  })
7829
7830
  });
7830
7831
  }, "Popover");
7832
+ var ANCHOR_NAME = "PopoverAnchor";
7833
+ var PopoverAnchor = /* @__PURE__ */ React.forwardRef(/* @__PURE__ */ __name(function PopoverAnchor2(props, forwardedRef) {
7834
+ const { __scopePopover, ...anchorProps } = props;
7835
+ const context = usePopoverContext(ANCHOR_NAME, __scopePopover);
7836
+ const popperScope = usePopperScope(__scopePopover);
7837
+ const { onCustomAnchorAdd, onCustomAnchorRemove } = context;
7838
+ React.useEffect(() => {
7839
+ onCustomAnchorAdd();
7840
+ return () => onCustomAnchorRemove();
7841
+ }, [onCustomAnchorAdd, onCustomAnchorRemove]);
7842
+ return /* @__PURE__ */ jsx(Anchor, {
7843
+ ...popperScope,
7844
+ ...anchorProps,
7845
+ ref: forwardedRef
7846
+ });
7847
+ }, "PopoverAnchor"));
7831
7848
  var TRIGGER_NAME = "PopoverTrigger";
7832
7849
  var PopoverTrigger = /* @__PURE__ */ React.forwardRef(/* @__PURE__ */ __name(function PopoverTrigger2(props, forwardedRef) {
7833
7850
  const { __scopePopover, ...triggerProps } = props;
@@ -7990,6 +8007,7 @@ function getState(open) {
7990
8007
  }
7991
8008
  __name(getState, "getState");
7992
8009
  var Root2 = Popover;
8010
+ var Anchor2 = PopoverAnchor;
7993
8011
  var Trigger = PopoverTrigger;
7994
8012
  var Portal = PopoverPortal;
7995
8013
  var Content2 = PopoverContent;
@@ -11523,22 +11541,84 @@ function CanvasBar({ canvas, page, theme, onTheme, sidebar, onSidebar, onPlay, c
11523
11541
  }) : null]
11524
11542
  });
11525
11543
  }
11526
- /** The selected item's name and actions, floating over the bottom of the canvas. */
11544
+ /** The selected item's name and actions, floating over the bottom of the canvas; it eases in and out. */
11527
11545
  function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
11528
11546
  const selected = useStore(store, (state) => state.selected);
11529
- const active = useStore(store, (state) => state.active);
11530
- const status = useStore(store, (state) => selected ? state.frames[selected] : void 0);
11531
11547
  const item = layoutItems.find((i) => i.id === selected);
11532
- const openUrl = useOpenUrl(canvas.id, item?.kind === "screen" || item?.kind === "url" ? item : void 0, theme);
11533
- if (!item) return null;
11534
- const frame = item.kind === "screen" || item.kind === "url";
11535
- const reload = () => reloadFrame(document.querySelector(`[data-item="${CSS.escape(item.id)}"] iframe`));
11536
- const errors = status?.errors ?? [];
11537
11548
  return /* @__PURE__ */ jsx("div", {
11538
11549
  "data-ui": true,
11539
11550
  className: "pointer-events-none absolute inset-x-0 bottom-5 z-20 flex justify-center px-4",
11540
- children: /* @__PURE__ */ jsxs("div", {
11541
- className: "pointer-events-auto flex max-w-full flex-col overflow-hidden rounded-[10px] border border-rule bg-surface shadow-pop animate-[q-pop_120ms_ease-out]",
11551
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: item ? /* @__PURE__ */ jsx(SelectedBar, {
11552
+ canvas,
11553
+ store,
11554
+ item,
11555
+ theme,
11556
+ onPlay,
11557
+ inspect
11558
+ }, "bar") : null })
11559
+ });
11560
+ }
11561
+ function SelectedBar({ canvas, store, item, theme, onPlay, inspect }) {
11562
+ const { slots } = useViewer();
11563
+ const status = useStore(store, (state) => state.frames[item.id]);
11564
+ const openUrl = useOpenUrl(canvas.id, item.kind === "screen" || item.kind === "url" ? item : void 0, theme);
11565
+ const frame = item.kind === "screen" || item.kind === "url";
11566
+ const reload = () => reloadFrame(document.querySelector(`[data-item="${CSS.escape(item.id)}"] iframe`));
11567
+ const errors = status?.errors ?? [];
11568
+ return /* @__PURE__ */ jsxs(motion.div, {
11569
+ initial: {
11570
+ opacity: 0,
11571
+ y: 10,
11572
+ scale: .98
11573
+ },
11574
+ animate: {
11575
+ opacity: 1,
11576
+ y: 0,
11577
+ scale: 1
11578
+ },
11579
+ exit: {
11580
+ opacity: 0,
11581
+ y: 10,
11582
+ scale: .98,
11583
+ transition: {
11584
+ duration: .14,
11585
+ ease: "easeIn"
11586
+ }
11587
+ },
11588
+ transition: {
11589
+ type: "spring",
11590
+ duration: .26,
11591
+ bounce: .12
11592
+ },
11593
+ className: "relative flex w-fit max-w-[95%] min-w-0 flex-col",
11594
+ children: [/* @__PURE__ */ jsx("div", {
11595
+ className: "pointer-events-none absolute inset-x-5 bottom-full flex justify-center",
11596
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: frame && inspect ? /* @__PURE__ */ jsx(motion.div, {
11597
+ initial: {
11598
+ y: "100%",
11599
+ opacity: 0
11600
+ },
11601
+ animate: {
11602
+ y: 0,
11603
+ opacity: .9
11604
+ },
11605
+ exit: {
11606
+ y: "100%",
11607
+ opacity: 0
11608
+ },
11609
+ transition: {
11610
+ type: "spring",
11611
+ duration: .28,
11612
+ bounce: .15
11613
+ },
11614
+ className: "flex h-[22px] min-w-0 items-center rounded-t-[7px] border border-b-0 border-rule bg-surface/80 px-3 text-[11.5px] whitespace-nowrap text-muted backdrop-blur-md",
11615
+ children: /* @__PURE__ */ jsx("span", {
11616
+ className: "truncate",
11617
+ children: "Click an element to inspect it"
11618
+ })
11619
+ }, "inspect") : null })
11620
+ }), /* @__PURE__ */ jsxs("div", {
11621
+ className: "pointer-events-auto relative flex max-w-full flex-col overflow-hidden rounded-[10px] border border-rule bg-surface shadow-pop",
11542
11622
  children: [errors.length ? /* @__PURE__ */ jsxs("div", {
11543
11623
  className: "flex max-h-40 items-start gap-2 overflow-y-auto border-b border-rule bg-danger-soft px-3.5 py-2 font-mono text-[12px] whitespace-pre-wrap text-danger-text",
11544
11624
  children: [/* @__PURE__ */ jsx(Icon, {
@@ -11550,29 +11630,11 @@ function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
11550
11630
  children: errors.join("\n")
11551
11631
  })]
11552
11632
  }) : null, /* @__PURE__ */ jsxs("div", {
11553
- className: "flex h-[44px] items-center gap-1 pr-1.5 pl-3.5",
11633
+ className: "flex h-[44px] items-center gap-1 pr-1.5 pl-3.5 [&>:not(:first-child)]:shrink-0",
11554
11634
  children: [
11555
- /* @__PURE__ */ jsxs("div", {
11556
- className: "flex min-w-0 items-center gap-2 pr-2",
11557
- children: [/* @__PURE__ */ jsx("span", {
11558
- className: "truncate text-[13.5px] font-medium",
11559
- children: item.title || item.id
11560
- }), frame && active === item.id ? /* @__PURE__ */ jsxs("span", {
11561
- className: "flex shrink-0 items-center gap-1.5 text-[12px] text-muted",
11562
- children: [
11563
- /* @__PURE__ */ jsx(Icon, {
11564
- name: "pointer",
11565
- size: 13
11566
- }),
11567
- " ",
11568
- inspect ? "Inspecting" : "Interacting",
11569
- " ",
11570
- /* @__PURE__ */ jsx(Kbd, { children: "esc" })
11571
- ]
11572
- }) : frame ? /* @__PURE__ */ jsx("span", {
11573
- className: "shrink-0 text-[12px] text-muted",
11574
- children: inspect ? "Click an element to inspect it" : "Click the screen to interact"
11575
- }) : null]
11635
+ /* @__PURE__ */ jsx("span", {
11636
+ className: "min-w-0 truncate pr-2 text-[13.5px] font-medium",
11637
+ children: item.title || item.id
11576
11638
  }),
11577
11639
  frame ? /* @__PURE__ */ jsx(Tooltip, {
11578
11640
  content: /* @__PURE__ */ jsxs("span", {
@@ -11615,6 +11677,7 @@ function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
11615
11677
  onClick: () => void copyText(item.file, "Path copied")
11616
11678
  })
11617
11679
  }) : null,
11680
+ slots.selectionActions?.(canvas.id, item),
11618
11681
  /* @__PURE__ */ jsx(Tooltip, {
11619
11682
  content: /* @__PURE__ */ jsxs("span", {
11620
11683
  className: "flex items-center gap-1.5",
@@ -11631,7 +11694,7 @@ function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
11631
11694
  })
11632
11695
  ]
11633
11696
  })]
11634
- })
11697
+ })]
11635
11698
  });
11636
11699
  }
11637
11700
  /** The inspect mode switch: a button that stays pressed while on. */
@@ -11748,7 +11811,8 @@ function layoutPage(page, sizes) {
11748
11811
  y: top,
11749
11812
  w: Math.max(width, header ? HEADER_MIN_WIDTH : 0),
11750
11813
  h: bottom - top,
11751
- header
11814
+ header,
11815
+ body: header ? header + HEADER_GAP : 0
11752
11816
  });
11753
11817
  y = bottom + SECTION_GAP;
11754
11818
  }
@@ -12168,13 +12232,17 @@ var Label = memo(function Label({ placed, store }) {
12168
12232
  ]
12169
12233
  });
12170
12234
  });
12171
- /** A section's title in screen space, for zooms where the one on the canvas is too small to read. */
12235
+ /**
12236
+ * A section's title in screen space, for zooms where the one on the canvas is too small to read: where
12237
+ * that one was, lifted only as far as it must be to clear the frame labels under it.
12238
+ */
12172
12239
  var SectionTitle = memo(function SectionTitle({ placed }) {
12173
12240
  return /* @__PURE__ */ jsx("div", {
12174
12241
  "data-at": "",
12175
12242
  style: {
12176
12243
  "--x": placed.x,
12177
- "--y": placed.y
12244
+ "--y": placed.y,
12245
+ "--body": placed.body
12178
12246
  },
12179
12247
  className: "section-title text-[15px] leading-none font-semibold whitespace-nowrap text-ink select-none",
12180
12248
  children: placed.section.title
@@ -12772,15 +12840,20 @@ function CanvasView({ canvas, page }) {
12772
12840
  useEffect(() => {
12773
12841
  if (picked) setInspect(true);
12774
12842
  }, [picked]);
12843
+ const pickedInfo = inspection.info;
12775
12844
  useEffect(() => {
12776
12845
  const frame = inspection.frame;
12777
- if (!frame) return;
12846
+ if (!frame || !pickedInfo) return;
12778
12847
  const id = Object.entries(store.get().frameEls).find(([, el]) => el === frame)?.[0];
12779
12848
  if (id && store.get().selected !== id) store.set((state) => ({
12780
12849
  ...state,
12781
12850
  selected: id
12782
12851
  }));
12783
- }, [inspection.frame, store]);
12852
+ }, [
12853
+ inspection.frame,
12854
+ pickedInfo,
12855
+ store
12856
+ ]);
12784
12857
  const order = useMemo(() => frameOrder(layout), [layout]);
12785
12858
  const key = cameraKey(canvas.id, page?.id ?? "");
12786
12859
  const placed = useRef(false);
@@ -12895,7 +12968,8 @@ function CanvasView({ canvas, page }) {
12895
12968
  onEscape() {
12896
12969
  store.set((state) => ({
12897
12970
  ...state,
12898
- active: null
12971
+ active: null,
12972
+ selected: null
12899
12973
  }));
12900
12974
  }
12901
12975
  }), [
@@ -12928,12 +13002,9 @@ function CanvasView({ canvas, page }) {
12928
13002
  if (e.key === "Escape") {
12929
13003
  if (inspection.info) inspection.select(null);
12930
13004
  else if (inspectOn) setInspect(false);
12931
- else if (state.active) store.set((s) => ({
12932
- ...s,
12933
- active: null
12934
- }));
12935
- else if (state.selected) store.set((s) => ({
13005
+ else if (state.active || state.selected) store.set((s) => ({
12936
13006
  ...s,
13007
+ active: null,
12937
13008
  selected: null
12938
13009
  }));
12939
13010
  } else if (e.key === "i" && !mod && !e.altKey) setInspect((value) => !value);
@@ -13045,7 +13116,13 @@ function CanvasView({ canvas, page }) {
13045
13116
  capture: false,
13046
13117
  apiRef,
13047
13118
  onPlay: play,
13048
- onSelect: select
13119
+ onSelect: (id) => {
13120
+ if (!id) {
13121
+ inspection.select(null);
13122
+ setInspect(false);
13123
+ }
13124
+ select(id);
13125
+ }
13049
13126
  }) : /* @__PURE__ */ jsx("div", {
13050
13127
  className: "canvas-dots flex h-full",
13051
13128
  children: /* @__PURE__ */ jsx(Notice, {
@@ -19094,6 +19171,155 @@ function Avatar({ src, name, size = 28, shape = "circle", className }) {
19094
19171
  });
19095
19172
  }
19096
19173
  //#endregion
19174
+ //#region viewer/src/ui/combobox.tsx
19175
+ /**
19176
+ * One option out of many, in a field: it shows the picked one, typing narrows the list, arrows and
19177
+ * Enter pick without the mouse. The list keeps to a few rows and scrolls, however many there are.
19178
+ */
19179
+ function Combobox({ options, value, onChange, placeholder, empty = "Nothing matches", className, "aria-label": label }) {
19180
+ const listId = useId();
19181
+ const anchor = useRef(null);
19182
+ const field = useRef(null);
19183
+ const list = useRef(null);
19184
+ const [open, setOpen] = useState(false);
19185
+ const [query, setQuery] = useState("");
19186
+ const [active, setActive] = useState(0);
19187
+ const picked = options.find((option) => option.value === value);
19188
+ const shown = useMemo(() => {
19189
+ const q = query.trim().toLowerCase();
19190
+ return q ? options.filter((option) => `${option.label} ${option.keywords ?? ""}`.toLowerCase().includes(q)) : options;
19191
+ }, [options, query]);
19192
+ const current = Math.min(active, shown.length - 1);
19193
+ const show = () => {
19194
+ setQuery("");
19195
+ setActive(Math.max(0, options.findIndex((option) => option.value === value)));
19196
+ setOpen(true);
19197
+ };
19198
+ const pick = (option) => {
19199
+ onChange(option.value);
19200
+ setOpen(false);
19201
+ setQuery("");
19202
+ };
19203
+ useEffect(() => {
19204
+ if (open) list.current?.querySelector(`[data-index="${current}"]`)?.scrollIntoView({ block: "nearest" });
19205
+ }, [open, current]);
19206
+ return /* @__PURE__ */ jsxs(Root2, {
19207
+ open,
19208
+ onOpenChange: setOpen,
19209
+ children: [/* @__PURE__ */ jsx(Anchor2, {
19210
+ asChild: true,
19211
+ children: /* @__PURE__ */ jsxs("div", {
19212
+ ref: anchor,
19213
+ onClick: () => {
19214
+ field.current?.focus();
19215
+ if (!open) show();
19216
+ },
19217
+ className: cn("flex h-ctrl w-full min-w-0 cursor-text items-center gap-2 rounded-[6px] border border-rule bg-surface pr-2 pl-2 text-ctrl transition-colors hover:border-rule-strong", "focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-action", className),
19218
+ children: [
19219
+ picked?.media && !query ? /* @__PURE__ */ jsx("span", {
19220
+ className: "flex shrink-0",
19221
+ children: picked.media
19222
+ }) : null,
19223
+ /* @__PURE__ */ jsx("input", {
19224
+ ref: field,
19225
+ value: open ? query : picked?.label ?? "",
19226
+ placeholder: open && picked ? picked.label : placeholder,
19227
+ autoComplete: "off",
19228
+ spellCheck: false,
19229
+ role: "combobox",
19230
+ "aria-label": label,
19231
+ "aria-expanded": open,
19232
+ "aria-controls": listId,
19233
+ "aria-autocomplete": "list",
19234
+ "aria-activedescendant": open && shown[current] ? `${listId}-${current}` : void 0,
19235
+ onChange: (event) => {
19236
+ setQuery(event.target.value);
19237
+ setActive(0);
19238
+ setOpen(true);
19239
+ },
19240
+ onKeyDown: (event) => {
19241
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
19242
+ event.preventDefault();
19243
+ if (!open) return show();
19244
+ const step = event.key === "ArrowDown" ? 1 : -1;
19245
+ setActive((i) => Math.min(Math.max(i + step, 0), shown.length - 1));
19246
+ } else if (event.key === "Enter" && open && shown[current]) {
19247
+ event.preventDefault();
19248
+ pick(shown[current]);
19249
+ } else if (event.key === "Escape" && open) {
19250
+ event.preventDefault();
19251
+ event.stopPropagation();
19252
+ setOpen(false);
19253
+ } else if (event.key === "Tab") setOpen(false);
19254
+ },
19255
+ className: "h-full min-w-0 grow border-0 bg-transparent p-0 text-ink outline-none placeholder:text-muted"
19256
+ }),
19257
+ /* @__PURE__ */ jsx(Icon, {
19258
+ name: "chevron-down",
19259
+ size: 16,
19260
+ className: cn("shrink-0 text-muted transition-transform", open && "rotate-180")
19261
+ })
19262
+ ]
19263
+ })
19264
+ }), /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Content2, {
19265
+ side: "bottom",
19266
+ align: "start",
19267
+ sideOffset: 4,
19268
+ collisionPadding: 12,
19269
+ onOpenAutoFocus: (event) => event.preventDefault(),
19270
+ onCloseAutoFocus: (event) => event.preventDefault(),
19271
+ onInteractOutside: (event) => {
19272
+ if (anchor.current?.contains(event.target)) event.preventDefault();
19273
+ },
19274
+ className: cn(menuSurface, "max-h-[min(296px,var(--radix-popover-content-available-height))] overflow-y-auto"),
19275
+ style: { width: "var(--radix-popper-anchor-width)" },
19276
+ children: /* @__PURE__ */ jsx("div", {
19277
+ ref: list,
19278
+ id: listId,
19279
+ role: "listbox",
19280
+ "aria-label": label,
19281
+ children: shown.length ? shown.map((option, index) => /* @__PURE__ */ jsxs("div", {
19282
+ id: `${listId}-${index}`,
19283
+ "data-index": index,
19284
+ role: "option",
19285
+ "aria-selected": option.value === value,
19286
+ tabIndex: -1,
19287
+ onMouseDown: (event) => event.preventDefault(),
19288
+ onMouseMove: () => index !== current && setActive(index),
19289
+ onClick: () => pick(option),
19290
+ className: cn("flex min-h-[44px] cursor-pointer items-center gap-2.5 px-2 py-1.5 text-[13.5px]", index === current && "bg-soft"),
19291
+ children: [
19292
+ option.media ? /* @__PURE__ */ jsx("span", {
19293
+ className: "flex shrink-0",
19294
+ children: option.media
19295
+ }) : null,
19296
+ /* @__PURE__ */ jsxs("span", {
19297
+ className: "flex min-w-0 grow flex-col gap-0.5",
19298
+ children: [/* @__PURE__ */ jsx("span", {
19299
+ className: "truncate font-medium text-ink",
19300
+ children: option.label
19301
+ }), option.sub ? /* @__PURE__ */ jsx("span", {
19302
+ className: "truncate text-[12px] text-muted",
19303
+ children: option.sub
19304
+ }) : null]
19305
+ }),
19306
+ /* @__PURE__ */ jsx("span", {
19307
+ className: cn("flex w-4 shrink-0 text-ink", option.value !== value && "invisible"),
19308
+ children: /* @__PURE__ */ jsx(Icon, {
19309
+ name: "check",
19310
+ size: 16
19311
+ })
19312
+ })
19313
+ ]
19314
+ }, option.value)) : /* @__PURE__ */ jsx("div", {
19315
+ className: "px-3 py-2.5 text-[13px] text-muted",
19316
+ children: empty
19317
+ })
19318
+ })
19319
+ }) })]
19320
+ });
19321
+ }
19322
+ //#endregion
19097
19323
  //#region viewer/src/ui/copy-button.tsx
19098
19324
  /** A ghost "Copy" button that turns into "Copied" with a check for a moment after it copies. */
19099
19325
  function CopyButton({ text, label = "Copy", done = "Copied", className }) {
@@ -19479,4 +19705,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
19479
19705
  });
19480
19706
  }
19481
19707
  //#endregion
19482
- export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, CopyButton, CornerPlate, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Sheet, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, followLink, frameSrc, iconButtonClass, initials, listenToFrame, localSource, menuItemClass, menuSurface, resolveUrl, sendTheme, setThemePref, staticSource, toast, useBarCompact, useIsPhone, useTheme, useThemePref };
19708
+ export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Combobox, Content, CopyButton, CornerPlate, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Sheet, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, followLink, frameSrc, iconButtonClass, initials, listenToFrame, localSource, menuItemClass, menuSurface, resolveUrl, sendTheme, setThemePref, staticSource, toast, useBarCompact, useIsPhone, useTheme, useThemePref };
@@ -74,6 +74,7 @@
74
74
  --animate-spin: spin 1s linear infinite;
75
75
  --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
76
76
  --blur-sm: 8px;
77
+ --blur-md: 12px;
77
78
  --default-transition-duration: .15s;
78
79
  --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
79
80
  --default-font-family: "IBM Plex Sans", system-ui, sans-serif;
@@ -417,6 +418,10 @@
417
418
  pointer-events: none;
418
419
  }
419
420
 
421
+ .invisible {
422
+ visibility: hidden;
423
+ }
424
+
420
425
  .visible {
421
426
  visibility: visible;
422
427
  }
@@ -465,6 +470,10 @@
465
470
  inset-inline: 0;
466
471
  }
467
472
 
473
+ .inset-x-5 {
474
+ inset-inline: calc(var(--spacing) * 5);
475
+ }
476
+
468
477
  .inset-x-6 {
469
478
  inset-inline: calc(var(--spacing) * 6);
470
479
  }
@@ -525,6 +534,10 @@
525
534
  bottom: calc(var(--spacing) * 5);
526
535
  }
527
536
 
537
+ .bottom-full {
538
+ bottom: 100%;
539
+ }
540
+
528
541
  .left-0 {
529
542
  left: 0;
530
543
  }
@@ -894,6 +907,10 @@
894
907
  max-height: min(56vh, 520px);
895
908
  }
896
909
 
910
+ .max-h-\[min\(296px\,var\(--radix-popover-content-available-height\)\)\] {
911
+ max-height: min(296px, var(--radix-popover-content-available-height));
912
+ }
913
+
897
914
  .max-h-\[min\(560px\,76vh\)\] {
898
915
  max-height: min(560px, 76vh);
899
916
  }
@@ -914,6 +931,10 @@
914
931
  min-height: 38px;
915
932
  }
916
933
 
934
+ .min-h-\[44px\] {
935
+ min-height: 44px;
936
+ }
937
+
917
938
  .min-h-\[46px\] {
918
939
  min-height: 46px;
919
940
  }
@@ -940,6 +961,10 @@
940
961
  }
941
962
  }
942
963
 
964
+ .w-4 {
965
+ width: calc(var(--spacing) * 4);
966
+ }
967
+
943
968
  .w-5 {
944
969
  width: calc(var(--spacing) * 5);
945
970
  }
@@ -1028,6 +1053,10 @@
1028
1053
  max-width: 80%;
1029
1054
  }
1030
1055
 
1056
+ .max-w-\[95\%\] {
1057
+ max-width: 95%;
1058
+ }
1059
+
1031
1060
  .max-w-\[260px\] {
1032
1061
  max-width: 260px;
1033
1062
  }
@@ -1130,6 +1159,10 @@
1130
1159
  translate: var(--tw-translate-x) var(--tw-translate-y);
1131
1160
  }
1132
1161
 
1162
+ .rotate-180 {
1163
+ rotate: 180deg;
1164
+ }
1165
+
1133
1166
  .transform {
1134
1167
  transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
1135
1168
  }
@@ -1138,10 +1171,6 @@
1138
1171
  animation: .16s ease-out q-fade-in;
1139
1172
  }
1140
1173
 
1141
- .animate-\[q-pop_120ms_ease-out\] {
1142
- animation: .12s ease-out q-pop;
1143
- }
1144
-
1145
1174
  .animate-pulse {
1146
1175
  animation: var(--animate-pulse);
1147
1176
  }
@@ -1170,6 +1199,10 @@
1170
1199
  cursor: pointer;
1171
1200
  }
1172
1201
 
1202
+ .cursor-text {
1203
+ cursor: text;
1204
+ }
1205
+
1173
1206
  .touch-none {
1174
1207
  touch-action: none;
1175
1208
  }
@@ -1376,6 +1409,11 @@
1376
1409
  border-radius: 3.40282e38px;
1377
1410
  }
1378
1411
 
1412
+ .rounded-t-\[7px\] {
1413
+ border-top-left-radius: 7px;
1414
+ border-top-right-radius: 7px;
1415
+ }
1416
+
1379
1417
  .rounded-t-\[14px\] {
1380
1418
  border-top-left-radius: 14px;
1381
1419
  border-top-right-radius: 14px;
@@ -1411,6 +1449,11 @@
1411
1449
  border-bottom-width: 1px;
1412
1450
  }
1413
1451
 
1452
+ .border-b-0 {
1453
+ border-bottom-style: var(--tw-border-style);
1454
+ border-bottom-width: 0;
1455
+ }
1456
+
1414
1457
  .border-l {
1415
1458
  border-left-style: var(--tw-border-style);
1416
1459
  border-left-width: 1px;
@@ -1558,6 +1601,16 @@
1558
1601
  }
1559
1602
  }
1560
1603
 
1604
+ .bg-surface\/80 {
1605
+ background-color: var(--surface);
1606
+ }
1607
+
1608
+ @supports (color: color-mix(in lab, red, red)) {
1609
+ .bg-surface\/80 {
1610
+ background-color: color-mix(in oklab, var(--surface) 80%, transparent);
1611
+ }
1612
+ }
1613
+
1561
1614
  .bg-surface\/90 {
1562
1615
  background-color: var(--surface);
1563
1616
  }
@@ -2186,6 +2239,12 @@
2186
2239
  filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
2187
2240
  }
2188
2241
 
2242
+ .backdrop-blur-md {
2243
+ --tw-backdrop-blur: blur(var(--blur-md));
2244
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
2245
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
2246
+ }
2247
+
2189
2248
  .backdrop-blur-sm {
2190
2249
  --tw-backdrop-blur: blur(var(--blur-sm));
2191
2250
  -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
@@ -2512,6 +2571,10 @@
2512
2571
  flex-wrap: wrap;
2513
2572
  }
2514
2573
  }
2574
+
2575
+ .\[\&\>\:not\(\:first-child\)\]\:shrink-0 > :not(:first-child) {
2576
+ flex-shrink: 0;
2577
+ }
2515
2578
  }
2516
2579
 
2517
2580
  @font-face {
@@ -3082,9 +3145,9 @@
3082
3145
  }
3083
3146
 
3084
3147
  .canvas-overlay .section-title {
3148
+ translate: 0 max(-100%, min(0px, calc(var(--body) * var(--z) * 1px - 44px)));
3085
3149
  padding-bottom: 8px;
3086
3150
  display: none;
3087
- translate: 0 -100%;
3088
3151
  }
3089
3152
 
3090
3153
  .canvas-overlay[data-titles="on"] .section-title {
@@ -1,3 +1,4 @@
1
+ import type { CanvasItem } from '../../../src/shared/types';
1
2
  import { type ReactNode } from 'react';
2
3
  import type { ViewerSource } from './source';
3
4
  /** Places where an app that embeds the viewer adds its own controls. */
@@ -28,6 +29,8 @@ export interface ViewerSlots {
28
29
  canvasesAfter?: ReactNode;
29
30
  /** Below the panels of the home page, full width. */
30
31
  homeAfter?: ReactNode;
32
+ /** In the bar of the item selected on a canvas, before the way to zoom to it: sharing that screen, say. */
33
+ selectionActions?: (canvasId: string, item: CanvasItem) => ReactNode;
31
34
  /**
32
35
  * On a screen's full-window page, the corner plate with the way out: `note` is a muted line under it
33
36
  * (whose content the screen is), `actions` sit next to the way out, and with `badge` the plate folds
@@ -16,6 +16,7 @@ export { Avatar, initials } from './ui/avatar';
16
16
  export { Badge, Count, type Tone } from './ui/badge';
17
17
  export { Button, ButtonAnchor, type ButtonKind, ButtonLink, type ButtonProps, buttonClass, IconButton, type IconButtonProps, iconButtonClass, } from './ui/button';
18
18
  export { Segmented, Switch } from './ui/choice';
19
+ export { Combobox, type ComboboxOption } from './ui/combobox';
19
20
  export { CopyButton } from './ui/copy-button';
20
21
  export { Dialog } from './ui/dialog';
21
22
  export { Icon, type IconName } from './ui/icon';