giggles 0.5.1 → 0.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.
@@ -48,7 +48,6 @@ function useKeybindingRegistry(focus) {
48
48
  return trapIndex >= 0 ? new Set(branchPath.slice(0, trapIndex + 1)) : null;
49
49
  })();
50
50
  const available = all.filter((b) => {
51
- if (b.when === "mounted") return withinTrapSet ? withinTrapSet.has(b.nodeId) : true;
52
51
  return (withinTrapSet ?? branchSet).has(b.nodeId);
53
52
  });
54
53
  const local = focus ? all.filter((b) => b.nodeId === focus.id) : [];
@@ -397,14 +396,12 @@ var FocusStore = class {
397
396
  if (typeof def === "function") {
398
397
  return [key, { handler: def }];
399
398
  }
400
- return [key, { handler: def.action, name: def.name, when: def.when }];
399
+ return [key, { handler: def.action, name: def.name }];
401
400
  });
402
401
  const registration = {
403
402
  bindings: new Map(entries),
404
- capture: (options == null ? void 0 : options.capture) ?? false,
405
- onKeypress: options == null ? void 0 : options.onKeypress,
406
- passthrough: (options == null ? void 0 : options.passthrough) ? new Set(options.passthrough) : void 0,
407
- layer: options == null ? void 0 : options.layer
403
+ fallback: options == null ? void 0 : options.fallback,
404
+ bubble: (options == null ? void 0 : options.bubble) ? new Set(options.bubble) : void 0
408
405
  };
409
406
  if (!this.keybindings.has(nodeId)) {
410
407
  this.keybindings.set(nodeId, /* @__PURE__ */ new Map());
@@ -424,33 +421,21 @@ var FocusStore = class {
424
421
  const nodeRegistrations = this.keybindings.get(nodeId);
425
422
  if (!nodeRegistrations || nodeRegistrations.size === 0) return void 0;
426
423
  const mergedBindings = /* @__PURE__ */ new Map();
427
- let finalCapture = false;
428
- let finalOnKeypress;
429
- let finalPassthrough;
430
- let finalLayer;
424
+ let finalFallback;
425
+ let finalBubble;
431
426
  for (const registration of nodeRegistrations.values()) {
432
- const isCaptureRegistration = registration.onKeypress !== void 0;
433
- const shouldIncludeBindings = !isCaptureRegistration || registration.capture;
434
- if (shouldIncludeBindings) {
435
- for (const [key, entry] of registration.bindings) {
436
- mergedBindings.set(key, entry);
437
- }
438
- }
439
- if (registration.capture) {
440
- finalCapture = true;
441
- finalOnKeypress = registration.onKeypress;
442
- finalPassthrough = registration.passthrough;
427
+ for (const [key, entry] of registration.bindings) {
428
+ mergedBindings.set(key, entry);
443
429
  }
444
- if (registration.layer) {
445
- finalLayer = registration.layer;
430
+ if (registration.fallback) {
431
+ finalFallback = registration.fallback;
432
+ finalBubble = registration.bubble;
446
433
  }
447
434
  }
448
435
  return {
449
436
  bindings: mergedBindings,
450
- capture: finalCapture,
451
- onKeypress: finalOnKeypress,
452
- passthrough: finalPassthrough,
453
- layer: finalLayer
437
+ fallback: finalFallback,
438
+ bubble: finalBubble
454
439
  };
455
440
  }
456
441
  getAllBindings() {
@@ -462,9 +447,7 @@ var FocusStore = class {
462
447
  nodeId,
463
448
  key,
464
449
  handler: entry.handler,
465
- name: entry.name,
466
- when: entry.when,
467
- layer: registration.layer
450
+ name: entry.name
468
451
  });
469
452
  }
470
453
  }
@@ -475,39 +458,42 @@ var FocusStore = class {
475
458
  // Input dispatch
476
459
  // ---------------------------------------------------------------------------
477
460
  // Bridge target for InputRouter. Walks the active branch path with passive-scope
478
- // skipping, capture mode, and trap boundary — the full dispatch algorithm.
461
+ // skipping, fallback handlers, and trap boundary — the full dispatch algorithm.
462
+ //
463
+ // Priority order (per node, walking focused → root):
464
+ // 1. Named bindings — always checked first
465
+ // 2. Fallback handler — deferred until after the full path walk, so named
466
+ // bindings at any ancestor still fire before the fallback kicks in.
467
+ // Keys in `bubble` skip the fallback and continue to the next ancestor.
468
+ // 3. Trap boundary — stops the walk; fallback inside the trap still fires.
479
469
  dispatch(input, key) {
480
470
  var _a;
481
471
  const keyName = normalizeKey(input, key);
482
472
  if (!keyName) return;
483
473
  const path = this.getActiveBranchPath();
484
474
  const trapNodeId = this.trapNodeId;
475
+ let pendingFallback;
485
476
  for (const nodeId of path) {
486
477
  if (this.passiveSet.has(nodeId)) continue;
487
478
  const nodeBindings = this.getNodeBindings(nodeId);
488
479
  if (nodeBindings) {
489
- if (nodeBindings.capture && nodeBindings.onKeypress) {
490
- if (!((_a = nodeBindings.passthrough) == null ? void 0 : _a.has(keyName))) {
491
- nodeBindings.onKeypress(input, key);
492
- return;
493
- }
494
- }
495
480
  const entry = nodeBindings.bindings.get(keyName);
496
- if (entry && entry.when !== "mounted") {
481
+ if (entry) {
497
482
  entry.handler(input, key);
498
483
  return;
499
484
  }
485
+ if (!pendingFallback && nodeBindings.fallback) {
486
+ if ((_a = nodeBindings.bubble) == null ? void 0 : _a.has(keyName)) {
487
+ continue;
488
+ }
489
+ pendingFallback = nodeBindings.fallback;
490
+ }
500
491
  }
501
492
  if (nodeId === trapNodeId) {
502
- return;
503
- }
504
- }
505
- for (const binding of this.getAllBindings()) {
506
- if (binding.key === keyName && binding.when === "mounted") {
507
- binding.handler(input, key);
508
- return;
493
+ break;
509
494
  }
510
495
  }
496
+ pendingFallback == null ? void 0 : pendingFallback(input, key);
511
497
  }
512
498
  // ---------------------------------------------------------------------------
513
499
  // Private helpers
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
3
  import { BoxProps } from 'ink';
4
4
  export { Key } from 'ink';
5
- import { K as Keybindings, a as KeybindingOptions, R as RegisteredKeybinding } from './types-Dmw9TKt4.js';
6
- export { b as KeyHandler } from './types-Dmw9TKt4.js';
5
+ import { K as Keybindings, a as KeybindingOptions, R as RegisteredKeybinding } from './types-HR6Vak_5.js';
6
+ export { b as KeyHandler } from './types-HR6Vak_5.js';
7
7
 
8
8
  declare class GigglesError extends Error {
9
9
  constructor(message: string);
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  useKeybindingRegistry,
15
15
  useKeybindings,
16
16
  useStore
17
- } from "./chunk-LXNKSYJT.js";
17
+ } from "./chunk-QV7GQTKO.js";
18
18
  import {
19
19
  ThemeProvider,
20
20
  useTheme
@@ -6,22 +6,17 @@ type KeyName = SpecialKey | (string & {});
6
6
  type KeybindingDefinition = KeyHandler | {
7
7
  action: KeyHandler;
8
8
  name: string;
9
- when?: 'focused' | 'mounted';
10
9
  };
11
10
  type Keybindings = Partial<Record<KeyName, KeybindingDefinition>>;
12
11
  type KeybindingOptions = {
13
- capture?: boolean;
14
- onKeypress?: (input: string, key: Key) => void;
15
- passthrough?: string[];
16
- layer?: string;
12
+ fallback?: (input: string, key: Key) => void;
13
+ bubble?: string[];
17
14
  };
18
15
  type RegisteredKeybinding = {
19
16
  nodeId: string;
20
17
  key: string;
21
18
  handler: KeyHandler;
22
19
  name?: string;
23
- when?: 'focused' | 'mounted';
24
- layer?: string;
25
20
  };
26
21
 
27
22
  export type { Keybindings as K, RegisteredKeybinding as R, KeybindingOptions as a, KeyHandler as b };
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { R as RegisteredKeybinding } from '../types-Dmw9TKt4.js';
2
+ import { R as RegisteredKeybinding } from '../types-HR6Vak_5.js';
3
3
  import React$1 from 'react';
4
4
  import { BoxProps } from 'ink';
5
5
 
package/dist/ui/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  useFocusNode,
5
5
  useKeybindingRegistry,
6
6
  useKeybindings
7
- } from "../chunk-LXNKSYJT.js";
7
+ } from "../chunk-QV7GQTKO.js";
8
8
  import {
9
9
  CodeBlock
10
10
  } from "../chunk-SKSDNDQF.js";
@@ -77,9 +77,7 @@ function Inner({ onClose, render }) {
77
77
  }
78
78
  },
