@kitlangton/knobs 0.4.0-next.12 → 0.4.0-next.14

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.
@@ -5046,7 +5046,8 @@ function ancestors(index, id) {
5046
5046
  out.push(p);
5047
5047
  return out;
5048
5048
  }
5049
- function navigate(rows, active, key, page = 10) {
5049
+ function navigate(rows, active, pressed, page = 10, dir = "ltr") {
5050
+ const key = dir === "rtl" && (pressed === "ArrowLeft" || pressed === "ArrowRight") ? pressed === "ArrowLeft" ? "ArrowRight" : "ArrowLeft" : pressed;
5050
5051
  if (rows.length === 0)
5051
5052
  return { active: null };
5052
5053
  const i = active === null ? -1 : rows.findIndex((r) => r.id === active);
@@ -5929,7 +5930,8 @@ function tree(container, options) {
5929
5930
  options.onMove(move);
5930
5931
  } else if (nav.includes(e.key) && !e.metaKey && !e.ctrlKey) {
5931
5932
  const page = Math.max(1, Math.floor(viewport() / h) - 1);
5932
- const r = navigate(rows, active, e.key, page);
5933
+ const dir = win.getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr";
5934
+ const r = navigate(rows, active, e.key, page, dir);
5933
5935
  if (r.toggle)
5934
5936
  toggle(r.toggle.id, r.toggle.expanded);
5935
5937
  else if (r.active) {
@@ -7643,7 +7645,7 @@ function presence(el, o = {}) {
7643
7645
  });
7644
7646
  const tune = (seconds, zeta) => {
7645
7647
  const reduced = reducedMotion(win);
7646
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
7648
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
7647
7649
  spring.zeta = reduced ? 1 : zeta;
7648
7650
  };
7649
7651
  render();
@@ -5045,7 +5045,8 @@ function ancestors(index, id) {
5045
5045
  out.push(p);
5046
5046
  return out;
5047
5047
  }
5048
- function navigate(rows, active, key, page = 10) {
5048
+ function navigate(rows, active, pressed, page = 10, dir = "ltr") {
5049
+ const key = dir === "rtl" && (pressed === "ArrowLeft" || pressed === "ArrowRight") ? pressed === "ArrowLeft" ? "ArrowRight" : "ArrowLeft" : pressed;
5049
5050
  if (rows.length === 0)
5050
5051
  return { active: null };
5051
5052
  const i = active === null ? -1 : rows.findIndex((r) => r.id === active);
@@ -5928,7 +5929,8 @@ function tree(container, options) {
5928
5929
  options.onMove(move);
5929
5930
  } else if (nav.includes(e.key) && !e.metaKey && !e.ctrlKey) {
5930
5931
  const page = Math.max(1, Math.floor(viewport() / h) - 1);
5931
- const r = navigate(rows, active, e.key, page);
5932
+ const dir = win.getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr";
5933
+ const r = navigate(rows, active, e.key, page, dir);
5932
5934
  if (r.toggle)
5933
5935
  toggle(r.toggle.id, r.toggle.expanded);
5934
5936
  else if (r.active) {
@@ -7642,7 +7644,7 @@ function presence(el, o = {}) {
7642
7644
  });
7643
7645
  const tune = (seconds, zeta) => {
7644
7646
  const reduced = reducedMotion(win);
7645
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
7647
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
7646
7648
  spring.zeta = reduced ? 1 : zeta;
7647
7649
  };
7648
7650
  render();
package/dist/review.js CHANGED
@@ -3769,7 +3769,7 @@ function presence(el, o = {}) {
3769
3769
  });
3770
3770
  const tune = (seconds, zeta) => {
3771
3771
  const reduced = reducedMotion(win);
3772
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
3772
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
3773
3773
  spring.zeta = reduced ? 1 : zeta;
3774
3774
  };
3775
3775
  render();
@@ -10203,7 +10203,8 @@ function ancestors(index, id) {
10203
10203
  out.push(p);
10204
10204
  return out;
10205
10205
  }
10206
- function navigate(rows, active, key, page = 10) {
10206
+ function navigate(rows, active, pressed, page = 10, dir = "ltr") {
10207
+ const key = dir === "rtl" && (pressed === "ArrowLeft" || pressed === "ArrowRight") ? pressed === "ArrowLeft" ? "ArrowRight" : "ArrowLeft" : pressed;
10207
10208
  if (rows.length === 0)
10208
10209
  return { active: null };
10209
10210
  const i = active === null ? -1 : rows.findIndex((r) => r.id === active);
@@ -11086,7 +11087,8 @@ function tree(container, options) {
11086
11087
  options.onMove(move);
11087
11088
  } else if (nav.includes(e.key) && !e.metaKey && !e.ctrlKey) {
11088
11089
  const page = Math.max(1, Math.floor(viewport() / h) - 1);
11089
- const r = navigate(rows, active, e.key, page);
11090
+ const dir = win.getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr";
11091
+ const r = navigate(rows, active, e.key, page, dir);
11090
11092
  if (r.toggle)
11091
11093
  toggle(r.toggle.id, r.toggle.expanded);
11092
11094
  else if (r.active) {
@@ -136,8 +136,12 @@ export interface RemoteControlsOptions {
136
136
  readonly nullDefaults?: boolean;
137
137
  readonly values?: State;
138
138
  readonly presets?: Readonly<Record<string, State>>;
139
+ /** Presets that belong to one tab or group (see `ControlsOptions.scopedPresets`). */
140
+ readonly scopedPresets?: Readonly<Record<string, Readonly<Record<string, State>>>>;
139
141
  /** Groups pinned above the tab strip, outside the presets (see `ControlsOptions.pinned`). */
140
142
  readonly pinned?: readonly string[];
143
+ /** Groups presets leave alone, in place (see `ControlsOptions.inPresets`). */
144
+ readonly inPresets?: Readonly<Record<string, boolean>>;
141
145
  /** Numbered presets per group, e.g. the phone's preset sets (see `ControlsOptions.groupPresets`). */
142
146
  readonly groupPresets?: Readonly<Record<string, readonly GroupPreset[]>>;
143
147
  /** Display labels by group id, section id and preset id (see `ControlsOptions.groupLabels`). */
@@ -145,7 +149,7 @@ export interface RemoteControlsOptions {
145
149
  readonly sectionLabels?: Readonly<Record<string, string>>;
146
150
  readonly presetLabels?: Readonly<Record<string, string>>;
147
151
  /** Shorthand for `presetLabels.default`, e.g. "Native". */
148
- readonly defaultLabel?: string;
152
+ readonly defaultLabel?: string | Readonly<Record<string, string>>;
149
153
  /** How a page card shows its group presets (see `ControlsOptions.groupPresetDisplay`). */
150
154
  readonly groupPresetDisplay?: GroupPresetDisplay;
151
155
  /** One muted line under a group's card title or a section's heading on a page, by id. */
@@ -56,6 +56,11 @@ export interface FieldMeta {
56
56
  * Panel presets and saved variants leave pinned fields alone. See `ControlsOptions.pinned`.
57
57
  */
58
58
  readonly pinned?: boolean;
59
+ /**
60
+ * `false`: presets, scoped presets and variants leave the field alone, as they leave a pinned
61
+ * one, but its row stays where it is. See `ControlsOptions.inPresets` for a whole group.
62
+ */
63
+ readonly inPresets?: false;
59
64
  /**
60
65
  * A collapsible sub-heading inside the field's group: a flat heading with a hairline, never a
61
66
  * card inside the card. Consecutive fields that share it sit under one heading.
@@ -63,7 +68,7 @@ export interface FieldMeta {
63
68
  readonly subsection?: string;
64
69
  }
65
70
  /** Meta keys copied from a field's options or a descriptor onto the field. */
66
- export declare const META_KEYS: readonly ["label", "group", "groupLabel", "section", "sectionLabel", "groupDescription", "sectionDescription", "shortcut", "random", "disabled", "readOnly", "folded", "sectionFolded", "tab", "pinned", "subsection"];
71
+ export declare const META_KEYS: readonly ["label", "group", "groupLabel", "section", "sectionLabel", "groupDescription", "sectionDescription", "shortcut", "random", "disabled", "readOnly", "folded", "sectionFolded", "tab", "pinned", "inPresets", "subsection"];
67
72
  /** The meta `source` gives, without undefined keys. */
68
73
  export declare function pickMeta(source: FieldMeta | undefined, skip?: readonly string[]): FieldMeta;
69
74
  /** Marks a knob as half of a flat spring: two number keys drawn as one spring card. */
@@ -365,6 +370,11 @@ export declare function variantsOf(schema: Schema, isPinned?: (key: string) => b
365
370
  * or whose first field that gives `pinned` says `true`, and ungrouped fields that say so themselves.
366
371
  */
367
372
  export declare function pinnedKeys(schema: Schema, groups?: readonly string[]): ReadonlySet<string>;
373
+ /**
374
+ * The keys presets leave alone: the pinned ones, fields that say `inPresets: false`, and every key
375
+ * of a group `groups` gives `false`.
376
+ */
377
+ export declare function presetFreeKeys(schema: Schema, pinned: ReadonlySet<string>, groups?: Readonly<Record<string, boolean>>): ReadonlySet<string>;
368
378
  export type FieldValue<F extends Field> = F extends Knob ? number : F extends Choice<infer T> ? T : F extends Toggle ? boolean : F extends SpringField ? SpringValue : F extends ColorField ? string : F extends PointField ? Point : F extends EasingField ? Bezier : F extends VecField<3> ? Vec3 : F extends VecField<2> ? Point : F extends RangeField ? readonly [number, number] : F extends AngleField | SeedField ? number : F extends KeybindField | TextField ? string : F extends GradientField ? readonly GradientStop[] : F extends CurveField ? readonly CurvePoint[] : never;
369
379
  /** The keys of a schema's readouts (what `controls.watch` takes). */
370
380
  export type ReadoutKeys<S extends Schema> = {
@@ -494,6 +504,7 @@ export declare function range(value: readonly [number, number], options: FieldMe
494
504
  sectionFolded?: boolean;
495
505
  tab?: string;
496
506
  pinned?: boolean;
507
+ inPresets?: false;
497
508
  subsection?: string;
498
509
  };
499
510
  /** Degrees on a dial (0–360 and wrapping by default): `angle(45)`, `angle(30, { min: -90, max: 90 })`. */
@@ -50,6 +50,19 @@ export interface PresetRef {
50
50
  readonly kind: "preset" | "variant";
51
51
  readonly id: string;
52
52
  }
53
+ /**
54
+ * A preset strip that belongs to one tab or group (see `ControlsOptions.scopedPresets`): its
55
+ * presets and saved variants move only its own keys, and it keeps its own active preset.
56
+ */
57
+ export interface PresetScope<V = State> {
58
+ /** The tab or group it's named for. */
59
+ readonly id: string;
60
+ /** The keys it moves: the tab's or group's, pinned ones aside. */
61
+ readonly keys: readonly string[];
62
+ /** By id, `default` first; keys outside the scope sit at their defaults. */
63
+ readonly presets: Readonly<Record<string, V>>;
64
+ readonly variants: Variants<V>;
65
+ }
53
66
  export interface ControlAction<V = State> {
54
67
  readonly id?: string;
55
68
  readonly label: string;
@@ -76,8 +89,11 @@ export interface LabelOptions {
76
89
  readonly sectionLabels?: Readonly<Record<string, string>>;
77
90
  /** By preset id: what the preset strip shows, e.g. `{ default: "Native" }`. */
78
91
  readonly presetLabels?: Readonly<Record<string, string>>;
79
- /** Shorthand for `presetLabels.default`. */
80
- readonly defaultLabel?: string;
92
+ /**
93
+ * Shorthand for `presetLabels.default`; by scope, `{ "": "Empty", Motion: "Shipped" }` names each
94
+ * strip's default chip on its own (`""` is the panel's, a tab or group's id one of `scopedPresets`).
95
+ */
96
+ readonly defaultLabel?: string | Readonly<Record<string, string>>;
81
97
  /** How a page card shows its group presets (see `GroupPresetDisplay`). */
82
98
  readonly groupPresetDisplay?: GroupPresetDisplay;
83
99
  /** By group id: one muted line under its card's title on a page, over fields' `groupDescription`. */
@@ -144,9 +160,14 @@ export interface MountPanelOptions extends LabelOptions {
144
160
  * and knobs/ui piece on the page (and the host's `data-hint` elements), so pressed here or
145
161
  * anywhere outside a text field it jumps across all of them; with nothing else on the page it's
146
162
  * this panel's alone, as always. `"panel"`: `s` here labels only this panel's controls, and they
147
- * don't join the page's. `keys: { hint: false }` turns hint mode off here either way.
163
+ * don't join the page's. `keys: { hint: false }` turns hint mode off here either way. As an
164
+ * object, `reach` is that choice and `digits: "always"` keeps the preset strips' digits on their
165
+ * chips all the time, quietly, rather than only while you use keys.
148
166
  */
149
- readonly hints?: "page" | "panel";
167
+ readonly hints?: "page" | "panel" | {
168
+ readonly reach?: "page" | "panel";
169
+ readonly digits?: "keys" | "always";
170
+ };
150
171
  /**
151
172
  * The key that shows and hides every panel and chip on the page that listens for it, outside
152
173
  * text fields and without modifiers; remembered per store. The store's `toggleKey` by default
@@ -169,8 +190,9 @@ export interface MountPanelOptions extends LabelOptions {
169
190
  */
170
191
  readonly header?: boolean | HeaderOptions;
171
192
  /**
172
- * `false` drops the scope line beside the title (`Shape › Cohesion`, where keys go, and a faint
173
- * `esc ↑ Shape` for where Esc goes). The focus lens and the armed frame stay.
193
+ * `true` shows a scope line beside the title (`Shape › Cohesion`, where keys go, and a faint
194
+ * `esc ↑ Shape` for where Esc goes). Off by default; the focus lens and the armed frame show
195
+ * either way.
174
196
  */
175
197
  readonly crumbs?: boolean;
176
198
  /**
@@ -355,12 +377,27 @@ export interface ControlsOptions<S extends Schema> extends LabelOptions {
355
377
  /** An undo stack the host owns; undo and redo call it (see `HostHistory`). */
356
378
  history?: HostHistory;
357
379
  presets?: Readonly<Record<string, Partial<Values<S>>>>;
380
+ /**
381
+ * Presets that belong to one tab or group, keyed by its id: `{ Sidebars: { snappy: {…} } }`.
382
+ * Each shows its own strip on that tab (a tab without any shows none), moves only that tab's
383
+ * or group's keys, and has its own active preset, modified mark and saved variants. A variants
384
+ * choice on a tab adds its options to that tab's. `presets` then covers only the keys no scope
385
+ * holds; leave it out and only the scoped strips show.
386
+ */
387
+ scopedPresets?: Readonly<Record<string, Readonly<Record<string, Partial<Values<S>>>>>>;
358
388
  /**
359
389
  * Groups pinned to the top of a panel, under the header and above the tab strip, on every tab
360
390
  * (as fields' `pinned: true` pins theirs). Presets and saved variants leave them alone: applying
361
391
  * one keeps their values, saving one keeps them out, and they're modified against their defaults.
362
392
  */
363
393
  pinned?: readonly string[];
394
+ /**
395
+ * Groups presets leave alone, where they are: `{ Motion: false }`. As with pinned groups,
396
+ * applying a preset or variant keeps their values, saving one leaves them out, and a preset's chip
397
+ * doesn't drift when they change; but their rows stay in place. A field says it alone with
398
+ * `inPresets: false`.
399
+ */
400
+ inPresets?: Readonly<Record<string, boolean>>;
364
401
  /**
365
402
  * Numbered presets per group, shown as 1, 2, 3… on the group's card in a page. A group's
366
403
  * presets cover every key any of them names; keys a preset leaves out are at their defaults,
@@ -408,6 +445,13 @@ export interface Controls<S extends Schema = Schema> {
408
445
  readonly persist: PersistMode;
409
446
  readonly presets: Readonly<Record<string, Values<S>>>;
410
447
  readonly variants: Variants<Values<S>>;
448
+ /** The tabs and groups with presets of their own, by id (see `ControlsOptions.scopedPresets`). */
449
+ readonly scopes: Readonly<Record<string, PresetScope<Values<S>>>>;
450
+ /**
451
+ * The scope `key`'s presets come from: a tab or group's id, `""` for `presets`, or undefined
452
+ * when presets leave it alone (pinned, `inPresets: false`).
453
+ */
454
+ scopeOf(key: string): string | undefined;
411
455
  readonly actions: readonly ControlAction<Values<S>>[];
412
456
  /** Its panels' theme, as given (see `ControlsOptions.theme`). */
413
457
  readonly theme: Theme | ThemeMode | undefined;
@@ -437,10 +481,12 @@ export interface Controls<S extends Schema = Schema> {
437
481
  readonly groupFolded: Readonly<Record<string, boolean>> | undefined;
438
482
  /** How a page's sections start (`sectionFolded`), as given. */
439
483
  readonly sectionFolded: SectionFolded | undefined;
440
- /** A preset's display label (`presetLabels`, `defaultLabel`), else its id. */
441
- presetLabel(id: string): string;
484
+ /** A preset's display label (`presetLabels`, `defaultLabel`), else its id; `scope` as in `preset`. */
485
+ presetLabel(id: string, scope?: string): string;
442
486
  /** Whether `key` is pinned (its group is, see `ControlsOptions.pinned`), outside the presets. */
443
487
  isPinned(key: string): boolean;
488
+ /** Whether presets move `key`: false when it's pinned or says `inPresets: false` (or its group does). */
489
+ inPresets(key: string): boolean;
444
490
  readonly error: string | null;
445
491
  get(): Values<S>;
446
492
  getServerSnapshot(): Values<S>;
@@ -555,17 +601,21 @@ export interface Controls<S extends Schema = Schema> {
555
601
  readonly comparing: boolean;
556
602
  /**
557
603
  * Applies a preset and makes it the active one (`"default"` clears it); one undo step, or
558
- * folded into the last with `coalesce` (a pan across the strip).
604
+ * folded into the last with `coalesce` (a pan across the strip). With `scope`, one of that tab
605
+ * or group's presets, which moves only its keys.
559
606
  */
560
607
  preset(id: string, options?: {
561
608
  coalesce?: boolean;
609
+ scope?: string;
562
610
  }): void;
563
- matchingPreset(): string;
611
+ /** The preset the current values match (in `scope`, over its keys), else `"custom"`. */
612
+ matchingPreset(scope?: string): string;
564
613
  /**
565
614
  * The preset (or saved variant) last applied, which Reset returns to and Random explores
566
- * around, or null. Kept with the values; forgotten by a reset to the defaults.
615
+ * around, or null. Kept with the values; forgotten by a reset to the defaults. With `scope`,
616
+ * that tab or group's.
567
617
  */
568
- activePreset(): PresetRef | null;
618
+ activePreset(scope?: string): PresetRef | null;
569
619
  /** The group preset last applied to `group` (0-based), or -1. */
570
620
  activeGroupPreset(group: string): number;
571
621
  /** The values Reset returns to first: the active presets over the defaults. */
@@ -22,4 +22,6 @@ export interface Variants<V> {
22
22
  export(): string;
23
23
  import(json: string): void;
24
24
  }
25
- export declare function createVariants<S extends Schema>(schema: () => S, id: string, key: string, storage: () => StoragePort | null, get: () => Values<S>, set: (v: Values<S>, id: string, coalesce: boolean) => void, notify: () => void, enabled: () => boolean): Variants<Values<S>>;
25
+ export declare function createVariants<S extends Schema>(schema: () => S, id: string, key: string, storage: () => StoragePort | null, get: () => Values<S>, set: (v: Values<S>, id: string, coalesce: boolean) => void, notify: () => void, enabled: () => boolean,
26
+ /** What the list starts as while storage holds none yet (it's kept once something's saved). */
27
+ seed?: () => readonly Variant<Values<S>>[]): Variants<Values<S>>;
@@ -434,6 +434,8 @@ export interface GpuPanelBinding {
434
434
  /** Each locked-capable row's lock icon, by its label element; `data-on` says it's locked. */
435
435
  readonly locks?: ReadonlyMap<HTMLElement, HTMLElement>;
436
436
  readonly presetStrip: PresetStrip;
437
+ /** Groups' own preset strips, each at the top of its card (see `scopedPresets`). */
438
+ readonly groupStrips?: () => Iterable<PresetStrip>;
437
439
  /** The tab strip under the header (hidden while the panel has no tabs). */
438
440
  readonly tabs?: TabStrip;
439
441
  readonly groups: readonly GpuGroupBinding[];
@@ -606,6 +608,12 @@ declare class GpuOverlayManager {
606
608
  private initWebGpu;
607
609
  private setAllPanelsActive;
608
610
  private pushPrim;
611
+ /**
612
+ * A preset strip: its tray (`tray`: the panel's strip has one, a group card's sits on the card),
613
+ * the selected chip's pill on its springs, the chips' names and drift marks, and the save chip.
614
+ * True while anything in it moves.
615
+ */
616
+ private drawPresetStrip;
609
617
  /** --k-rim-width in CSS px, on whole device pixels (at least one), so hairlines stay crisp. */
610
618
  private hairline;
611
619
  /**
@@ -19,8 +19,11 @@ export interface ActivePresetState {
19
19
  }
20
20
  /** How strong the selection pill stays on the active preset once the values drift off it. */
21
21
  export declare const DRIFTED_PILL = 0.5;
22
- /** The strip's id for the store's active preset (`p:` a preset, `v:` a saved variant), if any. */
23
- export declare function activeEntryId(controls: Controls<any>): string | null;
22
+ /**
23
+ * The strip's id for the store's active preset (`p:` a preset, `v:` a saved variant), if any;
24
+ * `scope` as in `Controls.preset`.
25
+ */
26
+ export declare function activeEntryId(controls: Controls<any>, scope?: string): string | null;
24
27
  /**
25
28
  * Resolves which preset or variant is currently active.
26
29
  * The store's active preset (`baseId`) holds the selection however far the values drift from it.
@@ -59,6 +62,8 @@ export interface PresetStrip {
59
62
  select(index: number): void;
60
63
  /** Lays the presets out as chips or a list (see `PresetDisplay`). */
61
64
  display(mode: PresetDisplay): void;
65
+ /** Keeps each of the first nine chips' digit on it, quietly, before its name. */
66
+ digits(on: boolean): void;
62
67
  selectById(id: string): void;
63
68
  /** Saves the current values as "Variant N" and opens its name to typing; returns a status message. */
64
69
  save(): string;
@@ -75,6 +80,11 @@ export interface PresetStrip {
75
80
  }
76
81
  export declare function createPresetStrip<S extends Schema>(doc: Document, controls: Controls<S>, showVariants: boolean, run: (action: () => unknown) => void, onAfterChange?: () => void,
77
82
  /** What a preset's chip says (its id stays its identity); `controls.presetLabel` by default. */
78
- presetLabel?: (id: string) => string,
83
+ presetLabel?: (id: string, scope: string) => string,
79
84
  /** Whether these entries matter where the strip is shown (a tab they don't touch hides it). */
80
- relevant?: (entries: readonly PresetEntry<any>[]) => boolean): PresetStrip;
85
+ relevant?: (entries: readonly PresetEntry<any>[]) => boolean,
86
+ /**
87
+ * Whose presets it shows: a tab or group's (see `ControlsOptions.scopedPresets`), `""` for the
88
+ * panel's own, or null for none (the strip hides).
89
+ */
90
+ scopeOf?: () => string | null): PresetStrip;
@@ -4,7 +4,7 @@ export { confirm, type ConfirmOptions } from "./confirm.js";
4
4
  export { overlayLook, type Anchor } from "./layer.js";
5
5
  export { assignPageCodes, hints, pageHints, type HintPainter, type HintSourceOptions, type PageHints, type PageHintTarget, } from "../panel/page-hints.js";
6
6
  export { fuzzy, inTriangle, marks, matchItem, place, safeTriangle, stepItem, rank, type Box, type FuzzyMatch, type Placed, type Placement, type Ranked, type Side, } from "./overlay-model.js";
7
- export { commandPalette, type Command, type CommandPalette, type CommandPaletteOptions, } from "./palette.js";
7
+ export { commandPalette, type Command, type CommandPalette, type CommandPaletteOptions, type PalettePresence, } from "./palette.js";
8
8
  export { contextMenu, menu, type ContextMenu, type ContextMenuOptions, type Menu, type MenuCloseReason, type MenuEntry, type MenuItem, type MenuOptions, } from "./menu.js";
9
9
  export { popover, type Popover, type PopoverCloseReason, type PopoverOptions } from "./popover.js";
10
10
  export { readout, type Readout } from "./readouts.js";
@@ -1,4 +1,5 @@
1
1
  import type { UiLook } from "./look.js";
2
+ import { type PresenceOptions } from "./layer.js";
2
3
  export interface Command {
3
4
  /** Stable across sessions (recents remember it); the label by default. */
4
5
  readonly id?: string;
@@ -25,9 +26,23 @@ export interface CommandPaletteOptions {
25
26
  readonly placeholder?: string;
26
27
  /** Recent commands are remembered under this key in localStorage; `false` remembers nothing. */
27
28
  readonly recents?: string | false;
29
+ /**
30
+ * How it comes and goes: `"fade"` (opacity over about 80 ms, the default), `"none"` or `false`
31
+ * (at once), or a spring of your own. Fields you leave out are the full entrance: grows from
32
+ * .96, sharpens from 8 px, drops 8 px, over .34 s; it leaves in .2 s at most.
33
+ */
34
+ readonly presence?: PalettePresence;
28
35
  readonly look?: UiLook;
29
36
  readonly doc?: Document;
30
37
  }
38
+ export type PalettePresence = "fade" | "none" | false | {
39
+ readonly duration?: number;
40
+ readonly scale?: number;
41
+ readonly blur?: number;
42
+ readonly dy?: number;
43
+ };
44
+ /** The layer motion for a `presence` option; null for none. */
45
+ export declare function palettePresence(p?: PalettePresence): PresenceOptions | null;
31
46
  export interface CommandPalette {
32
47
  open(query?: string): void;
33
48
  close(): void;
@@ -50,8 +50,11 @@ export interface NavResult {
50
50
  readonly expanded: boolean;
51
51
  };
52
52
  }
53
- /** Roving focus: ↑↓ move, ← closes or goes to the parent, → opens or goes to the first child. */
54
- export declare function navigate<T>(rows: readonly FlatRow<T>[], active: string | null, key: NavKey | "PageUp" | "PageDown", page?: number): NavResult;
53
+ /**
54
+ * Roving focus: ↑↓ move, ← closes or goes to the parent, → opens or goes to the first child.
55
+ * Right to left, ← and → swap.
56
+ */
57
+ export declare function navigate<T>(rows: readonly FlatRow<T>[], active: string | null, pressed: NavKey | "PageUp" | "PageDown", page?: number, dir?: "ltr" | "rtl"): NavResult;
55
58
  /**
56
59
  * Type-ahead: the next row whose label starts with what's been typed, after the active one (or
57
60
  * from it, once the word is longer than a letter, so typing on refines in place).
package/dist/ui-react.js CHANGED
@@ -5046,7 +5046,8 @@ function ancestors(index, id) {
5046
5046
  out.push(p);
5047
5047
  return out;
5048
5048
  }
5049
- function navigate(rows, active, key, page = 10) {
5049
+ function navigate(rows, active, pressed, page = 10, dir = "ltr") {
5050
+ const key = dir === "rtl" && (pressed === "ArrowLeft" || pressed === "ArrowRight") ? pressed === "ArrowLeft" ? "ArrowRight" : "ArrowLeft" : pressed;
5050
5051
  if (rows.length === 0)
5051
5052
  return { active: null };
5052
5053
  const i = active === null ? -1 : rows.findIndex((r) => r.id === active);
@@ -5929,7 +5930,8 @@ function tree(container, options) {
5929
5930
  options.onMove(move);
5930
5931
  } else if (nav.includes(e.key) && !e.metaKey && !e.ctrlKey) {
5931
5932
  const page = Math.max(1, Math.floor(viewport() / h) - 1);
5932
- const r = navigate(rows, active, e.key, page);
5933
+ const dir = win.getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr";
5934
+ const r = navigate(rows, active, e.key, page, dir);
5933
5935
  if (r.toggle)
5934
5936
  toggle(r.toggle.id, r.toggle.expanded);
5935
5937
  else if (r.active) {
@@ -7643,7 +7645,7 @@ function presence(el, o = {}) {
7643
7645
  });
7644
7646
  const tune = (seconds, zeta) => {
7645
7647
  const reduced = reducedMotion(win);
7646
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
7648
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
7647
7649
  spring.zeta = reduced ? 1 : zeta;
7648
7650
  };
7649
7651
  render();
package/dist/ui-solid.js CHANGED
@@ -5045,7 +5045,8 @@ function ancestors(index, id) {
5045
5045
  out.push(p);
5046
5046
  return out;
5047
5047
  }
5048
- function navigate(rows, active, key, page = 10) {
5048
+ function navigate(rows, active, pressed, page = 10, dir = "ltr") {
5049
+ const key = dir === "rtl" && (pressed === "ArrowLeft" || pressed === "ArrowRight") ? pressed === "ArrowLeft" ? "ArrowRight" : "ArrowLeft" : pressed;
5049
5050
  if (rows.length === 0)
5050
5051
  return { active: null };
5051
5052
  const i = active === null ? -1 : rows.findIndex((r) => r.id === active);
@@ -5928,7 +5929,8 @@ function tree(container, options) {
5928
5929
  options.onMove(move);
5929
5930
  } else if (nav.includes(e.key) && !e.metaKey && !e.ctrlKey) {
5930
5931
  const page = Math.max(1, Math.floor(viewport() / h) - 1);
5931
- const r = navigate(rows, active, e.key, page);
5932
+ const dir = win.getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr";
5933
+ const r = navigate(rows, active, e.key, page, dir);
5932
5934
  if (r.toggle)
5933
5935
  toggle(r.toggle.id, r.toggle.expanded);
5934
5936
  else if (r.active) {
@@ -7642,7 +7644,7 @@ function presence(el, o = {}) {
7642
7644
  });
7643
7645
  const tune = (seconds, zeta) => {
7644
7646
  const reduced = reducedMotion(win);
7645
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
7647
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
7646
7648
  spring.zeta = reduced ? 1 : zeta;
7647
7649
  };
7648
7650
  render();
package/dist/ui.js CHANGED
@@ -3244,7 +3244,7 @@ function presence(el, o = {}) {
3244
3244
  });
3245
3245
  const tune = (seconds, zeta) => {
3246
3246
  const reduced = reducedMotion(win);
3247
- spring.omega = 2 * Math.PI / (reduced ? FADE.duration : seconds);
3247
+ spring.omega = 2 * Math.PI / (reduced ? Math.min(FADE.duration, seconds) : seconds);
3248
3248
  spring.zeta = reduced ? 1 : zeta;
3249
3249
  };
3250
3250
  render();
@@ -4708,7 +4708,8 @@ function ancestors(index, id) {
4708
4708
  out.push(p);
4709
4709
  return out;
4710
4710
  }
4711
- function navigate(rows, active, key, page = 10) {
4711
+ function navigate(rows, active, pressed, page = 10, dir = "ltr") {
4712
+ const key = dir === "rtl" && (pressed === "ArrowLeft" || pressed === "ArrowRight") ? pressed === "ArrowLeft" ? "ArrowRight" : "ArrowLeft" : pressed;
4712
4713
  if (rows.length === 0)
4713
4714
  return { active: null };
4714
4715
  const i = active === null ? -1 : rows.findIndex((r) => r.id === active);
@@ -10649,7 +10650,8 @@ function tree(container, options) {
10649
10650
  options.onMove(move);
10650
10651
  } else if (nav.includes(e.key) && !e.metaKey && !e.ctrlKey) {
10651
10652
  const page = Math.max(1, Math.floor(viewport() / h) - 1);
10652
- const r = navigate(rows, active, e.key, page);
10653
+ const dir = win.getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr";
10654
+ const r = navigate(rows, active, e.key, page, dir);
10653
10655
  if (r.toggle)
10654
10656
  toggle(r.toggle.id, r.toggle.expanded);
10655
10657
  else if (r.active) {
@@ -13426,6 +13428,22 @@ function toast(message, options = {}) {
13426
13428
  }
13427
13429
 
13428
13430
  // src/ui/palette.ts
13431
+ var FADE_SECONDS = 0.08;
13432
+ function palettePresence(p = "fade") {
13433
+ if (p === false || p === "none")
13434
+ return null;
13435
+ if (p === "fade")
13436
+ return { scale: 1, blur: 0, dy: 0, duration: FADE_SECONDS, exit: FADE_SECONDS, bounce: 0 };
13437
+ const duration = p.duration ?? 0.34;
13438
+ return {
13439
+ scale: p.scale ?? 0.96,
13440
+ blur: p.blur ?? 8,
13441
+ dy: p.dy ?? -8,
13442
+ duration,
13443
+ exit: Math.min(0.2, duration),
13444
+ bounce: 0
13445
+ };
13446
+ }
13429
13447
  var idOf = (c) => c.id ?? c.label;
13430
13448
  var MAX_RECENT = 5;
13431
13449
  var MAX_SHOWN = 200;
@@ -13433,6 +13451,7 @@ function commandPalette(options) {
13433
13451
  const doc = options.doc ?? document;
13434
13452
  const win = doc.defaultView;
13435
13453
  const hotkey = options.hotkey === undefined ? "Mod+K" : options.hotkey;
13454
+ const motionOptions = palettePresence(options.presence);
13436
13455
  const storeKey = options.recents === false ? null : `knobs-palette:${options.recents ?? "recent"}`;
13437
13456
  const readRecents = () => {
13438
13457
  if (!storeKey)
@@ -13662,7 +13681,7 @@ function commandPalette(options) {
13662
13681
  const layer = el;
13663
13682
  const giveBack = focusReturn(doc, layer);
13664
13683
  const commands = typeof options.commands === "function" ? options.commands() : options.commands;
13665
- const motion = presence(layer, { scale: 0.96, blur: 8, dy: -8, duration: 0.34 });
13684
+ const motion = motionOptions ? presence(layer, motionOptions) : null;
13666
13685
  state = {
13667
13686
  input,
13668
13687
  list,
@@ -13700,7 +13719,7 @@ function commandPalette(options) {
13700
13719
  runAt(state.active);
13701
13720
  }
13702
13721
  });
13703
- motion.show();
13722
+ motion?.show();
13704
13723
  input.focus({ preventScroll: true });
13705
13724
  input.select();
13706
13725
  };
@@ -13713,7 +13732,10 @@ function commandPalette(options) {
13713
13732
  el = null;
13714
13733
  s.unstack();
13715
13734
  s.giveBack();
13716
- s.motion.hide().then(() => layer.remove());
13735
+ if (s.motion)
13736
+ s.motion.hide().then(() => layer.remove());
13737
+ else
13738
+ layer.remove();
13717
13739
  };
13718
13740
  const onKey = (e) => {
13719
13741
  if (!hotkey || !matchesCombo(hotkey, e))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitlangton/knobs",
3
- "version": "0.4.0-next.12",
3
+ "version": "0.4.0-next.14",
4
4
  "description": "Per-component tuning controls. Explore in development, ship constants.",
5
5
  "type": "module",
6
6
  "license": "MIT",