@weasel-js/labkit 1.7.2 → 1.8.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 (60) hide show
  1. package/dist/_dts/{CanvasStackContext-Cjy6I-Iv.d.ts → CanvasStackContext-C0fzFsQg.d.ts} +1 -1
  2. package/dist/_dts/{Stage-D6t7o0pF.d.ts → Stage-Cvwxdnfp.d.ts} +2 -2
  3. package/dist/_dts/{index-BTmaRy6a.d.ts → index-DEHRoTXK.d.ts} +2 -2
  4. package/dist/_dts/{index-DCMA4Fy1.d.ts → index-DRaswz91.d.ts} +1 -1
  5. package/dist/_dts/{types-BUBSIZuV.d.ts → types-DUfQ0isU.d.ts} +5 -1
  6. package/dist/_dts/{useTrialState-Bh8bzFgE.d.ts → useTrialState-CS4TdWPW.d.ts} +1 -1
  7. package/dist/canvas/index.d.ts +5 -5
  8. package/dist/chrome/index.d.ts +3 -3
  9. package/dist/chrome/index.js +4 -4
  10. package/dist/{chunk-XRX5S5NT.js → chunk-3L4MLLQB.js} +4 -4
  11. package/dist/chunk-3L4MLLQB.js.map +1 -0
  12. package/dist/{chunk-WWJDPNZZ.js → chunk-7KAETQSS.js} +19 -19
  13. package/dist/chunk-7KAETQSS.js.map +1 -0
  14. package/dist/{chunk-YCFKW5RX.js → chunk-J5HJJDLF.js} +11 -2
  15. package/dist/chunk-J5HJJDLF.js.map +1 -0
  16. package/dist/{chunk-E6NARGU7.js → chunk-MOTDMMMN.js} +3 -3
  17. package/dist/{chunk-E6NARGU7.js.map → chunk-MOTDMMMN.js.map} +1 -1
  18. package/dist/{chunk-XZDCEMEL.js → chunk-NVKBL7ZE.js} +3 -3
  19. package/dist/{chunk-XZDCEMEL.js.map → chunk-NVKBL7ZE.js.map} +1 -1
  20. package/dist/chunk-WURZTJGQ.js +15756 -0
  21. package/dist/chunk-WURZTJGQ.js.map +1 -0
  22. package/dist/{chunk-IBVRMHW5.js → chunk-XXACAPVU.js} +3 -3
  23. package/dist/{chunk-IBVRMHW5.js.map → chunk-XXACAPVU.js.map} +1 -1
  24. package/dist/{chunk-RODIY7HI.js → chunk-YDE6XATS.js} +7 -7
  25. package/dist/{chunk-RODIY7HI.js.map → chunk-YDE6XATS.js.map} +1 -1
  26. package/dist/{chunk-WFALF2X2.js → chunk-YX75W6EK.js} +53 -16
  27. package/dist/chunk-YX75W6EK.js.map +1 -0
  28. package/dist/config/index.d.ts +12 -5
  29. package/dist/config/index.js +2 -2
  30. package/dist/controls/index.d.ts +2 -2
  31. package/dist/controls/index.js +3 -3
  32. package/dist/dragdrop/index.d.ts +2 -2
  33. package/dist/index.d.ts +10 -10
  34. package/dist/index.js +175 -189
  35. package/dist/index.js.map +1 -1
  36. package/dist/layers/index.d.ts +2 -2
  37. package/dist/layers/index.js +1 -1
  38. package/dist/loupe/index.d.ts +1 -1
  39. package/dist/overview/index.d.ts +1 -1
  40. package/dist/passthrough/weasel-ui.js +1 -1
  41. package/dist/primitives/index.js +3 -3
  42. package/dist/state/index.d.ts +4 -4
  43. package/dist/state/index.js +3 -3
  44. package/dist/styles.css +21 -9
  45. package/dist/undo/index.d.ts +2 -2
  46. package/package.json +11 -11
  47. package/src/config/builder.ts +11 -1
  48. package/src/config/types.ts +5 -1
  49. package/src/controls/ControlMatrix.less +7 -2
  50. package/src/controls/ControlMatrix.test.tsx +10 -0
  51. package/src/controls/ControlMatrix.tsx +58 -8
  52. package/src/controls/ControlPanel.test.tsx +30 -0
  53. package/src/styles.less +2 -0
  54. package/src/theme/interstellar.theme.json +0 -14
  55. package/dist/chunk-U6UQVGFP.js +0 -15583
  56. package/dist/chunk-U6UQVGFP.js.map +0 -1
  57. package/dist/chunk-WFALF2X2.js.map +0 -1
  58. package/dist/chunk-WWJDPNZZ.js.map +0 -1
  59. package/dist/chunk-XRX5S5NT.js.map +0 -1
  60. package/dist/chunk-YCFKW5RX.js.map +0 -1
