@weasel-js/labkit 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +3 -7
  2. package/dist/_dts/{CanvasStackContext-CMMo180l.d.ts → CanvasStackContext-DQgcH1ny.d.ts} +1 -1
  3. package/dist/_dts/LayerList.d-2c9yup5Y.d.ts +83 -0
  4. package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
  5. package/dist/_dts/{Select.d-ZTiwzYyT.d.ts → Select.d-BchGMC2Y.d.ts} +102 -11
  6. package/dist/_dts/{frac-B4hFJMiN.d.ts → frac-DTRq7Xpb.d.ts} +18 -10
  7. package/dist/_dts/inDialog-D5ZKGjiD.d.ts +23 -0
  8. package/dist/_dts/{index-DlKolEIZ.d.ts → index-B0--8PEM.d.ts} +2 -2
  9. package/dist/_dts/index-DINmCuLA.d.ts +453 -0
  10. package/dist/_dts/number.d-OeQYQoUO.d.ts +28 -0
  11. package/dist/_dts/{types-Db-3NNk1.d.ts → types-B7y8hX1D.d.ts} +2 -2
  12. package/dist/_dts/{usePanZoom-SSTZe410.d.ts → usePanZoom-Bq73pwFe.d.ts} +2 -2
  13. package/dist/_dts/{useTrialState-pnAE-UBD.d.ts → useTrialState-CP4jfuKl.d.ts} +1 -1
  14. package/dist/canvas/index.d.ts +6 -5
  15. package/dist/chrome/index.d.ts +5 -4
  16. package/dist/chrome/index.js +7 -7
  17. package/dist/chunk-2RVCB2PT.js +3 -0
  18. package/dist/chunk-2RVCB2PT.js.map +1 -0
  19. package/dist/{chunk-753Y6HE6.js → chunk-3FI7Y4R4.js} +4 -4
  20. package/dist/chunk-3FI7Y4R4.js.map +1 -0
  21. package/dist/{chunk-REOBGYMC.js → chunk-AWH3SRPW.js} +31 -5
  22. package/dist/chunk-AWH3SRPW.js.map +1 -0
  23. package/dist/{chunk-A2ACAGW6.js → chunk-BVL7Y574.js} +22 -5
  24. package/dist/chunk-BVL7Y574.js.map +1 -0
  25. package/dist/{chunk-CKUKF72R.js → chunk-H5VIV7MQ.js} +26 -22
  26. package/dist/chunk-H5VIV7MQ.js.map +1 -0
  27. package/dist/chunk-HFRE4QQV.js +400 -0
  28. package/dist/chunk-HFRE4QQV.js.map +1 -0
  29. package/dist/{chunk-LRI3MBGR.js → chunk-MOLIUNVH.js} +4 -4
  30. package/dist/{chunk-LRI3MBGR.js.map → chunk-MOLIUNVH.js.map} +1 -1
  31. package/dist/chunk-USO6TERP.js +90 -0
  32. package/dist/chunk-USO6TERP.js.map +1 -0
  33. package/dist/{chunk-EMXAIRUJ.js → chunk-VQ47KWD5.js} +3 -3
  34. package/dist/{chunk-EMXAIRUJ.js.map → chunk-VQ47KWD5.js.map} +1 -1
  35. package/dist/chunk-WUJXJC5O.js +14568 -0
  36. package/dist/chunk-WUJXJC5O.js.map +1 -0
  37. package/dist/{chunk-KJCUERQF.js → chunk-ZORVNOKX.js} +6 -25
  38. package/dist/chunk-ZORVNOKX.js.map +1 -0
  39. package/dist/config/index.d.ts +50 -7
  40. package/dist/config/index.js +5 -5
  41. package/dist/controls/index.d.ts +6 -3
  42. package/dist/controls/index.js +4 -4
  43. package/dist/dragdrop/index.d.ts +4 -3
  44. package/dist/index.d.ts +83 -102
  45. package/dist/index.js +430 -458
  46. package/dist/index.js.map +1 -1
  47. package/dist/layers/index.d.ts +6 -35
  48. package/dist/layers/index.js +2 -3
  49. package/dist/loupe/index.d.ts +6 -5
  50. package/dist/passthrough/weasel-ui.d.ts +407 -204
  51. package/dist/passthrough/weasel-ui.js +1 -1
  52. package/dist/primitives/index.d.ts +2 -13
  53. package/dist/primitives/index.js +4 -4
  54. package/dist/state/index.d.ts +6 -5
  55. package/dist/state/index.js +5 -5
  56. package/dist/styles.css +298 -174
  57. package/dist/undo/index.d.ts +4 -3
  58. package/package.json +9 -13
  59. package/src/annotations/frac.test.ts +7 -0
  60. package/src/annotations/paint.test.ts +21 -0
  61. package/src/annotations/paint.ts +12 -0
  62. package/src/annotations/store.test.ts +31 -0
  63. package/src/annotations/types.ts +3 -2
  64. package/src/canvas/Stage.test.tsx +18 -0
  65. package/src/chrome/regions/SidebarRegion.less +197 -0
  66. package/src/chrome/regions/SidebarRegion.test.tsx +20 -0
  67. package/src/chrome/regions/SidebarRegion.tsx +7 -2
  68. package/src/chrome/types.ts +5 -2
  69. package/src/config/autoConfig.ts +2 -2
  70. package/src/config/builder.ts +20 -1
  71. package/src/config/entry.test.ts +8 -0
  72. package/src/config/index.ts +2 -0
  73. package/src/config/rules.ts +2 -0
  74. package/src/config/sectionTree.test.ts +146 -0
  75. package/src/config/sectionTree.ts +124 -0
  76. package/src/controls/ControlPanel.less +4 -0
  77. package/src/controls/ControlPanel.stories.tsx +55 -2
  78. package/src/controls/ControlPanel.test.tsx +30 -3
  79. package/src/controls/ControlPanel.tsx +116 -337
  80. package/src/controls/inDialog.test.tsx +82 -0
  81. package/src/controls/inDialog.tsx +46 -0
  82. package/src/controls/index.ts +1 -0
  83. package/src/index.ts +11 -26
  84. package/src/instrument/types.ts +5 -2
  85. package/src/lab/Lab.nebula.test.tsx +31 -0
  86. package/src/lab/Lab.tsx +38 -15
  87. package/src/lab/LabBoundary.test.tsx +66 -0
  88. package/src/lab/LabBoundary.tsx +56 -0
  89. package/src/lab/LabHeader.tsx +4 -23
  90. package/src/lab/LabRoot.tsx +2 -3
  91. package/src/lab/LabShell.less +0 -1
  92. package/src/lab/LabShell.stories.tsx +15 -0
  93. package/src/lab/LabShell.test.tsx +18 -0
  94. package/src/lab/LabShell.tsx +14 -1
  95. package/src/lab/LabSwitcher.less +1 -1
  96. package/src/lab/LabSwitcher.test.tsx +41 -0
  97. package/src/lab/LabSwitcher.tsx +30 -8
  98. package/src/lab/index.ts +1 -0
  99. package/src/layers/AGENTS.md +18 -74
  100. package/src/layers/index.ts +4 -2
  101. package/src/passthrough/weasel-ui.ts +25 -10
  102. package/src/primitives/index.ts +0 -2
  103. package/src/specimen/Specimen.tsx +42 -56
  104. package/src/state/types.ts +2 -1
  105. package/src/styles.less +4 -2
  106. package/src/theme/Interstellar.stories.tsx +8 -9
  107. package/src/theme/base.less +3 -20
  108. package/src/theme/interstellar.test.ts +6 -3
  109. package/src/theme/interstellar.theme.json +0 -56
  110. package/src/theme/interstellar.ts +2 -2
  111. package/src/trial/Trial.canvas.test.tsx +1 -1
  112. package/src/trial/Trial.tsx +30 -8
  113. package/dist/_dts/index-8LiKe3A-.d.ts +0 -410
  114. package/dist/chunk-6BUQ77ZV.js +0 -18
  115. package/dist/chunk-6BUQ77ZV.js.map +0 -1
  116. package/dist/chunk-753Y6HE6.js.map +0 -1
  117. package/dist/chunk-A2ACAGW6.js.map +0 -1
  118. package/dist/chunk-CKUKF72R.js.map +0 -1
  119. package/dist/chunk-D35PFTS7.js +0 -170
  120. package/dist/chunk-D35PFTS7.js.map +0 -1
  121. package/dist/chunk-FKLT3ITC.js +0 -590
  122. package/dist/chunk-FKLT3ITC.js.map +0 -1
  123. package/dist/chunk-KJCUERQF.js.map +0 -1
  124. package/dist/chunk-LASCBWW7.js +0 -13456
  125. package/dist/chunk-LASCBWW7.js.map +0 -1
  126. package/dist/chunk-REOBGYMC.js.map +0 -1
  127. package/dist/ui/layers/index.d.ts +0 -66
  128. package/dist/ui/layers/index.js +0 -3
  129. package/dist/ui/layers/index.js.map +0 -1
  130. package/src/lab/useSystemMode.ts +0 -30
  131. package/src/layers/LayerList.less +0 -93
  132. package/src/layers/LayerList.stories.tsx +0 -66
  133. package/src/layers/LayerList.test.tsx +0 -232
  134. package/src/layers/LayerList.tsx +0 -268
  135. package/src/primitives/Sidebar.less +0 -112
  136. package/src/primitives/Sidebar.stories.tsx +0 -38
  137. package/src/primitives/Sidebar.test.tsx +0 -74
  138. package/src/primitives/Sidebar.tsx +0 -35
  139. package/src/ui/layers/index.ts +0 -5