79
79
  {
80
- capture: true,
81
- passthrough: ["escape", "enter", "left", "right", "backspace"],
82
- onKeypress: (input, key) => {
80
+ fallback: (input, key) => {
83
81
  if (input.length === 1 && !key.ctrl) {
84
82
  setQuery((q) => q + input);
85
83
  setSelectedIndex(0);
@@ -179,15 +177,14 @@ function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
179
177
  ...onSubmit && { enter: () => onSubmit(value) }
180
178
  },
181
179
  {
182
- capture: true,
183
- passthrough: ["tab", "shift+tab", "enter", "escape", "backspace", "delete", "left", "right", "home", "end"],
184
- onKeypress: (input, key) => {
180
+ fallback: (input, key) => {
185
181
  if (input.length === 1 && !key.ctrl && !key.return && !key.escape && !key.tab) {
186
182
  const c = cursorRef.current;
187
183
  cursorRef.current = c + 1;
188
184
  onChange(value.slice(0, c) + input + value.slice(c));
189
185
  }
190
- }
186
+ },
187
+ bubble: ["tab", "shift+tab", "enter", "escape", "backspace", "delete", "left", "right", "home", "end"]
191
188
  }
192
189
  );
193
190
  const before = value.slice(0, cursor);
@@ -643,28 +640,14 @@ function Autocomplete({
643
640
  }
644
641
  },
645
642
  {
646
- capture: true,
647
- passthrough: [
648
- "tab",
649
- "shift+tab",
650
- "escape",
651
- "backspace",
652
- "delete",
653
- "left",
654
- "right",
655
- "home",
656
- "end",
657
- "up",
658
- "down",
659
- "enter"
660
- ],
661
- onKeypress: (input, key) => {
643
+ fallback: (input, key) => {
662
644
  if (input.length === 1 && !key.ctrl && !key.return && !key.escape && !key.tab) {
663
645
  const c = cursorRef.current;
664
646
  cursorRef.current = c + 1;
665
647
  updateQuery(query.slice(0, c) + input + query.slice(c));
666
648
  }
667
- }
649
+ },
650
+ bubble: ["tab", "shift+tab", "escape"]
668
651
  }
669
652
  );
670
653
  const before = query.slice(0, cursor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",