@nick-skriabin/glyph 0.1.32 → 0.1.34
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.
- package/dist/index.cjs +342 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -23
- package/dist/index.d.ts +77 -23
- package/dist/index.js +342 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2065,6 +2065,17 @@ function render(element, opts = {}) {
|
|
|
2065
2065
|
return () => {
|
|
2066
2066
|
focusChangeHandlers.delete(handler);
|
|
2067
2067
|
};
|
|
2068
|
+
},
|
|
2069
|
+
getRegisteredElements() {
|
|
2070
|
+
const result = [];
|
|
2071
|
+
for (const id of focusOrder) {
|
|
2072
|
+
if (skippableIds.has(id)) continue;
|
|
2073
|
+
const node = focusRegistry.get(id);
|
|
2074
|
+
if (node) {
|
|
2075
|
+
result.push({ id, node });
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return result;
|
|
2068
2079
|
}
|
|
2069
2080
|
};
|
|
2070
2081
|
const layoutSubscriptions = /* @__PURE__ */ new Map();
|
|
@@ -2288,13 +2299,17 @@ function render(element, opts = {}) {
|
|
|
2288
2299
|
};
|
|
2289
2300
|
return handle;
|
|
2290
2301
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
}
|
|
2302
|
+
var Box = React15.forwardRef(
|
|
2303
|
+
function Box2({ children, style, focusable }, ref) {
|
|
2304
|
+
return React15__default.default.createElement("box", { style, focusable, ref }, children);
|
|
2305
|
+
}
|
|
2306
|
+
);
|
|
2307
|
+
var Text = React15.forwardRef(
|
|
2308
|
+
function Text2({ children, style, wrap }, ref) {
|
|
2309
|
+
const mergedStyle = wrap ? { ...style, wrap } : style;
|
|
2310
|
+
return React15__default.default.createElement("text", { style: mergedStyle, ref }, children);
|
|
2311
|
+
}
|
|
2312
|
+
);
|
|
2298
2313
|
function cursorToVisualLine(text, pos, width) {
|
|
2299
2314
|
if (width <= 0) {
|
|
2300
2315
|
return { visualLine: 0, visualCol: pos, totalVisualLines: 1, lineStartOffset: 0, lineLength: text.length };
|
|
@@ -2401,6 +2416,7 @@ function Input(props) {
|
|
|
2401
2416
|
const [cursorPos, setCursorPos] = React15.useState(defaultValue.length);
|
|
2402
2417
|
const [innerWidth, setInnerWidth] = React15.useState(0);
|
|
2403
2418
|
const [isFocused, setIsFocused] = React15.useState(false);
|
|
2419
|
+
const [nodeReady, setNodeReady] = React15.useState(false);
|
|
2404
2420
|
const inputCtx = React15.useContext(InputContext);
|
|
2405
2421
|
const focusCtx = React15.useContext(FocusContext);
|
|
2406
2422
|
const layoutCtx = React15.useContext(LayoutContext);
|
|
@@ -2449,7 +2465,7 @@ function Input(props) {
|
|
|
2449
2465
|
React15.useEffect(() => {
|
|
2450
2466
|
if (!focusCtx || !focusIdRef.current || !nodeRef.current) return;
|
|
2451
2467
|
return focusCtx.register(focusIdRef.current, nodeRef.current);
|
|
2452
|
-
}, [focusCtx]);
|
|
2468
|
+
}, [focusCtx, nodeReady]);
|
|
2453
2469
|
const autoFocusedRef = React15.useRef(false);
|
|
2454
2470
|
React15.useEffect(() => {
|
|
2455
2471
|
if (autoFocus && !autoFocusedRef.current && focusCtx && focusIdRef.current) {
|
|
@@ -2459,7 +2475,7 @@ function Input(props) {
|
|
|
2459
2475
|
focusCtx.requestFocus(fid);
|
|
2460
2476
|
});
|
|
2461
2477
|
}
|
|
2462
|
-
}, [autoFocus, focusCtx]);
|
|
2478
|
+
}, [autoFocus, focusCtx, nodeReady]);
|
|
2463
2479
|
React15.useEffect(() => {
|
|
2464
2480
|
if (!focusCtx || !focusIdRef.current) return;
|
|
2465
2481
|
const fid = focusIdRef.current;
|
|
@@ -2467,7 +2483,7 @@ function Input(props) {
|
|
|
2467
2483
|
return focusCtx.onFocusChange((newId) => {
|
|
2468
2484
|
setIsFocused(newId === fid);
|
|
2469
2485
|
});
|
|
2470
|
-
}, [focusCtx]);
|
|
2486
|
+
}, [focusCtx, nodeReady]);
|
|
2471
2487
|
React15.useEffect(() => {
|
|
2472
2488
|
if (!inputCtx || !focusIdRef.current) return;
|
|
2473
2489
|
const fid = focusIdRef.current;
|
|
@@ -2677,7 +2693,7 @@ function Input(props) {
|
|
|
2677
2693
|
return false;
|
|
2678
2694
|
};
|
|
2679
2695
|
return inputCtx.registerInputHandler(fid, handler);
|
|
2680
|
-
}, [inputCtx]);
|
|
2696
|
+
}, [inputCtx, nodeReady]);
|
|
2681
2697
|
const mergedStyle = {
|
|
2682
2698
|
...style,
|
|
2683
2699
|
...isFocused && focusedStyle ? focusedStyle : {}
|
|
@@ -2695,6 +2711,11 @@ function Input(props) {
|
|
|
2695
2711
|
if (node) {
|
|
2696
2712
|
nodeRef.current = node;
|
|
2697
2713
|
focusIdRef.current = node.focusId;
|
|
2714
|
+
setNodeReady(true);
|
|
2715
|
+
} else {
|
|
2716
|
+
nodeRef.current = null;
|
|
2717
|
+
focusIdRef.current = null;
|
|
2718
|
+
setNodeReady(false);
|
|
2698
2719
|
}
|
|
2699
2720
|
}
|
|
2700
2721
|
});
|
|
@@ -2810,11 +2831,12 @@ function Button({
|
|
|
2810
2831
|
const focusIdRef = React15.useRef(null);
|
|
2811
2832
|
const onPressRef = React15.useRef(onPress);
|
|
2812
2833
|
onPressRef.current = onPress;
|
|
2834
|
+
const [nodeReady, setNodeReady] = React15.useState(false);
|
|
2813
2835
|
const [isFocused, setIsFocused] = React15.useState(false);
|
|
2814
2836
|
React15.useEffect(() => {
|
|
2815
2837
|
if (!focusCtx || !focusIdRef.current || !nodeRef.current || disabled) return;
|
|
2816
2838
|
return focusCtx.register(focusIdRef.current, nodeRef.current);
|
|
2817
|
-
}, [focusCtx, disabled]);
|
|
2839
|
+
}, [focusCtx, disabled, nodeReady]);
|
|
2818
2840
|
React15.useEffect(() => {
|
|
2819
2841
|
if (!focusCtx || !focusIdRef.current) return;
|
|
2820
2842
|
const fid = focusIdRef.current;
|
|
@@ -2822,7 +2844,7 @@ function Button({
|
|
|
2822
2844
|
return focusCtx.onFocusChange((newId) => {
|
|
2823
2845
|
setIsFocused(newId === fid);
|
|
2824
2846
|
});
|
|
2825
|
-
}, [focusCtx]);
|
|
2847
|
+
}, [focusCtx, nodeReady]);
|
|
2826
2848
|
React15.useEffect(() => {
|
|
2827
2849
|
if (!inputCtx || !focusIdRef.current || disabled) return;
|
|
2828
2850
|
const fid = focusIdRef.current;
|
|
@@ -2835,7 +2857,7 @@ function Button({
|
|
|
2835
2857
|
return false;
|
|
2836
2858
|
};
|
|
2837
2859
|
return inputCtx.registerInputHandler(fid, handler);
|
|
2838
|
-
}, [inputCtx, focusCtx, disabled]);
|
|
2860
|
+
}, [inputCtx, focusCtx, disabled, nodeReady]);
|
|
2839
2861
|
const mergedStyle = {
|
|
2840
2862
|
...style,
|
|
2841
2863
|
...isFocused && focusedStyle ? focusedStyle : {}
|
|
@@ -2849,6 +2871,11 @@ function Button({
|
|
|
2849
2871
|
if (node) {
|
|
2850
2872
|
nodeRef.current = node;
|
|
2851
2873
|
focusIdRef.current = node.focusId;
|
|
2874
|
+
setNodeReady(true);
|
|
2875
|
+
} else {
|
|
2876
|
+
nodeRef.current = null;
|
|
2877
|
+
focusIdRef.current = null;
|
|
2878
|
+
setNodeReady(false);
|
|
2852
2879
|
}
|
|
2853
2880
|
}
|
|
2854
2881
|
},
|
|
@@ -3124,6 +3151,7 @@ function List({
|
|
|
3124
3151
|
const focusIdRef = React15.useRef(null);
|
|
3125
3152
|
const onSelectRef = React15.useRef(onSelect);
|
|
3126
3153
|
onSelectRef.current = onSelect;
|
|
3154
|
+
const [nodeReady, setNodeReady] = React15.useState(false);
|
|
3127
3155
|
const [isFocused, setIsFocused] = React15.useState(false);
|
|
3128
3156
|
const lastKeyRef = React15.useRef(null);
|
|
3129
3157
|
const setIndex = React15.useCallback(
|
|
@@ -3154,7 +3182,7 @@ function List({
|
|
|
3154
3182
|
React15.useEffect(() => {
|
|
3155
3183
|
if (!focusCtx || !focusIdRef.current || !nodeRef.current || !focusable) return;
|
|
3156
3184
|
return focusCtx.register(focusIdRef.current, nodeRef.current);
|
|
3157
|
-
}, [focusCtx, focusable]);
|
|
3185
|
+
}, [focusCtx, focusable, nodeReady]);
|
|
3158
3186
|
React15.useEffect(() => {
|
|
3159
3187
|
if (!focusCtx || !focusIdRef.current) return;
|
|
3160
3188
|
const fid = focusIdRef.current;
|
|
@@ -3162,7 +3190,7 @@ function List({
|
|
|
3162
3190
|
return focusCtx.onFocusChange((newId) => {
|
|
3163
3191
|
setIsFocused(newId === fid);
|
|
3164
3192
|
});
|
|
3165
|
-
}, [focusCtx]);
|
|
3193
|
+
}, [focusCtx, nodeReady]);
|
|
3166
3194
|
const findFirstEnabled = React15.useCallback(
|
|
3167
3195
|
(fromEnd) => {
|
|
3168
3196
|
const start = fromEnd ? count - 1 : 0;
|
|
@@ -3212,7 +3240,7 @@ function List({
|
|
|
3212
3240
|
return false;
|
|
3213
3241
|
};
|
|
3214
3242
|
return inputCtx.registerInputHandler(fid, handler);
|
|
3215
|
-
}, [inputCtx, focusCtx, focusable, selectedIndex, setIndex, findNextEnabled, findFirstEnabled, disabledIndices]);
|
|
3243
|
+
}, [inputCtx, focusCtx, focusable, selectedIndex, setIndex, findNextEnabled, findFirstEnabled, disabledIndices, nodeReady]);
|
|
3216
3244
|
const items = [];
|
|
3217
3245
|
for (let i = 0; i < count; i++) {
|
|
3218
3246
|
items.push(
|
|
@@ -3232,6 +3260,11 @@ function List({
|
|
|
3232
3260
|
if (node) {
|
|
3233
3261
|
nodeRef.current = node;
|
|
3234
3262
|
focusIdRef.current = node.focusId;
|
|
3263
|
+
setNodeReady(true);
|
|
3264
|
+
} else {
|
|
3265
|
+
nodeRef.current = null;
|
|
3266
|
+
focusIdRef.current = null;
|
|
3267
|
+
setNodeReady(false);
|
|
3235
3268
|
}
|
|
3236
3269
|
}
|
|
3237
3270
|
},
|
|
@@ -3513,6 +3546,7 @@ function Select({
|
|
|
3513
3546
|
const focusIdRef = React15.useRef(null);
|
|
3514
3547
|
const onChangeRef = React15.useRef(onChange);
|
|
3515
3548
|
onChangeRef.current = onChange;
|
|
3549
|
+
const [nodeReady, setNodeReady] = React15.useState(false);
|
|
3516
3550
|
const [isFocused, setIsFocused] = React15.useState(false);
|
|
3517
3551
|
const [isOpen, setIsOpen] = React15.useState(false);
|
|
3518
3552
|
const [highlightIndex, setHighlightIndex] = React15.useState(0);
|
|
@@ -3545,11 +3579,11 @@ function Select({
|
|
|
3545
3579
|
React15.useEffect(() => {
|
|
3546
3580
|
if (!focusCtx || !focusIdRef.current || !nodeRef.current) return;
|
|
3547
3581
|
return focusCtx.register(focusIdRef.current, nodeRef.current);
|
|
3548
|
-
}, [focusCtx]);
|
|
3582
|
+
}, [focusCtx, nodeReady]);
|
|
3549
3583
|
React15.useEffect(() => {
|
|
3550
3584
|
if (!focusCtx || !focusIdRef.current) return;
|
|
3551
3585
|
focusCtx.setSkippable(focusIdRef.current, !!disabled);
|
|
3552
|
-
}, [focusCtx, disabled]);
|
|
3586
|
+
}, [focusCtx, disabled, nodeReady]);
|
|
3553
3587
|
React15.useEffect(() => {
|
|
3554
3588
|
if (!focusCtx || !focusIdRef.current) return;
|
|
3555
3589
|
const fid = focusIdRef.current;
|
|
@@ -3557,7 +3591,7 @@ function Select({
|
|
|
3557
3591
|
return focusCtx.onFocusChange((newId) => {
|
|
3558
3592
|
setIsFocused(newId === fid);
|
|
3559
3593
|
});
|
|
3560
|
-
}, [focusCtx]);
|
|
3594
|
+
}, [focusCtx, nodeReady]);
|
|
3561
3595
|
const findNextEnabled = React15.useCallback(
|
|
3562
3596
|
(from, direction) => {
|
|
3563
3597
|
let next = from + direction;
|
|
@@ -3669,7 +3703,8 @@ function Select({
|
|
|
3669
3703
|
searchable,
|
|
3670
3704
|
searchText,
|
|
3671
3705
|
findNextEnabled,
|
|
3672
|
-
ensureVisible
|
|
3706
|
+
ensureVisible,
|
|
3707
|
+
nodeReady
|
|
3673
3708
|
]);
|
|
3674
3709
|
const useDefaultBorder = !style?.bg && style?.border === void 0;
|
|
3675
3710
|
const triggerStyle = {
|
|
@@ -3846,6 +3881,11 @@ function Select({
|
|
|
3846
3881
|
if (node) {
|
|
3847
3882
|
nodeRef.current = node;
|
|
3848
3883
|
focusIdRef.current = node.focusId;
|
|
3884
|
+
setNodeReady(true);
|
|
3885
|
+
} else {
|
|
3886
|
+
nodeRef.current = null;
|
|
3887
|
+
focusIdRef.current = null;
|
|
3888
|
+
setNodeReady(false);
|
|
3849
3889
|
}
|
|
3850
3890
|
}
|
|
3851
3891
|
},
|
|
@@ -3873,11 +3913,12 @@ function Checkbox({
|
|
|
3873
3913
|
onChangeRef.current = onChange;
|
|
3874
3914
|
const checkedRef = React15.useRef(checked);
|
|
3875
3915
|
checkedRef.current = checked;
|
|
3916
|
+
const [nodeReady, setNodeReady] = React15.useState(false);
|
|
3876
3917
|
const [isFocused, setIsFocused] = React15.useState(false);
|
|
3877
3918
|
React15.useEffect(() => {
|
|
3878
3919
|
if (!focusCtx || !focusIdRef.current || !nodeRef.current || disabled) return;
|
|
3879
3920
|
return focusCtx.register(focusIdRef.current, nodeRef.current);
|
|
3880
|
-
}, [focusCtx, disabled]);
|
|
3921
|
+
}, [focusCtx, disabled, nodeReady]);
|
|
3881
3922
|
React15.useEffect(() => {
|
|
3882
3923
|
if (!focusCtx || !focusIdRef.current) return;
|
|
3883
3924
|
const fid = focusIdRef.current;
|
|
@@ -3885,7 +3926,7 @@ function Checkbox({
|
|
|
3885
3926
|
return focusCtx.onFocusChange((newId) => {
|
|
3886
3927
|
setIsFocused(newId === fid);
|
|
3887
3928
|
});
|
|
3888
|
-
}, [focusCtx]);
|
|
3929
|
+
}, [focusCtx, nodeReady]);
|
|
3889
3930
|
React15.useEffect(() => {
|
|
3890
3931
|
if (!inputCtx || !focusIdRef.current || disabled) return;
|
|
3891
3932
|
const fid = focusIdRef.current;
|
|
@@ -3898,7 +3939,7 @@ function Checkbox({
|
|
|
3898
3939
|
return false;
|
|
3899
3940
|
};
|
|
3900
3941
|
return inputCtx.registerInputHandler(fid, handler);
|
|
3901
|
-
}, [inputCtx, focusCtx, disabled]);
|
|
3942
|
+
}, [inputCtx, focusCtx, disabled, nodeReady]);
|
|
3902
3943
|
const mergedStyle = {
|
|
3903
3944
|
flexDirection: "row",
|
|
3904
3945
|
gap: 1,
|
|
@@ -3921,6 +3962,11 @@ function Checkbox({
|
|
|
3921
3962
|
if (node) {
|
|
3922
3963
|
nodeRef.current = node;
|
|
3923
3964
|
focusIdRef.current = node.focusId;
|
|
3965
|
+
setNodeReady(true);
|
|
3966
|
+
} else {
|
|
3967
|
+
nodeRef.current = null;
|
|
3968
|
+
focusIdRef.current = null;
|
|
3969
|
+
setNodeReady(false);
|
|
3924
3970
|
}
|
|
3925
3971
|
}
|
|
3926
3972
|
},
|
|
@@ -3956,6 +4002,7 @@ function Radio({
|
|
|
3956
4002
|
const focusIdRef = React15.useRef(null);
|
|
3957
4003
|
const onChangeRef = React15.useRef(onChange);
|
|
3958
4004
|
onChangeRef.current = onChange;
|
|
4005
|
+
const [nodeReady, setNodeReady] = React15.useState(false);
|
|
3959
4006
|
const [isFocused, setIsFocused] = React15.useState(false);
|
|
3960
4007
|
const [highlightedIndex, setHighlightedIndex] = React15.useState(() => {
|
|
3961
4008
|
const selectedIdx = items.findIndex((item) => item.value === value);
|
|
@@ -3976,7 +4023,7 @@ function Radio({
|
|
|
3976
4023
|
React15.useEffect(() => {
|
|
3977
4024
|
if (!focusCtx || !focusIdRef.current || !nodeRef.current || disabled) return;
|
|
3978
4025
|
return focusCtx.register(focusIdRef.current, nodeRef.current);
|
|
3979
|
-
}, [focusCtx, disabled]);
|
|
4026
|
+
}, [focusCtx, disabled, nodeReady]);
|
|
3980
4027
|
React15.useEffect(() => {
|
|
3981
4028
|
if (!focusCtx || !focusIdRef.current) return;
|
|
3982
4029
|
const fid = focusIdRef.current;
|
|
@@ -3984,7 +4031,7 @@ function Radio({
|
|
|
3984
4031
|
return focusCtx.onFocusChange((newId) => {
|
|
3985
4032
|
setIsFocused(newId === fid);
|
|
3986
4033
|
});
|
|
3987
|
-
}, [focusCtx]);
|
|
4034
|
+
}, [focusCtx, nodeReady]);
|
|
3988
4035
|
React15.useEffect(() => {
|
|
3989
4036
|
if (!inputCtx || !focusIdRef.current || disabled) return;
|
|
3990
4037
|
const fid = focusIdRef.current;
|
|
@@ -4008,7 +4055,7 @@ function Radio({
|
|
|
4008
4055
|
return false;
|
|
4009
4056
|
};
|
|
4010
4057
|
return inputCtx.registerInputHandler(fid, handler);
|
|
4011
|
-
}, [inputCtx, focusCtx, disabled, items, highlightedIndex, findNextEnabled]);
|
|
4058
|
+
}, [inputCtx, focusCtx, disabled, items, highlightedIndex, findNextEnabled, nodeReady]);
|
|
4012
4059
|
React15.useEffect(() => {
|
|
4013
4060
|
const selectedIdx = items.findIndex((item) => item.value === value);
|
|
4014
4061
|
if (selectedIdx >= 0) {
|
|
@@ -4061,6 +4108,11 @@ function Radio({
|
|
|
4061
4108
|
if (node) {
|
|
4062
4109
|
nodeRef.current = node;
|
|
4063
4110
|
focusIdRef.current = node.focusId;
|
|
4111
|
+
setNodeReady(true);
|
|
4112
|
+
} else {
|
|
4113
|
+
nodeRef.current = null;
|
|
4114
|
+
focusIdRef.current = null;
|
|
4115
|
+
setNodeReady(false);
|
|
4064
4116
|
}
|
|
4065
4117
|
}
|
|
4066
4118
|
},
|
|
@@ -4295,6 +4347,221 @@ function DialogOverlay({ dialog, onDismiss }) {
|
|
|
4295
4347
|
)
|
|
4296
4348
|
);
|
|
4297
4349
|
}
|
|
4350
|
+
var JumpNavContext = React15.createContext(null);
|
|
4351
|
+
function generateHints(count, chars) {
|
|
4352
|
+
const hints = [];
|
|
4353
|
+
const charList = chars.split("");
|
|
4354
|
+
if (count <= charList.length) {
|
|
4355
|
+
for (let i = 0; i < count; i++) {
|
|
4356
|
+
hints.push(charList[i]);
|
|
4357
|
+
}
|
|
4358
|
+
} else {
|
|
4359
|
+
for (let i = 0; i < charList.length && hints.length < count; i++) {
|
|
4360
|
+
for (let j = 0; j < charList.length && hints.length < count; j++) {
|
|
4361
|
+
hints.push(charList[i] + charList[j]);
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
return hints;
|
|
4366
|
+
}
|
|
4367
|
+
function JumpNav({
|
|
4368
|
+
children,
|
|
4369
|
+
activationKey = "ctrl+o",
|
|
4370
|
+
hintStyle,
|
|
4371
|
+
hintBg = "yellow",
|
|
4372
|
+
hintFg = "black",
|
|
4373
|
+
hintChars = "asdfghjklqwertyuiopzxcvbnm",
|
|
4374
|
+
enabled = true
|
|
4375
|
+
}) {
|
|
4376
|
+
const [isActive, setIsActive] = React15.useState(false);
|
|
4377
|
+
const [inputBuffer, setInputBuffer] = React15.useState("");
|
|
4378
|
+
const [hasChildJumpNav, setHasChildJumpNav] = React15.useState(false);
|
|
4379
|
+
const [elements, setElements] = React15.useState([]);
|
|
4380
|
+
const inputCtx = React15.useContext(InputContext);
|
|
4381
|
+
const focusCtx = React15.useContext(FocusContext);
|
|
4382
|
+
const layoutCtx = React15.useContext(LayoutContext);
|
|
4383
|
+
const parentJumpNav = React15.useContext(JumpNavContext);
|
|
4384
|
+
const wrapperRef = React15.useRef(null);
|
|
4385
|
+
React15.useEffect(() => {
|
|
4386
|
+
if (parentJumpNav) {
|
|
4387
|
+
return parentJumpNav.registerChildJumpNav();
|
|
4388
|
+
}
|
|
4389
|
+
}, [parentJumpNav]);
|
|
4390
|
+
const contextValue = React15.useMemo(() => ({
|
|
4391
|
+
isChildActive: hasChildJumpNav,
|
|
4392
|
+
registerChildJumpNav: () => {
|
|
4393
|
+
setHasChildJumpNav(true);
|
|
4394
|
+
return () => setHasChildJumpNav(false);
|
|
4395
|
+
}
|
|
4396
|
+
}), [hasChildJumpNav]);
|
|
4397
|
+
const parseKey = React15.useCallback((keyStr) => {
|
|
4398
|
+
const parts = keyStr.toLowerCase().split("+");
|
|
4399
|
+
return {
|
|
4400
|
+
ctrl: parts.includes("ctrl"),
|
|
4401
|
+
alt: parts.includes("alt"),
|
|
4402
|
+
shift: parts.includes("shift"),
|
|
4403
|
+
meta: parts.includes("meta"),
|
|
4404
|
+
name: parts[parts.length - 1] ?? ""
|
|
4405
|
+
};
|
|
4406
|
+
}, []);
|
|
4407
|
+
const activationKeyParsed = parseKey(activationKey);
|
|
4408
|
+
const findFocusableDescendants = React15.useCallback((node) => {
|
|
4409
|
+
const result = [];
|
|
4410
|
+
function walk(n) {
|
|
4411
|
+
if (n.focusId) {
|
|
4412
|
+
result.push({
|
|
4413
|
+
id: n.focusId,
|
|
4414
|
+
node: n,
|
|
4415
|
+
layout: layoutCtx?.getLayout(n) ?? n.layout
|
|
4416
|
+
});
|
|
4417
|
+
}
|
|
4418
|
+
for (const child of n.children) {
|
|
4419
|
+
walk(child);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
walk(node);
|
|
4423
|
+
return result;
|
|
4424
|
+
}, [layoutCtx]);
|
|
4425
|
+
const refreshElements = React15.useCallback(() => {
|
|
4426
|
+
if (!wrapperRef.current) return;
|
|
4427
|
+
const descendants = findFocusableDescendants(wrapperRef.current);
|
|
4428
|
+
descendants.sort((a, b) => {
|
|
4429
|
+
if (a.layout.y !== b.layout.y) {
|
|
4430
|
+
return a.layout.y - b.layout.y;
|
|
4431
|
+
}
|
|
4432
|
+
return a.layout.x - b.layout.x;
|
|
4433
|
+
});
|
|
4434
|
+
setElements(descendants);
|
|
4435
|
+
}, [findFocusableDescendants]);
|
|
4436
|
+
const wasActiveRef = React15.useRef(false);
|
|
4437
|
+
React15.useEffect(() => {
|
|
4438
|
+
if (isActive && !wasActiveRef.current) {
|
|
4439
|
+
refreshElements();
|
|
4440
|
+
}
|
|
4441
|
+
wasActiveRef.current = isActive;
|
|
4442
|
+
}, [isActive, refreshElements]);
|
|
4443
|
+
const visibleElements = elements.filter(
|
|
4444
|
+
(el) => el.layout.width > 0 && el.layout.height > 0
|
|
4445
|
+
);
|
|
4446
|
+
const visibleHints = generateHints(visibleElements.length, hintChars);
|
|
4447
|
+
const visibleHintMap = React15.useMemo(() => {
|
|
4448
|
+
const map = /* @__PURE__ */ new Map();
|
|
4449
|
+
visibleElements.forEach((el, i) => {
|
|
4450
|
+
if (visibleHints[i]) {
|
|
4451
|
+
map.set(visibleHints[i], el.id);
|
|
4452
|
+
}
|
|
4453
|
+
});
|
|
4454
|
+
return map;
|
|
4455
|
+
}, [visibleElements, visibleHints]);
|
|
4456
|
+
React15.useEffect(() => {
|
|
4457
|
+
if (!inputCtx || !enabled) return;
|
|
4458
|
+
const handler = (key) => {
|
|
4459
|
+
if (!isActive && !hasChildJumpNav && key.name === activationKeyParsed.name && !!key.ctrl === activationKeyParsed.ctrl && !!key.alt === activationKeyParsed.alt && !!key.shift === activationKeyParsed.shift && !!key.meta === activationKeyParsed.meta) {
|
|
4460
|
+
setIsActive(true);
|
|
4461
|
+
setInputBuffer("");
|
|
4462
|
+
return true;
|
|
4463
|
+
}
|
|
4464
|
+
if (isActive) {
|
|
4465
|
+
if (key.name === "escape") {
|
|
4466
|
+
setIsActive(false);
|
|
4467
|
+
setInputBuffer("");
|
|
4468
|
+
return true;
|
|
4469
|
+
}
|
|
4470
|
+
if (key.name === "backspace") {
|
|
4471
|
+
setInputBuffer("");
|
|
4472
|
+
return true;
|
|
4473
|
+
}
|
|
4474
|
+
if (key.sequence && key.sequence.length === 1 && /[a-z]/i.test(key.sequence)) {
|
|
4475
|
+
const newBuffer = inputBuffer + key.sequence.toLowerCase();
|
|
4476
|
+
const targetId = visibleHintMap.get(newBuffer);
|
|
4477
|
+
if (targetId) {
|
|
4478
|
+
focusCtx?.requestFocus(targetId);
|
|
4479
|
+
setIsActive(false);
|
|
4480
|
+
setInputBuffer("");
|
|
4481
|
+
return true;
|
|
4482
|
+
}
|
|
4483
|
+
const hasPartialMatch = [...visibleHintMap.keys()].some((h) => h.startsWith(newBuffer));
|
|
4484
|
+
if (hasPartialMatch) {
|
|
4485
|
+
setInputBuffer(newBuffer);
|
|
4486
|
+
return true;
|
|
4487
|
+
}
|
|
4488
|
+
setInputBuffer("");
|
|
4489
|
+
return true;
|
|
4490
|
+
}
|
|
4491
|
+
return true;
|
|
4492
|
+
}
|
|
4493
|
+
return false;
|
|
4494
|
+
};
|
|
4495
|
+
return inputCtx.subscribePriority(handler);
|
|
4496
|
+
}, [inputCtx, enabled, isActive, activationKeyParsed, inputBuffer, visibleHintMap, focusCtx, hasChildJumpNav]);
|
|
4497
|
+
const hintsOverlay = isActive ? React15__default.default.createElement(
|
|
4498
|
+
React15__default.default.Fragment,
|
|
4499
|
+
null,
|
|
4500
|
+
...visibleElements.map((el, i) => {
|
|
4501
|
+
const hint = visibleHints[i];
|
|
4502
|
+
if (!hint) return null;
|
|
4503
|
+
const { x, y } = el.layout;
|
|
4504
|
+
const isPartialMatch = hint.startsWith(inputBuffer) && inputBuffer.length > 0;
|
|
4505
|
+
return React15__default.default.createElement(
|
|
4506
|
+
"box",
|
|
4507
|
+
{
|
|
4508
|
+
key: el.id,
|
|
4509
|
+
style: {
|
|
4510
|
+
position: "absolute",
|
|
4511
|
+
top: y,
|
|
4512
|
+
left: Math.max(0, x - hint.length - 2),
|
|
4513
|
+
bg: isPartialMatch ? "cyan" : hintBg,
|
|
4514
|
+
color: hintFg,
|
|
4515
|
+
paddingX: 1,
|
|
4516
|
+
zIndex: 99999,
|
|
4517
|
+
...hintStyle
|
|
4518
|
+
}
|
|
4519
|
+
},
|
|
4520
|
+
React15__default.default.createElement("text", {
|
|
4521
|
+
style: { bold: true, color: hintFg }
|
|
4522
|
+
}, hint)
|
|
4523
|
+
);
|
|
4524
|
+
}),
|
|
4525
|
+
// Status bar at bottom
|
|
4526
|
+
React15__default.default.createElement(
|
|
4527
|
+
"box",
|
|
4528
|
+
{
|
|
4529
|
+
style: {
|
|
4530
|
+
position: "absolute",
|
|
4531
|
+
bottom: 0,
|
|
4532
|
+
left: 0,
|
|
4533
|
+
right: 0,
|
|
4534
|
+
bg: "blackBright",
|
|
4535
|
+
paddingX: 1,
|
|
4536
|
+
zIndex: 99999
|
|
4537
|
+
}
|
|
4538
|
+
},
|
|
4539
|
+
React15__default.default.createElement("text", {
|
|
4540
|
+
style: { color: "white" }
|
|
4541
|
+
}, inputBuffer ? `Jump: ${inputBuffer}_` : "Press a key to jump \u2022 ESC to cancel")
|
|
4542
|
+
)
|
|
4543
|
+
) : null;
|
|
4544
|
+
const wrappedChildren = React15__default.default.createElement(
|
|
4545
|
+
"box",
|
|
4546
|
+
{
|
|
4547
|
+
ref: (node) => {
|
|
4548
|
+
wrapperRef.current = node;
|
|
4549
|
+
},
|
|
4550
|
+
style: {
|
|
4551
|
+
// Invisible wrapper - takes full size of parent
|
|
4552
|
+
flexGrow: 1,
|
|
4553
|
+
flexDirection: "column"
|
|
4554
|
+
}
|
|
4555
|
+
},
|
|
4556
|
+
children
|
|
4557
|
+
);
|
|
4558
|
+
return React15__default.default.createElement(
|
|
4559
|
+
JumpNavContext.Provider,
|
|
4560
|
+
{ value: contextValue },
|
|
4561
|
+
wrappedChildren,
|
|
4562
|
+
hintsOverlay
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
4298
4565
|
function useFocus(nodeRef) {
|
|
4299
4566
|
const focusCtx = React15.useContext(FocusContext);
|
|
4300
4567
|
const [id] = React15.useState(() => `focus-${Math.random().toString(36).slice(2, 9)}`);
|
|
@@ -4393,6 +4660,52 @@ function useApp() {
|
|
|
4393
4660
|
}
|
|
4394
4661
|
};
|
|
4395
4662
|
}
|
|
4663
|
+
function useFocusRegistry() {
|
|
4664
|
+
const focusCtx = React15.useContext(FocusContext);
|
|
4665
|
+
const layoutCtx = React15.useContext(LayoutContext);
|
|
4666
|
+
const [elements, setElements] = React15.useState([]);
|
|
4667
|
+
const updateRef = React15.useRef(() => {
|
|
4668
|
+
});
|
|
4669
|
+
const updateElements = React15.useCallback(() => {
|
|
4670
|
+
if (!focusCtx) return;
|
|
4671
|
+
const registered = focusCtx.getRegisteredElements?.() ?? [];
|
|
4672
|
+
const mapped = registered.map(({ id, node }) => ({
|
|
4673
|
+
id,
|
|
4674
|
+
node,
|
|
4675
|
+
layout: layoutCtx?.getLayout(node) ?? node.layout,
|
|
4676
|
+
type: node.type
|
|
4677
|
+
}));
|
|
4678
|
+
mapped.sort((a, b) => {
|
|
4679
|
+
if (a.layout.y !== b.layout.y) {
|
|
4680
|
+
return a.layout.y - b.layout.y;
|
|
4681
|
+
}
|
|
4682
|
+
return a.layout.x - b.layout.x;
|
|
4683
|
+
});
|
|
4684
|
+
setElements(mapped);
|
|
4685
|
+
}, [focusCtx, layoutCtx]);
|
|
4686
|
+
updateRef.current = updateElements;
|
|
4687
|
+
React15.useEffect(() => {
|
|
4688
|
+
if (!focusCtx) return;
|
|
4689
|
+
updateElements();
|
|
4690
|
+
const unsubscribe = focusCtx.onFocusChange(() => {
|
|
4691
|
+
updateElements();
|
|
4692
|
+
});
|
|
4693
|
+
const timer = setTimeout(updateElements, 50);
|
|
4694
|
+
return () => {
|
|
4695
|
+
unsubscribe();
|
|
4696
|
+
clearTimeout(timer);
|
|
4697
|
+
};
|
|
4698
|
+
}, [focusCtx, layoutCtx, updateElements]);
|
|
4699
|
+
if (!focusCtx) return null;
|
|
4700
|
+
return {
|
|
4701
|
+
elements,
|
|
4702
|
+
focusedId: focusCtx.focusedId,
|
|
4703
|
+
requestFocus: focusCtx.requestFocus,
|
|
4704
|
+
focusNext: focusCtx.focusNext,
|
|
4705
|
+
focusPrev: focusCtx.focusPrev,
|
|
4706
|
+
refresh: () => updateRef.current()
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4396
4709
|
|
|
4397
4710
|
// src/utils/mask.ts
|
|
4398
4711
|
function parseMask(mask) {
|
|
@@ -4500,6 +4813,7 @@ exports.Checkbox = Checkbox;
|
|
|
4500
4813
|
exports.DialogHost = DialogHost;
|
|
4501
4814
|
exports.FocusScope = FocusScope;
|
|
4502
4815
|
exports.Input = Input;
|
|
4816
|
+
exports.JumpNav = JumpNav;
|
|
4503
4817
|
exports.Keybind = Keybind;
|
|
4504
4818
|
exports.List = List;
|
|
4505
4819
|
exports.Menu = Menu;
|
|
@@ -4518,6 +4832,7 @@ exports.render = render;
|
|
|
4518
4832
|
exports.useApp = useApp;
|
|
4519
4833
|
exports.useDialog = useDialog;
|
|
4520
4834
|
exports.useFocus = useFocus;
|
|
4835
|
+
exports.useFocusRegistry = useFocusRegistry;
|
|
4521
4836
|
exports.useFocusable = useFocusable;
|
|
4522
4837
|
exports.useInput = useInput;
|
|
4523
4838
|
exports.useLayout = useLayout;
|