@@ -9,6 +9,8 @@ const PAGES = [
9
9
  { href: '/ingest', label: 'Ingestion' },
10
10
  ];
11
11
 
12
+ const open2 = (name: string) =>
13
+ fireEvent.click(screen.getByRole('button', { name: new RegExp(name) }));
12
14
  const open = () => fireEvent.click(screen.getByRole('button', { name: /corpus stats/ }));
13
15
 
14
16
  describe('currentPage', () => {
@@ -27,6 +29,45 @@ describe('currentPage', () => {
27
29
  it('reports -1 for a page that is not one of them', () => {
28
30
  expect(currentPage('/bench', PAGES)).toBe(-1);
29
31
  });
32
+
33
+ // One document serving its pages from the hash: every href shares a path, so
34
+ // only the fragment can tell them apart.
35
+ const ROUTES = [
36
+ { href: '/app/', label: 'App' },
37
+ { href: '#/dev/tools', label: 'Tools' },
38
+ { href: '#/dev/registry', label: 'Registry' },
39
+ ];
40
+
41
+ it('tells hash routes on one document apart', () => {
42
+ expect(currentPage('/app/#/dev/registry', ROUTES)).toBe(2);
43
+ expect(currentPage('/app/#/dev/tools', ROUTES)).toBe(1);
44
+ });
45
+
46
+ it('matches a hash route through its own query and sub-routes', () => {
47
+ expect(currentPage('/app/#/dev/tools?bundle=minimal', ROUTES)).toBe(1);
48
+ expect(currentPage('/app/#/dev/tools/', ROUTES)).toBe(1);
49
+ expect(currentPage('/app/#/dev/registry/tool/rect', ROUTES)).toBe(2);
50
+ expect(currentPage('/app/#/dev/toolsets', ROUTES)).toBe(0);
51
+ });
52
+
53
+ it('falls back to the page without a hash when no route matches', () => {
54
+ expect(currentPage('/app/', ROUTES)).toBe(0);
55
+ expect(currentPage('/app/#/elsewhere', ROUTES)).toBe(0);
56
+ });
57
+
58
+ it('reads the hash off the current location by default', () => {
59
+ window.history.replaceState(null, '', '/app/#/dev/registry');
60
+ try {
61
+ render(<LabSwitcher title="Registry" pages={ROUTES} />);
62
+ open2('Registry');
63
+ expect(screen.getByRole('menuitem', { name: 'Registry' })).toHaveAttribute(
64
+ 'aria-current',
65
+ 'page',
66
+ );
67
+ } finally {
68
+ window.history.replaceState(null, '', '/');
69
+ }
70
+ });
30
71
  });
31
72
 
32
73
  describe('<LabSwitcher>', () => {
@@ -2,7 +2,8 @@ import { useCallback, useEffect, useId, useRef, useState } from 'react';
2
2
 
3
3
  /** One page in a project's set of labs. */
4
4
  export interface LabPage {
5
- /** The page's own URL, and how the switcher knows which page is open. */
5
+ /** The page's own URL, and how the switcher knows which page is open. A
6
+ * hash route (`#/dev/tools`) names a page on the current document. */
6
7
  href: string;
7
8
  label: string;
8
9
  }
@@ -18,18 +19,39 @@ export interface LabSwitcherProps {
18
19
  className?: string;
19
20
  }
20
21
 
22
+ /** A URL split into the document it names and the hash route on it, each
23
+ * without its query, trailing slash or `.html`. */
24
+ function locate(url: string): { doc: string; route: string } {
25
+ const hash = url.indexOf('#');
26
+ const trim = (part: string) => part.replace(/\?.*$/, '').replace(/\/$/, '');
27
+ return {
28
+ doc: trim(hash < 0 ? url : url.slice(0, hash)).replace(/\.html$/, ''),
29
+ route: hash < 0 ? '' : trim(url.slice(hash + 1)),
30
+ };
31
+ }
32
+
21
33
  /** Which of `pages` a path is on, or -1.
22
34
  *
23
35
  * Matched on the end of the path so a query string, a trailing slash or a
24
36
  * leftover `.html` cannot lose it: a project whose dev server maps `/stats`
25
37
  * to `stats.html` serves both spellings, and a bookmark from before the URLs
26
- * lost their extension still resolves to the same page. */
38
+ * lost their extension still resolves to the same page.
39
+ *
40
+ * A page whose `href` carries a hash is a route on a single document and wins
41
+ * when the path's hash is that route or one under it. A page without one
42
+ * ignores the hash, so an in-page anchor does not lose it. */
27
43
  export function currentPage(path: string, pages: readonly LabPage[]): number {
28
- const here = path
29
- .replace(/[?#].*$/, '')
30
- .replace(/\/$/, '')
31
- .replace(/\.html$/, '');
32
- return pages.findIndex((p) => here.endsWith(p.href.replace(/\.html$/, '')));
44
+ const here = locate(path);
45
+ const onDoc = (doc: string) => here.doc.endsWith(doc);
46
+ const routed = pages.findIndex((p) => {
47
+ if (!p.href.includes('#')) return false;
48
+ const page = locate(p.href);
49
+ return (
50
+ onDoc(page.doc) && (here.route === page.route || here.route.startsWith(`${page.route}/`))
51
+ );
52
+ });
53
+ if (routed >= 0) return routed;
54
+ return pages.findIndex((p) => !p.href.includes('#') && onDoc(locate(p.href).doc));
33
55
  }
34
56
 
35
57
  /** A project's lab title, doubling as the way to reach its other labs.
@@ -47,7 +69,7 @@ export function LabSwitcher({ title, pages, path, className }: LabSwitcherProps)
47
69
  const root = useRef<HTMLDivElement>(null);
48
70
  const menuId = useId();
49
71
  const here = currentPage(
50
- path ?? (typeof window === 'undefined' ? '' : window.location.pathname),
72
+ path ?? (typeof window === 'undefined' ? '' : window.location.pathname + window.location.hash),
51
73
  pages,
52
74
  );
53
75
 
package/src/lab/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type { LabProps } from './Lab';
2
2
  export { Lab } from './Lab';
3
+ export { LabBoundary, type LabBoundaryProps } from './LabBoundary';
3
4
  export type { LabContextValue } from './LabContext';
4
5
  export { LabContext, useLabContext } from './LabContext';
5
6
  export type { LabPaletteProps } from './LabPalette';
@@ -1,33 +1,11 @@
1
1
  # Layers — Agent Guide
2
2
 
3
- The `src/layers/` directory implements the `<LayerList>` UI used by the `layers` instrument capability.
3
+ `src/layers/` re-exports `<LayerList>` from `@weasel-js/ui`, along with the
4
+ `layers` capability types. The component itself — rows, cards, nesting,
5
+ selection, visibility, reorder — is documented at its source in
6
+ `packages/ui/src/components/LayerList/`.
4
7
 
5
- ## Files
6
-
7
- | File | Role |
8
- |---|---|
9
- | `LayerList.tsx` | Layer toggle/reorder list, flat or nested (sidebar widget) |
10
- | `LayerList.less` | Row styling, drag handle, pinned-row variant, subtree indent |
11
- | `LayerList.stories.tsx` | Flat and nested renderings |
12
-
13
- ## Props
14
-
15
- ```ts
16
- interface LayerListProps {
17
- layers: LayerTreeNode[]; // LayerDescriptor + { children?, defaultCollapsed? }
18
- visibility: Record<string, boolean>; // missing key → visible
19
- onReorder: (newOrder: LayerTreeNode[]) => void;
20
- onToggle: (id: string, visible: boolean) => void;
21
- collapsedIds?: readonly string[]; // supplying it makes collapse controlled
22
- onCollapsedChange?: (ids: string[]) => void;
23
- className?: string;
24
- }
25
- ```
26
-
27
- `LayerDescriptor` lives in `src/instrument/types.ts`; `LayerTreeNode` extends it
28
- in `LayerList.tsx`, so a plain `LayerDescriptor[]` is still a valid `layers`.
29
-
30
- ## How it integrates
8
+ ## How the capability uses it
31
9
 
32
10
  The instrument declares layer ids:
33
11
 
@@ -35,53 +13,19 @@ The instrument declares layer ids:
35
13
  layers: { ids: ['grid', 'plants'] }
36
14
  ```
37
15
 
38
- `Trial.tsx` converts each id into a `LayerDescriptor` (with `label === id` by default) and tracks `visibility` and a derived `layerOrder` in local state. The order produced by `onReorder` is then applied to `instrument.canvas.layers` before they're passed to `<CanvasStack>`.
39
-
40
- There is no transitive coupling between `layers.ids` and `canvas.layers[].id` — the trial assumes the ids match. If you declare a layer in `canvas` that isn't in `layers.ids`, it stays in default order and is always visible.
41
-
42
- ## `alwaysOn` semantics
43
-
44
- Setting `alwaysOn: true` on a `LayerDescriptor` does two things:
45
-
46
- 1. The row renders with a 🔒 badge instead of a drag handle and visibility checkbox.
47
- 2. The row is excluded from reorder operations — pinned rows always sort to the bottom of the rendered list.
48
-
49
- This is intended for legend/HUD layers that should never be toggled off. Visibility for pinned rows is **not** read from the `visibility` prop; they always render.
50
-
51
- ## Nesting
52
-
53
- A node with `children` renders an expandable subtree. The expand control is
54
- `<Disclosure>` from `@weasel-js/ui` — the same twisty every other collapsible
55
- surface uses.
56
-
57
- Reordering is scoped to siblings: a drag moves a row within its own parent's
58
- child list and never reparents it. `onReorder` still receives the whole tree,
59
- with only that sibling group's order changed.
60
-
61
- The twisty column appears only when some node in the tree has children — a flat
62
- list renders exactly the rows it always did. Within a tree, childless rows get
63
- `.lk-layer-list__twisty-gap` so labels stay aligned down a level.
64
-
65
- Collapse is uncontrolled by default, seeded once from each node's
66
- `defaultCollapsed`. Pass `collapsedIds` to own it; `onCollapsedChange` fires
67
- either way.
68
-
69
- ## Drag handle customization
70
-
71
- The drag handle is a button with class `lk-layer-list__handle` holding `<DragHandleGlyph>` (from `@weasel-js/ui`), the same grip `LayerStack` uses. Its padding is transparent and cancelled by an equal negative margin, so the grab target is larger than the drawn dots without widening the row. Row pitch — height plus row gap — is measured from the DOM when a drag starts, so restyling the row does not skew drag distance. It used to be a hardcoded `28`, which had already drifted from the rendered height by the time it was found.
72
-
73
- Pointer capture is acquired on `pointerdown` and released on `pointerup`, so dragging works across the document without requiring window-level listeners.
74
-
75
- ## Empty state
76
-
77
- If `layers.length === 0`, renders `.lk-layer-list__empty` with the text "No layers". Trial already guards this case (`layerDescriptors.length > 0`) so the empty state is rare in practice.
16
+ `Trial.tsx` turns each id into a `LayerDescriptor` (`label === id` unless the
17
+ entry is a descriptor), keeps `visibility` and `layerOrder` in local state, and
18
+ builds the list's items from them in the order the canvas draws. The order a
19
+ drag produces is applied to `instrument.canvas.layers` before they reach
20
+ `<CanvasStack>`, and each toggle and reorder emits `layers.toggle` /
21
+ `layers.reorder` on the bus.
78
22
 
79
- ## When to fork
23
+ `layers.ids` and `canvas.layers[].id` are assumed to match. A canvas layer not
24
+ in `layers.ids` keeps its default order and is always visible.
80
25
 
81
- Fork this component if you need:
82
- - Multi-select reorder (current implementation is single-row)
83
- - Right-click context menu
84
- - Reparenting by drag (nesting renders, but a drag stays within one parent)
85
- - Solo/mute UI common in DAW-style apps
26
+ ## `alwaysOn`
86
27
 
87
- It is self-contained: `LayerDescriptor` from instrument types and `Disclosure`/`DragHandleGlyph` from `@weasel-js/ui`.
28
+ A descriptor with `alwaysOn: true` becomes a `locked` item, pinned below the
29
+ rest: it shows a lock in place of the grip and has no visibility checkbox,
30
+ cannot be dragged, and no drag can cross it. It always renders, whatever
31
+ `visibility` says. Intended for legend and HUD layers.
@@ -1,3 +1,5 @@
1
+ // LayerList lives in `@weasel-js/ui`. Named, not `export *` — a star re-export
2
+ // of an external package emits no binding in the bundle.
3
+ export type { LayerListItem, LayerListProps, LayerMove } from '@weasel-js/ui';
4
+ export { LayerList, moveLayers } from '@weasel-js/ui';
1
5
  export type { LayerCapability, LayerDescriptor } from '../instrument/types';
2
- export type { LayerListProps, LayerTreeNode } from './LayerList';
3
- export { LayerList } from './LayerList';
@@ -8,11 +8,6 @@
8
8
  export {
9
9
  ActionBar,
10
10
  type ActionBarProps,
11
- ActionsBar,
12
- type ActionsBarItem,
13
- type ActionsBarProps,
14
- type ActionsBarSize,
15
- type ActionsBarVariant,
16
11
  type AddPointMode,
17
12
  type AnchorRenderProps,
18
13
  type AsyncOption,
@@ -22,12 +17,17 @@ export {
22
17
  type BadgeProps,
23
18
  type BadgeShape,
24
19
  type BadgeSize,
25
- type BadgeTone,
20
+ type BadgeStatus,
26
21
  type BadgeVariant,
27
22
  type BoundsCtx,
28
23
  type BuiltInEdgeName,
29
24
  type BuiltinPref,
30
25
  Button,
26
+ ButtonBar,
27
+ type ButtonBarItem,
28
+ type ButtonBarProps,
29
+ type ButtonBarSize,
30
+ type ButtonBarVariant,
31
31
  type ButtonProps,
32
32
  type ButtonSize,
33
33
  type ButtonVariant,
@@ -38,6 +38,8 @@ export {
38
38
  type CheckboxProps,
39
39
  type ChromaCurve,
40
40
  type ChromaCurvePoint,
41
+ ColorModeControl,
42
+ type ColorModeControlProps,
41
43
  ComboBox,
42
44
  type ComboBoxCommit,
43
45
  type ComboBoxFilter,
@@ -56,10 +58,13 @@ export {
56
58
  Dialog,
57
59
  type DialogProps,
58
60
  Disclosure,
59
- type DisclosureDirection,
61
+ DisclosureMark,
62
+ type DisclosureMarkProps,
60
63
  type DisclosureProps,
61
64
  DisclosureRow,
62
65
  type DisclosureRowProps,
66
+ DragGhost,
67
+ type DragGhostProps,
63
68
  DragHandleGlyph,
64
69
  type DragHandleGlyphProps,
65
70
  detectPlatform,
@@ -98,16 +103,20 @@ export {
98
103
  keyGlyph,
99
104
  keySpecFromKey,
100
105
  keySpecsFromMods,
106
+ keySpecsFromShortcut,
107
+ LayerList,
101
108
  type LayerListItem,
102
- LayerStack,
103
- type LayerStackItem,
104
- type LayerStackProps,
109
+ type LayerListProps,
110
+ type LayerMove,
111
+ ListEditor,
112
+ type ListEditorProps,
105
113
  type LogicalMod,
106
114
  type LogicalModSpec,
107
115
  MenuButton,
108
116
  type MenuButtonItem,
109
117
  type MenuButtonProps,
110
118
  MINUS_SIGN,
119
+ moveLayers,
111
120
  NumberField,
112
121
  type NumberFieldProps,
113
122
  OptionsBar,
@@ -145,6 +154,11 @@ export {
145
154
  type PrefRenderer,
146
155
  type PrefString,
147
156
  type PrefStringControl,
157
+ PrefsDialog,
158
+ type PrefsDialogProps,
159
+ PrefsForm,
160
+ type PrefsFormProps,
161
+ type PrefsLayout,
148
162
  paintGradientTrack,
149
163
  prefValueAtPath,
150
164
  Radio,
@@ -155,6 +169,7 @@ export {
155
169
  type RangeSliderProps,
156
170
  type ReorderDragHandlers,
157
171
  type ReorderDragState,
172
+ type ReorderGhost,
158
173
  type RovingItem,
159
174
  type RovingTabIndex,
160
175
  Select,
@@ -7,8 +7,6 @@ export type { LegendEntry, LegendMark, LegendProps } from './Legend';
7
7
  export { Legend } from './Legend';
8
8
  export type { ScaleIndicatorProps } from './ScaleIndicator';
9
9
  export { ScaleIndicator } from './ScaleIndicator';
10
- export type { SidebarProps } from './Sidebar';
11
- export { Sidebar } from './Sidebar';
12
10
  export type { SplitProps } from './Split';
13
11
  export { Split } from './Split';
14
12
  export type { StatusBarProps, StatusBarSectionProps } from './StatusBar';
@@ -1,15 +1,13 @@
1
1
  import type { FillStyle, GradientFill, Track } from '@weasel-js/core';
2
2
  import {
3
- ActionsBar,
4
3
  Badge,
5
4
  type Band,
6
5
  BandEditor,
7
6
  Button,
7
+ ButtonBar,
8
8
  Callout,
9
9
  Checkbox,
10
- CheckboxRow,
11
10
  ColorField,
12
- ColorRow,
13
11
  ComboBox,
14
12
  type ControlPoint,
15
13
  CurveEditor,
@@ -24,11 +22,11 @@ import {
24
22
  ItemList,
25
23
  KeyCap,
26
24
  KeySequence,
27
- LayerStack,
28
- type LayerStackItem,
25
+ LayerList,
26
+ type LayerListItem,
29
27
  MenuButton,
28
+ moveLayers,
30
29
  NumberField,
31
- NumberRow,
32
30
  OptionsBar,
33
31
  PaintInput,
34
32
  Plot2D,
@@ -36,6 +34,7 @@ import {
36
34
  Powerline,
37
35
  type PrefGroup,
38
36
  PrefsForm,
37
+ PropertyField,
39
38
  PropertyGroup,
40
39
  PropertyList,
41
40
  PropertyPanel,
@@ -44,10 +43,9 @@ import {
44
43
  RangeSlider,
45
44
  ResizeHandle,
46
45
  Select,
47
- SelectRow,
46
+ Sidebar,
48
47
  SidebarPanel,
49
48
  Slider,
50
- SliderRow,
51
49
  StatusBarItem,
52
50
  StatusBarSpacer,
53
51
  Switch,
@@ -55,18 +53,15 @@ import {
55
53
  TabList,
56
54
  TabPanel,
57
55
  Tabs,
58
- TextRow,
59
56
  type Thumb,
60
57
  Timeline,
61
58
  ToastRegion,
62
59
  ToggleBar,
63
- ToggleRow,
64
60
  ToolButton,
65
61
  ToolGroup,
66
62
  ToolOptionsBar,
67
63
  Tooltip,
68
64
  TooltipTrigger,
69
- Sidebar as UiSidebar,
70
65
  StatusBar as UiStatusBar,
71
66
  } from '@weasel-js/ui';
72
67
  import { type CSSProperties, type ReactNode, useId, useMemo, useRef, useState } from 'react';
@@ -76,7 +71,6 @@ import { FloatingPanel } from '../primitives/FloatingPanel';
76
71
  import { JobProgress } from '../primitives/JobProgress';
77
72
  import { Legend, type LegendEntry } from '../primitives/Legend';
78
73
  import { ScaleIndicator } from '../primitives/ScaleIndicator';
79
- import { Sidebar } from '../primitives/Sidebar';
80
74
  import { Split } from '../primitives/Split';
81
75
  import { StatusBar } from '../primitives/StatusBar';
82
76
  import { Toolbar } from '../primitives/Toolbar';
@@ -213,19 +207,19 @@ function ButtonsAndToggles() {
213
207
  </Cell>
214
208
  <Cell label="Badge">
215
209
  <div className="lk-specimen__row">
216
- <Badge shape="pill" tone="accent" variant="outline" size="sm">
210
+ <Badge shape="pill" status="accent" variant="outline" size="sm">
217
211
  ACCENT
218
212
  </Badge>
219
- <Badge tone="info" variant="subtle">
213
+ <Badge status="info" variant="subtle">
220
214
  INFO
221
215
  </Badge>
222
- <Badge tone="warn" variant="solid">
216
+ <Badge status="warn" variant="solid">
223
217
  WARN
224
218
  </Badge>
225
- <Badge tone="danger" variant="solid">
219
+ <Badge status="danger" variant="solid">
226
220
  DANGER
227
221
  </Badge>
228
- <Badge tone="muted">MUTED</Badge>
222
+ <Badge status="muted">MUTED</Badge>
229
223
  </div>
230
224
  </Cell>
231
225
  <Cell label="Checkbox, Switch">
@@ -269,7 +263,7 @@ function ButtonsAndToggles() {
269
263
  ]}
270
264
  />
271
265
  </Cell>
272
- <Cell label="OptionsBar, ActionsBar">
266
+ <Cell label="OptionsBar, ButtonBar">
273
267
  <div className="lk-specimen__row">
274
268
  <OptionsBar
275
269
  ariaLabel="Text style"
@@ -278,7 +272,7 @@ function ButtonsAndToggles() {
278
272
  { value: 'i', label: 'I', selected: italic, onChange: setItalic },
279
273
  ]}
280
274
  />
281
- <ActionsBar
275
+ <ButtonBar
282
276
  items={[
283
277
  { value: 'undo', label: 'Undo', onAction: () => {} },
284
278
  { value: 'redo', label: 'Redo', onAction: () => {} },
@@ -297,7 +291,6 @@ function ButtonsAndToggles() {
297
291
  ]}
298
292
  />
299
293
  <Disclosure open={open} onToggle={() => setOpen((o) => !o)} label="Shapes" />
300
- <Disclosure open={false} onToggle={() => {}} label="Down" direction="down" />
301
294
  </div>
302
295
  </Cell>
303
296
  <Cell label="Tabs">
@@ -436,7 +429,7 @@ function PanelsAndRows() {
436
429
  const [label, setLabel] = useState('Badge');
437
430
  const [visible, setVisible] = useState(true);
438
431
  const [prefs, setPrefs] = useState<Record<string, unknown>>({});
439
- const [layers, setLayers] = useState<LayerStackItem[]>([
432
+ const [layers, setLayers] = useState<LayerListItem[]>([
440
433
  { id: 'base', label: 'Base coat' },
441
434
  { id: 'wash', label: 'Wash' },
442
435
  ]);
@@ -446,7 +439,9 @@ function PanelsAndRows() {
446
439
  <Cell label="PropertyPanel">
447
440
  <PropertyPanel title="Shape">
448
441
  <PropertyList>
449
- <SliderRow
442
+ <PropertyField
443
+ kind="number"
444
+ control="slider"
450
445
  label="Opacity"
451
446
  value={opacity}
452
447
  min={0}
@@ -454,7 +449,9 @@ function PanelsAndRows() {
454
449
  step={0.01}
455
450
  onChange={setOpacity}
456
451
  />
457
- <SliderRow
452
+ <PropertyField
453
+ kind="number"
454
+ control="slider"
458
455
  label="Radius"
459
456
  value={radius}
460
457
  min={0}
@@ -462,16 +459,18 @@ function PanelsAndRows() {
462
459
  unit="px"
463
460
  onChange={setRadius}
464
461
  />
465
- <ColorRow
462
+ <PropertyField
463
+ kind="color"
466
464
  label="Fill"
467
465
  value={fill}
468
466
  onChange={setFill}
469
467
  alpha={alpha}
470
468
  onAlphaChange={setAlpha}
471
469
  />
472
- <ColorRow label="Stroke" value={stroke} onChange={setStroke} />
473
- <NumberRow label="Count" value={count} min={0} max={100} step={1} onChange={setCount} />
474
- <SelectRow
470
+ <PropertyField kind="color" label="Stroke" value={stroke} onChange={setStroke} />
471
+ <PropertyField kind="number" label="Count" value={count} min={0} max={100} step={1} onChange={setCount} />
472
+ <PropertyField
473
+ kind="enum"
475
474
  label="Line cap"
476
475
  value={cap}
477
476
  onChange={setCap}
@@ -480,7 +479,9 @@ function PanelsAndRows() {
480
479
  { value: 'round', label: 'Round' },
481
480
  ]}
482
481
  />
483
- <ToggleRow
482
+ <PropertyField
483
+ kind="enum"
484
+ control="toggle"
484
485
  label="Align"
485
486
  value={side}
486
487
  onChange={setSide}
@@ -490,10 +491,10 @@ function PanelsAndRows() {
490
491
  { value: 'right', label: 'R' },
491
492
  ]}
492
493
  />
493
- <TextRow label="Label" value={label} onChange={setLabel} />
494
- <CheckboxRow label="Visible" value={visible} onChange={setVisible} />
494
+ <PropertyField kind="string" label="Label" value={label} onChange={setLabel} />
495
+ <PropertyField kind="boolean" label="Visible" value={visible} onChange={setVisible} />
495
496
  <PropertyGroup title="Bevel" defaultCollapsed>
496
- <SliderRow label="Rings" value={32} min={4} max={96} step={1} onChange={() => {}} />
497
+ <PropertyField kind="number" control="slider" label="Rings" value={32} min={4} max={96} step={1} onChange={() => {}} />
497
498
  </PropertyGroup>
498
499
  </PropertyList>
499
500
  </PropertyPanel>
@@ -505,18 +506,16 @@ function PanelsAndRows() {
505
506
  onChange={(path, value) => setPrefs((prev) => setAt(prev, path, value))}
506
507
  />
507
508
  </Cell>
508
- <Cell label="LayerStack">
509
- <LayerStack
509
+ <Cell label="LayerList">
510
+ <LayerList
510
511
  items={layers}
511
- onReorder={(ids) =>
512
- setLayers(ids.flatMap((id) => layers.filter((item) => item.id === id)))
513
- }
512
+ onReorder={(move) => setLayers(moveLayers(layers, move))}
514
513
  renderBody={(item) => <div>settings for {item.label}</div>}
515
514
  />
516
515
  </Cell>
517
516
  <Cell label="Sidebar, SidebarPanel">
518
517
  <div className="lk-specimen__box">
519
- <UiSidebar side="right" ariaLabel="Specimen sidebar">
518
+ <Sidebar side="right" ariaLabel="Specimen sidebar">
520
519
  <SidebarPanel
521
520
  title="Selection"
522
521
  collapsed={panelCollapsed}
@@ -525,7 +524,7 @@ function PanelsAndRows() {
525
524
  >
526
525
  <div className="lk-specimen__pad">2 items selected</div>
527
526
  </SidebarPanel>
528
- </UiSidebar>
527
+ </Sidebar>
529
528
  </div>
530
529
  </Cell>
531
530
  </Section>
@@ -554,8 +553,8 @@ function OverlaysAndFeedback() {
554
553
  triggerRef={calloutAnchor}
555
554
  isOpen={calloutOpen}
556
555
  onOpenChange={setCalloutOpen}
557
- tone="info"
558
- title="Info"
556
+ stance="notice"
557
+ title="Notice"
559
558
  >
560
559
  Neutral guidance.
561
560
  </Callout>
@@ -587,9 +586,9 @@ function OverlaysAndFeedback() {
587
586
  variant="solid"
588
587
  size="sm"
589
588
  segments={[
590
- { text: 'main', tone: 'accent', endCap: 'chevron' },
591
- { text: '✓ 12', tone: 'info', endCap: 'slant' },
592
- { text: '~/proj', tone: 'muted' },
589
+ { text: 'main', status: 'accent', endCap: 'chevron' },
590
+ { text: '✓ 12', status: 'info', endCap: 'slant' },
591
+ { text: '~/proj', status: 'muted' },
593
592
  ]}
594
593
  />
595
594
  </Cell>
@@ -799,7 +798,6 @@ function EditorsAndPlots() {
799
798
  }
800
799
 
801
800
  function LabChrome() {
802
- const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
803
801
  const [zoom, setZoom] = useState(1);
804
802
  return (
805
803
  <Section title="Lab chrome">
@@ -823,18 +821,6 @@ function LabChrome() {
823
821
  <StatusBar.Section>Zoom: 100%</StatusBar.Section>
824
822
  </StatusBar>
825
823
  </Cell>
826
- <Cell label="Sidebar">
827
- <div className="lk-specimen__box">
828
- <div className="lk-specimen__box-main">main</div>
829
- <Sidebar
830
- title="Controls"
831
- collapsed={sidebarCollapsed}
832
- onToggle={() => setSidebarCollapsed((v) => !v)}
833
- >
834
- <p>One slider</p>
835
- </Sidebar>
836
- </div>
837
- </Cell>
838
824
  <Cell label="Legend, JobProgress">
839
825
  <Legend entries={LEGEND} />
840
826
  <JobProgress job={JOB} />
@@ -1,3 +1,4 @@
1
+ import type { ColorModePreference } from '@weasel-js/theme';
1
2
  import type { Auto } from '../config/auto';
2
3
  import type { ConfigPath, ResolvedConfig, ValueAtPath } from '../config/types';
3
4
  import type { InstrumentList } from '../instrument/types';
@@ -78,7 +79,7 @@ export interface SavedSnapshot {
78
79
  }
79
80
 
80
81
  /** `auto` follows the OS; the other two are an explicit choice. */
81
- export type LabMode = 'auto' | 'light' | 'dark';
82
+ export type LabMode = ColorModePreference;
82
83
 
83
84
  /** How much room the lab's own chrome takes — the theme's density axis, named
84
85
  * the way the theme names it. A lab's instruments are unaffected: this sizes
package/src/styles.less CHANGED
@@ -1,10 +1,12 @@
1
1
  // Imports compile-time only. Component .less files are added in later steps.
2
+ // Left as a CSS @import for the consumer's bundler to resolve; build-css.mjs
3
+ // swaps it for the faces themselves, pointed at dist/fonts/.
4
+ @import (css) '@weasel-js/theme/faces.css';
2
5
  @import './theme/base.less';
3
6
  @import './lab/Workspace.less';
4
7
  @import './lab/LabShell.less';
5
8
  @import './lab/LabSwitcher.less';
6
9
  @import './primitives/Toolbar.less';
7
- @import './primitives/Sidebar.less';
8
10
  @import './primitives/Split.less';
9
11
  @import './primitives/ZoomControl.less';
10
12
  @import './primitives/StatusBar.less';
@@ -17,11 +19,11 @@
17
19
  @import './trial/Trial.less';
18
20
  @import './chrome/regions/ViewportRegion.less';
19
21
  @import './chrome/regions/PaletteRegion.less';
22
+ @import './chrome/regions/SidebarRegion.less';
20
23
  @import './controls/ControlPanel.less';
21
24
  @import './canvas/CanvasStack.less';
22
25
  @import './canvas/Stage.less';
23
26
  @import './loupe/Loupe.less';
24
- @import './layers/LayerList.less';
25
27
  @import './annotations/Annotations.less';
26
28
  @import './dragdrop/Palette.less';
27
29
  @import './dragdrop/DragGhost.less';