@@ -1,9 +1,9 @@
1
- export { at as UndoCapability } from '../_dts/index-DCMA4Fy1.js';
1
+ export { at as UndoCapability } from '../_dts/index-DRaswz91.js';
2
2
  import '@weasel-js/ui';
3
3
  import '@weasel-js/core';
4
4
  import 'react';
5
5
  import '@weasel-js/theme';
6
- import '../_dts/types-BUBSIZuV.js';
6
+ import '../_dts/types-DUfQ0isU.js';
7
7
  import '@weasel-js/quantity';
8
8
  import '@weasel-js/geom';
9
9
  import '../_dts/types-DJ79Tg5J.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/labkit",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "React widgets for building self-contained interactive lab pages",
5
5
  "license": "MIT",
6
6
  "author": "orochi235",
@@ -115,18 +115,18 @@
115
115
  "dev:3d": "LABKIT_EXAMPLE=3d-lab vite"
116
116
  },
117
117
  "peerDependencies": {
118
- "@weasel-js/core": "1.7.2",
119
- "@weasel-js/theme": "1.7.2",
118
+ "@weasel-js/core": "1.8.0",
119
+ "@weasel-js/theme": "1.8.0",
120
120
  "react": "^19.0.0",
121
121
  "react-dom": "^19.0.0"
122
122
  },
