@kitlangton/knobs 0.4.0-next.13 → 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) {
@@ -140,6 +140,8 @@ export interface RemoteControlsOptions {
140
140
  readonly scopedPresets?: Readonly<Record<string, Readonly<Record<string, State>>>>;
141
141
  /** Groups pinned above the tab strip, outside the presets (see `ControlsOptions.pinned`). */
142
142
  readonly pinned?: readonly string[];
143
+ /** Groups presets leave alone, in place (see `ControlsOptions.inPresets`). */
144
+ readonly inPresets?: Readonly<Record<string, boolean>>;
143
145
  /** Numbered presets per group, e.g. the phone's preset sets (see `ControlsOptions.groupPresets`). */
144
146
  readonly groupPresets?: Readonly<Record<string, readonly GroupPreset[]>>;
145
147
  /** Display labels by group id, section id and preset id (see `ControlsOptions.groupLabels`). */
@@ -147,7 +149,7 @@ export interface RemoteControlsOptions {
147
149
  readonly sectionLabels?: Readonly<Record<string, string>>;
148
150
  readonly presetLabels?: Readonly<Record<string, string>>;
149
151
  /** Shorthand for `presetLabels.default`, e.g. "Native". */
150
- readonly defaultLabel?: string;
152
+ readonly defaultLabel?: string | Readonly<Record<string, string>>;
151
153
  /** How a page card shows its group presets (see `ControlsOptions.groupPresetDisplay`). */
152
154
  readonly groupPresetDisplay?: GroupPresetDisplay;
153
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 })`. */
@@ -89,8 +89,11 @@ export interface LabelOptions {
89
89
  readonly sectionLabels?: Readonly<Record<string, string>>;
90
90
  /** By preset id: what the preset strip shows, e.g. `{ default: "Native" }`. */
91
91
  readonly presetLabels?: Readonly<Record<string, string>>;
92
- /** Shorthand for `presetLabels.default`. */
93
- 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>>;
94
97
  /** How a page card shows its group presets (see `GroupPresetDisplay`). */
95
98
  readonly groupPresetDisplay?: GroupPresetDisplay;
96
99
  /** By group id: one muted line under its card's title on a page, over fields' `groupDescription`. */
@@ -157,9 +160,14 @@ export interface MountPanelOptions extends LabelOptions {
157
160
  * and knobs/ui piece on the page (and the host's `data-hint` elements), so pressed here or
158
161
  * anywhere outside a text field it jumps across all of them; with nothing else on the page it's
159
162
  * this panel's alone, as always. `"panel"`: `s` here labels only this panel's controls, and they
160
- * 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.
161
166
  */
162
- readonly hints?: "page" | "panel";
167
+ readonly hints?: "page" | "panel" | {
168
+ readonly reach?: "page" | "panel";
169
+ readonly digits?: "keys" | "always";
170
+ };
163
171
  /**
164
172
  * The key that shows and hides every panel and chip on the page that listens for it, outside
165
173
  * text fields and without modifiers; remembered per store. The store's `toggleKey` by default
@@ -383,6 +391,13 @@ export interface ControlsOptions<S extends Schema> extends LabelOptions {
383
391
  * one keeps their values, saving one keeps them out, and they're modified against their defaults.
384
392
  */
385
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>>;
386
401
  /**
387
402
  * Numbered presets per group, shown as 1, 2, 3… on the group's card in a page. A group's
388
403
  * presets cover every key any of them names; keys a preset leaves out are at their defaults,
@@ -432,7 +447,10 @@ export interface Controls<S extends Schema = Schema> {
432
447
  readonly variants: Variants<Values<S>>;
433
448
  /** The tabs and groups with presets of their own, by id (see `ControlsOptions.scopedPresets`). */
434
449
  readonly scopes: Readonly<Record<string, PresetScope<Values<S>>>>;
435
- /** The scope `key`'s presets come from: a tab or group's id, `""` for `presets`, or undefined when pinned. */
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
+ */
436
454
  scopeOf(key: string): string | undefined;
437
455
  readonly actions: readonly ControlAction<Values<S>>[];
438
456
  /** Its panels' theme, as given (see `ControlsOptions.theme`). */
@@ -467,6 +485,8 @@ export interface Controls<S extends Schema = Schema> {
467
485
  presetLabel(id: string, scope?: string): string;
468
486
  /** Whether `key` is pinned (its group is, see `ControlsOptions.pinned`), outside the presets. */
469
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;
470
490
  readonly error: string | null;
471
491
  get(): Values<S>;
472
492
  getServerSnapshot(): 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
  /**
@@ -62,6 +62,8 @@ export interface PresetStrip {
62
62
  select(index: number): void;
63
63
  /** Lays the presets out as chips or a list (see `PresetDisplay`). */
64
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;
65
67
  selectById(id: string): void;
66
68
  /** Saves the current values as "Variant N" and opens its name to typing; returns a status message. */
67
69
  save(): string;
@@ -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.13",
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",