123
123
  "dependencies": {
124
- "@weasel-js/geom": "1.7.2",
125
- "@weasel-js/kernel3d": "1.7.2",
126
- "@weasel-js/loupe": "1.7.2",
127
- "@weasel-js/quantity": "1.7.2",
128
- "@weasel-js/svg": "1.7.2",
129
- "@weasel-js/ui": "1.7.2",
124
+ "@weasel-js/geom": "1.8.0",
125
+ "@weasel-js/kernel3d": "1.8.0",
126
+ "@weasel-js/loupe": "1.8.0",
127
+ "@weasel-js/quantity": "1.8.0",
128
+ "@weasel-js/svg": "1.8.0",
129
+ "@weasel-js/ui": "1.8.0",
130
130
  "earcut": "3.2.3",
131
131
  "polygon-clipping": "^0.15.7",
132
132
  "react-aria-components": "^1.5.0",
@@ -136,8 +136,8 @@
136
136
  "devDependencies": {
137
137
  "@biomejs/biome": "^2.4.12",
138
138
  "@testing-library/jest-dom": "^7.0.1",
139
- "@weasel-js/guides": "1.7.2",
140
- "@weasel-js/modes": "1.7.2",
139
+ "@weasel-js/guides": "1.8.0",
140
+ "@weasel-js/modes": "1.8.0",
141
141
  "less": "^4.2.0",
142
142
  "rollup-plugin-dts": "^6.4.1",
143
143
  "tsx": "^4.19.0",
@@ -1,4 +1,4 @@
1
- import type { Display } from '@weasel-js/quantity';
1
+ import type { Display, InfinityText } from '@weasel-js/quantity';
2
2
  import type { PrefLeaf, PrefNumberUnit } from '@weasel-js/ui';
3
3
  import { type Auto, isAuto } from './auto';
4
4
  import type {
@@ -159,6 +159,16 @@ export class NumberNode extends BaseNode<number> {
159
159
  return this.ann({ unit });
160
160
  }
161
161
 
162
+ /**
163
+ * Make the top of the range mean infinity: the slider's `max` stop stores
164
+ * `Infinity` and reads `word` — `.range(0, 5000).endless('never')`. A value
165
+ * of `Infinity` sits at that stop. `at: 'min'` puts it at the bottom
166
+ * instead, as `-Infinity`; `'both'` does both.
167
+ */
168
+ endless(word?: InfinityText, at: 'min' | 'max' | 'both' = 'max'): this {
169
+ return this.ann(word === undefined ? { endless: at } : { endless: at, infinity: word });
170
+ }
171
+
162
172
  /** Force a slider even without both bounds. */
163
173
  slider(): this {
164
174
  return this.ann({ control: 'slider' });
@@ -1,4 +1,4 @@
1
- import type { Display } from '@weasel-js/quantity';
1
+ import type { Display, InfinityText } from '@weasel-js/quantity';
2
2
  import type { PrefGroup, PrefLeaf, PrefNumberUnit, PrefRenderer } from '@weasel-js/ui';
3
3
  import type { ReactNode } from 'react';
4
4
 
@@ -36,6 +36,10 @@ export interface Annotations {
36
36
  /** Display-unit conversion for a number stored in a canonical unit — the
37
37
  * value, its bounds and its step all convert at the control's edge. */
38
38
  unit?: PrefNumberUnit;
39
+ /** Which end of a number's slider stands for infinity. */
40
+ endless?: 'min' | 'max' | 'both';
41
+ /** The word a number's infinity shows as — `'never'`. Default `∞`. */
42
+ infinity?: InfinityText;
39
43
  /** A color leaf's value carries alpha (`#rrggbbaa`) and its row edits it. */
40
44
  alpha?: boolean;
41
45
  control?: string;
@@ -55,6 +55,8 @@
55
55
  border: 0;
56
56
  border-radius: var(--wzl-radius-sm);
57
57
  background: none;
58
+ backdrop-filter: none;
59
+ -webkit-backdrop-filter: none;
58
60
  color: inherit;
59
61
  font: inherit;
60
62
  letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wide));
@@ -96,8 +98,8 @@
96
98
  }
97
99
 
98
100
  // Inherited: the value in effect, but not chosen here.
99
- .lk-control-matrix__cell.is-inherited > * {
100
- opacity: 0.4;
101
+ .lk-control-matrix__cell.is-inherited {
102
+ .inherited();
101
103
  }
102
104
 
103
105
  // Every cell control fills its cell, so the cell is the click target.
@@ -112,6 +114,9 @@
112
114
  border: 1px solid transparent;
113
115
  border-radius: var(--wzl-radius-sm);
114
116
  background: none;
117
+ // labkit's default button blurs its backdrop; dozens of blurred cells make Chrome flicker.
118
+ backdrop-filter: none;
119
+ -webkit-backdrop-filter: none;
115
120
  color: inherit;
116
121
  font: inherit;
117
122
  cursor: pointer;
@@ -158,6 +158,16 @@ describe('<ControlMatrix>', () => {
158
158
  expect(setConfig).toHaveBeenCalledWith('looks.window.glow', 0.75);
159
159
  });
160
160
 
161
+ it('lists an enum cell’s options in its popover, with no dropdown inside, and closes on a choice', () => {
162
+ const { setConfig } = setup();
163
+ fireEvent.click(cell('Window Shading'));
164
+ const dialog = screen.getByRole('dialog', { name: 'Window Shading' });
165
+ expect(within(dialog).queryByRole('button', { name: /Shading/ })).toBeNull();
166
+ fireEvent.click(within(dialog).getByRole('radio', { name: 'Soft' }));
167
+ expect(setConfig).toHaveBeenCalledWith('looks.window.shading', 'soft');
168
+ expect(screen.queryByRole('dialog')).toBeNull();
169
+ });
170
+
161
171
  it('unpins from the popover with Inherit, disabled while already inherited', () => {
162
172
  const { setConfig, unmount } = setup();
163
173
  fireEvent.click(cell('Window Glow'));
@@ -5,6 +5,7 @@ import {
5
5
  isPrefLeaf,
6
6
  type PrefLeaf,
7
7
  type PropertyControlProps,
8
+ PropertyField,
8
9
  PropertyHelp,
9
10
  PropertyList,
10
11
  PropertyPanel,
@@ -211,16 +212,12 @@ export function ControlMatrix({
211
212
  aria-label={`${nameOf(open.column)} ${open.label}`}
212
213
  >
213
214
  <PropertyList pack="one-up">
214
- <ControlRow
215
- path={open.path}
216
- leaf={open.leaf}
217
- resolved={schema}
215
+ <Editor
216
+ open={open}
217
+ schema={schema}
218
218
  config={config}
219
- shown={config}
220
219
  setConfig={setConfig}
221
- pack="one-up"
222
- auto={NO_AUTO}
223
- setRowAuto={noop}
220
+ close={() => setOpen(null)}
224
221
  />
225
222
  </PropertyList>
226
223
  {clearable(open.leaf) ? (
@@ -262,6 +259,59 @@ export function ControlMatrix({
262
259
  );
263
260
  }
264
261
 
262
+ /**
263
+ * The control a cell opens. An enum lists its options outright, as radios: the
264
+ * panel's dropdown would be a second popup opened from inside this one.
265
+ * Choosing one is the whole edit, so it closes the popover.
266
+ */
267
+ function Editor({
268
+ open,
269
+ schema,
270
+ config,
271
+ setConfig,
272
+ close,
273
+ }: {
274
+ open: OpenCell;
275
+ schema: ResolvedConfig;
276
+ config: Record<string, unknown>;
277
+ setConfig: (path: string, value: unknown) => void;
278
+ close: () => void;
279
+ }) {
280
+ const { path, leaf } = open;
281
+ const field = isBuiltinToolPref(leaf)
282
+ ? prefFieldProps(leaf, {
283
+ value: valueAtPath(config, path) ?? leaf.default,
284
+ setValue: (next) => setConfig(path, next),
285
+ })
286
+ : null;
287
+ if (field?.kind === 'enum') {
288
+ return (
289
+ <PropertyField
290
+ {...field}
291
+ label={leaf.name}
292
+ control="radio"
293
+ onChange={(next: Parameters<typeof field.onChange>[0]) => {
294
+ field.onChange(next);
295
+ close();
296
+ }}
297
+ />
298
+ );
299
+ }
300
+ return (
301
+ <ControlRow
302
+ path={path}
303
+ leaf={leaf}
304
+ resolved={schema}
305
+ config={config}
306
+ shown={config}
307
+ setConfig={setConfig}
308
+ pack="one-up"
309
+ auto={NO_AUTO}
310
+ setRowAuto={noop}
311
+ />
312
+ );
313
+ }
314
+
265
315
  function ColumnHeader({
266
316
  column,
267
317
  onClick,
@@ -1188,3 +1188,33 @@ describe('<ControlPanel> resettable group', () => {
1188
1188
  ]);
1189
1189
  });
1190
1190
  });
1191
+
1192
+ describe('<ControlPanel> endless number', () => {
1193
+ it('reads its word at the top of the range and writes Infinity there', () => {
1194
+ const schema = resolveConfigSchema(
1195
+ f.schema({ cut: f.number(Infinity).range(0, 5000).step(50).suffix('ms').endless('never') }),
1196
+ [],
1197
+ );
1198
+ const setConfig = vi.fn();
1199
+ function Harness() {
1200
+ const [config, setState] = useState<Record<string, unknown>>({ cut: Infinity });
1201
+ return (
1202
+ <ControlPanel
1203
+ schema={schema}
1204
+ config={config}
1205
+ setConfig={(key: string, value: unknown) => {
1206
+ setConfig(key, value);
1207
+ setState((c) => ({ ...c, [key]: value }));
1208
+ }}
1209
+ />
1210
+ );
1211
+ }
1212
+ render(<Harness />);
1213
+ expect(screen.getByRole('spinbutton')).toHaveValue('never');
1214
+ expect(screen.queryByText('ms')).toBeNull();
1215
+ fireEvent.change(screen.getByRole('slider'), { target: { value: '4950' } });
1216
+ expect(setConfig).toHaveBeenLastCalledWith('cut', 4950);
1217
+ fireEvent.change(screen.getByRole('slider'), { target: { value: '5000' } });
1218
+ expect(setConfig).toHaveBeenLastCalledWith('cut', Infinity);
1219
+ });
1220
+ });
package/src/styles.less CHANGED
@@ -4,6 +4,8 @@
4
4
  @import (css) '@weasel-js/theme/faces.css';
5
5
  // The numeric face's one definition, taken as a mixin: `.numeric();`.
6
6
  @import (reference, less) '@weasel-js/theme/numeric.module.css';
7
+ // What an inherited value draws in, the same way: `.inherited();`.
8
+ @import (reference, less) '@weasel-js/theme/inherited.module.css';
7
9
  @import './theme/base.less';
8
10
  @import './lab/Workspace.less';
9
11
  @import './lab/LabShell.less';
@@ -64,20 +64,6 @@
64
64
  "type": "color"
65
65
  }
66
66
  },
67
- "fg-muted": {
68
- "by": "mode",
69
- "dark": {
70
- "value": "#a8a8c0",
71
- "type": "color"
72
- }
73
- },
74
- "fg-subtle": {
75
- "by": "mode",
76
- "dark": {
77
- "value": "#5a5a78",
78
- "type": "color"
79
- }
80
- },
81
67
  "accent": {
82
68
  "by": "mode",
83
69
  "dark": {