@joewinke/jatui 0.1.28 → 0.1.29
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/LICENSE +21 -0
- package/dist/actions/collapse.js +15 -29
- package/dist/actions/listNav.js +108 -137
- package/dist/actions/railNav.js +72 -89
- package/dist/components/markup.js +59 -62
- package/dist/components/session-nav/transcriptModel.js +49 -66
- package/dist/data/emojis.js +4 -4
- package/dist/stores/confirmDialog.svelte.js +5 -5
- package/dist/stores/inputDialog.svelte.js +5 -5
- package/dist/types/booking.js +4 -4
- package/dist/types/comments.js +2 -2
- package/dist/types/messaging.js +3 -3
- package/dist/types/pipeline.js +2 -2
- package/dist/utils/currency.js +2 -4
- package/dist/utils/daisyuiColors.js +25 -27
- package/dist/utils/dateFormatters.js +44 -44
- package/dist/utils/googleMapsLoader.js +46 -100
- package/dist/utils/mentionParser.js +30 -30
- package/dist/utils/phone.js +21 -27
- package/dist/utils/phoneFormat.js +9 -9
- package/dist/utils/taskUtils.js +1 -1
- package/package.json +6 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joe Winke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/actions/collapse.js
CHANGED
|
@@ -58,34 +58,22 @@
|
|
|
58
58
|
* state (e.g. a `SvelteSet` of ids, with the id interpolating the row's own id) —
|
|
59
59
|
* one action instance attaches per row element.
|
|
60
60
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
return t;
|
|
69
|
-
};
|
|
70
|
-
return __assign.apply(this, arguments);
|
|
71
|
-
};
|
|
72
|
-
var DEFAULTS = { threshold: 120, expanded: false, fadeStart: 55, epsilon: 4 };
|
|
73
|
-
export function collapse(node, options) {
|
|
74
|
-
if (options === void 0) { options = {}; }
|
|
75
|
-
var opts = __assign(__assign({}, DEFAULTS), options);
|
|
76
|
-
var collapsible = false;
|
|
77
|
-
var rafId = 0;
|
|
78
|
-
var applied = false;
|
|
61
|
+
const DEFAULTS = { threshold: 120, expanded: false, fadeStart: 55, epsilon: 4 };
|
|
62
|
+
export function collapse(node, options = {}) {
|
|
63
|
+
let opts = { ...DEFAULTS, ...options };
|
|
64
|
+
let collapsible = false;
|
|
65
|
+
let rafId = 0;
|
|
66
|
+
let applied = false;
|
|
79
67
|
function gradient() {
|
|
80
|
-
return
|
|
68
|
+
return `linear-gradient(to bottom, black ${opts.fadeStart}%, transparent 100%)`;
|
|
81
69
|
}
|
|
82
70
|
function apply() {
|
|
83
|
-
|
|
71
|
+
const shouldCollapse = collapsible && !opts.expanded;
|
|
84
72
|
if (shouldCollapse === applied)
|
|
85
73
|
return; // idempotent — avoids RO feedback loops
|
|
86
74
|
applied = shouldCollapse;
|
|
87
75
|
if (shouldCollapse) {
|
|
88
|
-
node.style.maxHeight =
|
|
76
|
+
node.style.maxHeight = `${opts.threshold}px`;
|
|
89
77
|
node.style.overflow = 'hidden';
|
|
90
78
|
node.style.maskImage = gradient();
|
|
91
79
|
node.style.webkitMaskImage = gradient();
|
|
@@ -98,12 +86,11 @@ export function collapse(node, options) {
|
|
|
98
86
|
}
|
|
99
87
|
}
|
|
100
88
|
function measure() {
|
|
101
|
-
var _a;
|
|
102
89
|
// scrollHeight is the full content height even while collapsed.
|
|
103
|
-
|
|
90
|
+
const next = node.scrollHeight > opts.threshold + opts.epsilon;
|
|
104
91
|
if (next !== collapsible) {
|
|
105
92
|
collapsible = next;
|
|
106
|
-
|
|
93
|
+
opts.onCollapsible?.(collapsible);
|
|
107
94
|
}
|
|
108
95
|
apply();
|
|
109
96
|
}
|
|
@@ -112,13 +99,12 @@ export function collapse(node, options) {
|
|
|
112
99
|
rafId = requestAnimationFrame(measure);
|
|
113
100
|
}
|
|
114
101
|
// Re-measure when the content's natural size changes (streaming, {@html}, lazy images).
|
|
115
|
-
|
|
102
|
+
const ro = new ResizeObserver(scheduleMeasure);
|
|
116
103
|
ro.observe(node);
|
|
117
104
|
scheduleMeasure();
|
|
118
105
|
return {
|
|
119
|
-
update
|
|
120
|
-
|
|
121
|
-
opts = __assign(__assign({}, DEFAULTS), next);
|
|
106
|
+
update(next = {}) {
|
|
107
|
+
opts = { ...DEFAULTS, ...next };
|
|
122
108
|
// expanded/threshold/fade may have changed — re-measure and let
|
|
123
109
|
// apply() compare the freshly computed shouldCollapse against the
|
|
124
110
|
// real last-applied state. (Force-flipping `applied` here used to make
|
|
@@ -126,7 +112,7 @@ export function collapse(node, options) {
|
|
|
126
112
|
// silently skipping the style update.)
|
|
127
113
|
scheduleMeasure();
|
|
128
114
|
},
|
|
129
|
-
destroy
|
|
115
|
+
destroy() {
|
|
130
116
|
cancelAnimationFrame(rafId);
|
|
131
117
|
ro.disconnect();
|
|
132
118
|
},
|
package/dist/actions/listNav.js
CHANGED
|
@@ -56,32 +56,21 @@
|
|
|
56
56
|
* // ...page-specific shortcuts
|
|
57
57
|
* }
|
|
58
58
|
*/
|
|
59
|
-
var __assign = (this && this.__assign) || function () {
|
|
60
|
-
__assign = Object.assign || function(t) {
|
|
61
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
62
|
-
s = arguments[i];
|
|
63
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
64
|
-
t[p] = s[p];
|
|
65
|
-
}
|
|
66
|
-
return t;
|
|
67
|
-
};
|
|
68
|
-
return __assign.apply(this, arguments);
|
|
69
|
-
};
|
|
70
59
|
/** How long a half-typed motion (count prefix, pending `g`, pending `z`) waits
|
|
71
60
|
* for its completion before resetting. Matches Vim's default `timeoutlen`. */
|
|
72
|
-
|
|
61
|
+
const MOTION_BUFFER_TIMEOUT_MS = 1500;
|
|
73
62
|
/** Upper cap so `99999j` can't produce pathological math. Above any list size
|
|
74
63
|
* you'd reasonably navigate with j/k. */
|
|
75
|
-
|
|
64
|
+
const MAX_COUNT = 99999;
|
|
76
65
|
/** Fallback half-page size when we can't measure the viewport (SSR, jsdom
|
|
77
66
|
* without layout, items not yet attached). */
|
|
78
|
-
|
|
67
|
+
const DEFAULT_HALF_PAGE = 5;
|
|
79
68
|
function isTypingTarget(target) {
|
|
80
69
|
if (typeof HTMLElement === 'undefined')
|
|
81
70
|
return false;
|
|
82
71
|
if (!(target instanceof HTMLElement))
|
|
83
72
|
return false;
|
|
84
|
-
|
|
73
|
+
const tag = target.tagName;
|
|
85
74
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT')
|
|
86
75
|
return true;
|
|
87
76
|
return target.isContentEditable;
|
|
@@ -90,10 +79,10 @@ function hasModifier(e) {
|
|
|
90
79
|
return e.ctrlKey || e.metaKey || e.altKey;
|
|
91
80
|
}
|
|
92
81
|
function resolveItems(optsRef) {
|
|
93
|
-
|
|
82
|
+
const getItems = optsRef.current.getItems;
|
|
94
83
|
if (!getItems)
|
|
95
84
|
return [];
|
|
96
|
-
|
|
85
|
+
const result = getItems();
|
|
97
86
|
return Array.isArray(result) ? result : Array.from(result);
|
|
98
87
|
}
|
|
99
88
|
function wrapIndex(next, length, wrap) {
|
|
@@ -111,17 +100,16 @@ function wrapIndex(next, length, wrap) {
|
|
|
111
100
|
* when layout isn't available (SSR, jsdom, detached elements).
|
|
112
101
|
*/
|
|
113
102
|
function getHalfPageSize(items, currentIdx) {
|
|
114
|
-
var _a, _b;
|
|
115
103
|
if (items.length === 0)
|
|
116
104
|
return 0;
|
|
117
105
|
try {
|
|
118
|
-
|
|
106
|
+
const pivot = items[Math.max(0, Math.min(items.length - 1, currentIdx))] ?? items[0];
|
|
119
107
|
if (!pivot || typeof window === 'undefined')
|
|
120
108
|
return DEFAULT_HALF_PAGE;
|
|
121
109
|
// Walk up looking for an ancestor that is actually scrolling.
|
|
122
|
-
|
|
110
|
+
let scroller = pivot.parentElement;
|
|
123
111
|
while (scroller) {
|
|
124
|
-
|
|
112
|
+
const cs = window.getComputedStyle(scroller);
|
|
125
113
|
if ((cs.overflowY === 'auto' || cs.overflowY === 'scroll') &&
|
|
126
114
|
scroller.clientHeight > 0 &&
|
|
127
115
|
scroller.clientHeight < scroller.scrollHeight) {
|
|
@@ -129,34 +117,34 @@ function getHalfPageSize(items, currentIdx) {
|
|
|
129
117
|
}
|
|
130
118
|
scroller = scroller.parentElement;
|
|
131
119
|
}
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
const viewportHeight = scroller?.clientHeight ?? window.innerHeight;
|
|
121
|
+
const itemHeight = pivot.getBoundingClientRect().height;
|
|
134
122
|
if (viewportHeight > 0 && itemHeight > 0) {
|
|
135
|
-
|
|
123
|
+
const fullPage = Math.max(1, Math.floor(viewportHeight / itemHeight));
|
|
136
124
|
return Math.max(1, Math.floor(fullPage / 2));
|
|
137
125
|
}
|
|
138
126
|
}
|
|
139
|
-
catch
|
|
127
|
+
catch {
|
|
140
128
|
// Fall through to default.
|
|
141
129
|
}
|
|
142
130
|
return DEFAULT_HALF_PAGE;
|
|
143
131
|
}
|
|
144
132
|
function makeController(optsRef) {
|
|
145
|
-
|
|
146
|
-
|
|
133
|
+
let focused = -1;
|
|
134
|
+
let lastEl = null;
|
|
147
135
|
// Motion-buffer state. A motion is 'pending' until its follow-up key
|
|
148
136
|
// arrives (e.g. the second `g` in `gg`) or the buffer times out.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
let pendingCount = null;
|
|
138
|
+
let pendingG = false;
|
|
139
|
+
let pendingZ = false;
|
|
140
|
+
let bufferTimer = null;
|
|
153
141
|
// Selection state. `selected` holds stable ids (from getItemId / data-nav-id)
|
|
154
142
|
// rather than indices, since list filters can shift indices but ids persist.
|
|
155
143
|
// `visualMode` flips on/off via V; while on, j/k extends the range from
|
|
156
144
|
// `visualAnchor` (the index where V was first pressed) to current focus.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
const selected = new Set();
|
|
146
|
+
let visualMode = false;
|
|
147
|
+
let visualAnchor = -1;
|
|
160
148
|
function clearBufferTimer() {
|
|
161
149
|
if (bufferTimer !== null) {
|
|
162
150
|
clearTimeout(bufferTimer);
|
|
@@ -176,25 +164,22 @@ function makeController(optsRef) {
|
|
|
176
164
|
}
|
|
177
165
|
}
|
|
178
166
|
function focusedClass() {
|
|
179
|
-
|
|
180
|
-
return (_a = optsRef.current.focusedClass) !== null && _a !== void 0 ? _a : 'jk-focused';
|
|
167
|
+
return optsRef.current.focusedClass ?? 'jk-focused';
|
|
181
168
|
}
|
|
182
169
|
function selectedClass() {
|
|
183
|
-
|
|
184
|
-
return (_a = optsRef.current.selectedClass) !== null && _a !== void 0 ? _a : 'jk-selected';
|
|
170
|
+
return optsRef.current.selectedClass ?? 'jk-selected';
|
|
185
171
|
}
|
|
186
172
|
function getId(el) {
|
|
187
173
|
if (!el)
|
|
188
174
|
return null;
|
|
189
|
-
|
|
190
|
-
|
|
175
|
+
const resolver = optsRef.current.getItemId;
|
|
176
|
+
const raw = resolver ? resolver(el) : el.dataset.navId;
|
|
191
177
|
return raw == null ? null : String(raw);
|
|
192
178
|
}
|
|
193
179
|
function applySelectionClasses(items) {
|
|
194
|
-
|
|
195
|
-
for (
|
|
196
|
-
|
|
197
|
-
var id = getId(el);
|
|
180
|
+
const cls = selectedClass();
|
|
181
|
+
for (const el of items) {
|
|
182
|
+
const id = getId(el);
|
|
198
183
|
if (id !== null && selected.has(id))
|
|
199
184
|
el.classList.add(cls);
|
|
200
185
|
else
|
|
@@ -202,23 +187,21 @@ function makeController(optsRef) {
|
|
|
202
187
|
}
|
|
203
188
|
}
|
|
204
189
|
function emitSelectionChange() {
|
|
205
|
-
var _a, _b;
|
|
206
190
|
applySelectionClasses(resolveItems(optsRef));
|
|
207
|
-
|
|
191
|
+
optsRef.current.onSelectionChange?.(new Set(selected));
|
|
208
192
|
}
|
|
209
193
|
function apply(items, idx) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
var next = (_a = items[idx]) !== null && _a !== void 0 ? _a : null;
|
|
194
|
+
const cls = focusedClass();
|
|
195
|
+
const next = items[idx] ?? null;
|
|
213
196
|
if (lastEl && lastEl !== next)
|
|
214
197
|
lastEl.classList.remove(cls);
|
|
215
198
|
if (next) {
|
|
216
199
|
next.classList.add(cls);
|
|
217
|
-
|
|
200
|
+
next.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
|
|
218
201
|
}
|
|
219
202
|
lastEl = next;
|
|
220
203
|
focused = next ? idx : -1;
|
|
221
|
-
|
|
204
|
+
optsRef.current.onFocusChange?.(next, focused);
|
|
222
205
|
}
|
|
223
206
|
/**
|
|
224
207
|
* Re-derive the selected set from the inclusive range [anchor..focus] while
|
|
@@ -226,16 +209,15 @@ function makeController(optsRef) {
|
|
|
226
209
|
* V was pressed are preserved (additive — Vim's `V` extends, doesn't reset).
|
|
227
210
|
*/
|
|
228
211
|
function extendVisualRange(items) {
|
|
229
|
-
var _a;
|
|
230
212
|
if (!visualMode || visualAnchor < 0 || focused < 0)
|
|
231
213
|
return;
|
|
232
|
-
|
|
233
|
-
|
|
214
|
+
const lo = Math.min(visualAnchor, focused);
|
|
215
|
+
const hi = Math.max(visualAnchor, focused);
|
|
234
216
|
// Add every id in [lo..hi]. Items outside the range are left alone so
|
|
235
217
|
// existing pre-V selections survive moving the anchor to a new region.
|
|
236
|
-
|
|
237
|
-
for (
|
|
238
|
-
|
|
218
|
+
let mutated = false;
|
|
219
|
+
for (let i = lo; i <= hi; i++) {
|
|
220
|
+
const id = getId(items[i] ?? null);
|
|
239
221
|
if (id !== null && !selected.has(id)) {
|
|
240
222
|
selected.add(id);
|
|
241
223
|
mutated = true;
|
|
@@ -247,7 +229,7 @@ function makeController(optsRef) {
|
|
|
247
229
|
applySelectionClasses(items);
|
|
248
230
|
}
|
|
249
231
|
function focus(idx) {
|
|
250
|
-
|
|
232
|
+
const items = resolveItems(optsRef);
|
|
251
233
|
if (items.length === 0)
|
|
252
234
|
return;
|
|
253
235
|
apply(items, Math.max(0, Math.min(items.length - 1, idx)));
|
|
@@ -256,28 +238,27 @@ function makeController(optsRef) {
|
|
|
256
238
|
focus(0);
|
|
257
239
|
}
|
|
258
240
|
function focusLast() {
|
|
259
|
-
|
|
241
|
+
const items = resolveItems(optsRef);
|
|
260
242
|
if (items.length)
|
|
261
243
|
apply(items, items.length - 1);
|
|
262
244
|
}
|
|
263
245
|
function clear() {
|
|
264
|
-
var _a, _b;
|
|
265
246
|
if (lastEl)
|
|
266
247
|
lastEl.classList.remove(focusedClass());
|
|
267
248
|
lastEl = null;
|
|
268
249
|
focused = -1;
|
|
269
250
|
clearPending();
|
|
270
|
-
|
|
251
|
+
optsRef.current.onFocusChange?.(null, -1);
|
|
271
252
|
}
|
|
272
253
|
function refresh() {
|
|
273
|
-
|
|
254
|
+
const items = resolveItems(optsRef);
|
|
274
255
|
if (items.length === 0) {
|
|
275
256
|
clear();
|
|
276
257
|
return;
|
|
277
258
|
}
|
|
278
259
|
// Re-home the focused element if the old node has been removed.
|
|
279
260
|
if (lastEl && !items.includes(lastEl)) {
|
|
280
|
-
|
|
261
|
+
const newIdx = Math.min(focused, items.length - 1);
|
|
281
262
|
apply(items, Math.max(0, newIdx));
|
|
282
263
|
}
|
|
283
264
|
else if (focused >= 0 && focused < items.length) {
|
|
@@ -288,8 +269,7 @@ function makeController(optsRef) {
|
|
|
288
269
|
applySelectionClasses(items);
|
|
289
270
|
}
|
|
290
271
|
function toggleSelectionByIndex(items, idx) {
|
|
291
|
-
|
|
292
|
-
var id = getId((_a = items[idx]) !== null && _a !== void 0 ? _a : null);
|
|
272
|
+
const id = getId(items[idx] ?? null);
|
|
293
273
|
if (id === null)
|
|
294
274
|
return;
|
|
295
275
|
if (selected.has(id))
|
|
@@ -303,15 +283,14 @@ function makeController(optsRef) {
|
|
|
303
283
|
visualAnchor = -1;
|
|
304
284
|
}
|
|
305
285
|
function handleKeydown(e) {
|
|
306
|
-
|
|
307
|
-
var opts = optsRef.current;
|
|
286
|
+
const opts = optsRef.current;
|
|
308
287
|
if (opts.enabled === false)
|
|
309
288
|
return false;
|
|
310
289
|
if (isTypingTarget(e.target))
|
|
311
290
|
return false;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
291
|
+
const items = resolveItems(optsRef);
|
|
292
|
+
const wrap = opts.wraparound ?? true;
|
|
293
|
+
const selectable = opts.selectable === true;
|
|
315
294
|
// --- Selection layer (opt-in) ----------------------------------------
|
|
316
295
|
// These keys are only owned by the controller when `selectable` is true,
|
|
317
296
|
// so existing pages without a selection model keep their bindings.
|
|
@@ -334,7 +313,7 @@ function makeController(optsRef) {
|
|
|
334
313
|
toggleSelectionByIndex(items, focused);
|
|
335
314
|
// If the toggle deselected the anchor, re-add it — entering
|
|
336
315
|
// visual mode should always include the anchor.
|
|
337
|
-
|
|
316
|
+
const anchorId = getId(items[focused] ?? null);
|
|
338
317
|
if (anchorId !== null && !selected.has(anchorId)) {
|
|
339
318
|
selected.add(anchorId);
|
|
340
319
|
emitSelectionChange();
|
|
@@ -357,25 +336,20 @@ function makeController(optsRef) {
|
|
|
357
336
|
if (items.length === 0)
|
|
358
337
|
return false;
|
|
359
338
|
e.preventDefault();
|
|
360
|
-
|
|
361
|
-
for (
|
|
362
|
-
|
|
363
|
-
var id = getId(el);
|
|
339
|
+
const allIds = [];
|
|
340
|
+
for (const el of items) {
|
|
341
|
+
const id = getId(el);
|
|
364
342
|
if (id !== null)
|
|
365
343
|
allIds.push(id);
|
|
366
344
|
}
|
|
367
|
-
|
|
345
|
+
const allSelected = allIds.length > 0 && allIds.every((id) => selected.has(id));
|
|
368
346
|
if (allSelected) {
|
|
369
|
-
for (
|
|
370
|
-
var id = allIds_1[_h];
|
|
347
|
+
for (const id of allIds)
|
|
371
348
|
selected.delete(id);
|
|
372
|
-
}
|
|
373
349
|
}
|
|
374
350
|
else {
|
|
375
|
-
for (
|
|
376
|
-
var id = allIds_2[_j];
|
|
351
|
+
for (const id of allIds)
|
|
377
352
|
selected.add(id);
|
|
378
|
-
}
|
|
379
353
|
}
|
|
380
354
|
emitSelectionChange();
|
|
381
355
|
clearPending();
|
|
@@ -388,14 +362,14 @@ function makeController(optsRef) {
|
|
|
388
362
|
if (items.length === 0)
|
|
389
363
|
return false;
|
|
390
364
|
e.preventDefault();
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
365
|
+
const dir = e.key === 'J' ? 1 : -1;
|
|
366
|
+
const before = focused < 0 ? (dir > 0 ? 0 : items.length - 1) : focused;
|
|
367
|
+
const beforeId = getId(items[before] ?? null);
|
|
394
368
|
if (beforeId !== null)
|
|
395
369
|
selected.add(beforeId);
|
|
396
|
-
|
|
370
|
+
const afterIdx = Math.max(0, Math.min(items.length - 1, before + dir));
|
|
397
371
|
apply(items, afterIdx);
|
|
398
|
-
|
|
372
|
+
const afterId = getId(items[afterIdx] ?? null);
|
|
399
373
|
if (afterId !== null)
|
|
400
374
|
selected.add(afterId);
|
|
401
375
|
emitSelectionChange();
|
|
@@ -407,14 +381,14 @@ function makeController(optsRef) {
|
|
|
407
381
|
// Ctrl/Meta/Alt bailout so vim half-page works; every other modifier
|
|
408
382
|
// combination still falls through untouched.
|
|
409
383
|
if (e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) {
|
|
410
|
-
|
|
384
|
+
const lower = e.key.toLowerCase();
|
|
411
385
|
if (lower === 'd' || lower === 'u') {
|
|
412
386
|
if (items.length === 0)
|
|
413
387
|
return false;
|
|
414
388
|
e.preventDefault();
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
389
|
+
const half = getHalfPageSize(items, focused);
|
|
390
|
+
const dir = lower === 'd' ? 1 : -1;
|
|
391
|
+
const start = focused < 0 ? (dir > 0 ? 0 : items.length - 1) : focused;
|
|
418
392
|
apply(items, wrapIndex(start + dir * half, items.length, wrap));
|
|
419
393
|
clearPending();
|
|
420
394
|
return true;
|
|
@@ -429,7 +403,7 @@ function makeController(optsRef) {
|
|
|
429
403
|
if (pendingCount === null && e.key === '0')
|
|
430
404
|
return false;
|
|
431
405
|
e.preventDefault();
|
|
432
|
-
pendingCount = (pendingCount
|
|
406
|
+
pendingCount = (pendingCount ?? 0) * 10 + Number(e.key);
|
|
433
407
|
if (pendingCount > MAX_COUNT)
|
|
434
408
|
pendingCount = MAX_COUNT;
|
|
435
409
|
pendingG = false;
|
|
@@ -445,7 +419,7 @@ function makeController(optsRef) {
|
|
|
445
419
|
}
|
|
446
420
|
e.preventDefault();
|
|
447
421
|
if (pendingG) {
|
|
448
|
-
|
|
422
|
+
const target = pendingCount !== null
|
|
449
423
|
? Math.max(0, Math.min(items.length - 1, pendingCount - 1))
|
|
450
424
|
: 0;
|
|
451
425
|
apply(items, target);
|
|
@@ -465,7 +439,7 @@ function makeController(optsRef) {
|
|
|
465
439
|
return false;
|
|
466
440
|
}
|
|
467
441
|
e.preventDefault();
|
|
468
|
-
|
|
442
|
+
const target = pendingCount !== null
|
|
469
443
|
? Math.max(0, Math.min(items.length - 1, pendingCount - 1))
|
|
470
444
|
: items.length - 1;
|
|
471
445
|
apply(items, target);
|
|
@@ -476,8 +450,8 @@ function makeController(optsRef) {
|
|
|
476
450
|
if (e.key === 'z' && !e.shiftKey) {
|
|
477
451
|
e.preventDefault();
|
|
478
452
|
if (pendingZ) {
|
|
479
|
-
|
|
480
|
-
|
|
453
|
+
const el = lastEl ?? items[focused] ?? null;
|
|
454
|
+
el?.scrollIntoView?.({ block: 'center', inline: 'nearest' });
|
|
481
455
|
clearPending();
|
|
482
456
|
}
|
|
483
457
|
else {
|
|
@@ -493,10 +467,10 @@ function makeController(optsRef) {
|
|
|
493
467
|
if (items.length === 0)
|
|
494
468
|
return false;
|
|
495
469
|
e.preventDefault();
|
|
496
|
-
|
|
470
|
+
const step = pendingCount ?? 1;
|
|
497
471
|
// From an unfocused state, `{n}j` selects the n-th item
|
|
498
472
|
// (1-indexed) so it mirrors `{n}G` from the top.
|
|
499
|
-
|
|
473
|
+
const target = focused < 0 ? step - 1 : focused + step;
|
|
500
474
|
apply(items, wrapIndex(target, items.length, wrap));
|
|
501
475
|
clearPending();
|
|
502
476
|
if (selectable && visualMode)
|
|
@@ -508,8 +482,8 @@ function makeController(optsRef) {
|
|
|
508
482
|
if (items.length === 0)
|
|
509
483
|
return false;
|
|
510
484
|
e.preventDefault();
|
|
511
|
-
|
|
512
|
-
|
|
485
|
+
const step = pendingCount ?? 1;
|
|
486
|
+
const target = focused < 0 ? items.length - step : focused - step;
|
|
513
487
|
apply(items, wrapIndex(target, items.length, wrap));
|
|
514
488
|
clearPending();
|
|
515
489
|
if (selectable && visualMode)
|
|
@@ -520,12 +494,12 @@ function makeController(optsRef) {
|
|
|
520
494
|
case ' ': {
|
|
521
495
|
if (focused < 0)
|
|
522
496
|
return false;
|
|
523
|
-
|
|
497
|
+
const el = items[focused];
|
|
524
498
|
if (!el)
|
|
525
499
|
return false;
|
|
526
500
|
e.preventDefault();
|
|
527
501
|
clearPending();
|
|
528
|
-
|
|
502
|
+
opts.onSelect?.(el, focused);
|
|
529
503
|
return true;
|
|
530
504
|
}
|
|
531
505
|
case 'Escape': {
|
|
@@ -567,16 +541,16 @@ function makeController(optsRef) {
|
|
|
567
541
|
return false;
|
|
568
542
|
}
|
|
569
543
|
return {
|
|
570
|
-
handleKeydown
|
|
571
|
-
focusedIndex:
|
|
572
|
-
focus
|
|
573
|
-
focusFirst
|
|
574
|
-
focusLast
|
|
575
|
-
clear
|
|
576
|
-
refresh
|
|
577
|
-
selectedIds:
|
|
578
|
-
toggleSelection:
|
|
579
|
-
|
|
544
|
+
handleKeydown,
|
|
545
|
+
focusedIndex: () => focused,
|
|
546
|
+
focus,
|
|
547
|
+
focusFirst,
|
|
548
|
+
focusLast,
|
|
549
|
+
clear,
|
|
550
|
+
refresh,
|
|
551
|
+
selectedIds: () => new Set(selected),
|
|
552
|
+
toggleSelection: (navId) => {
|
|
553
|
+
const items = resolveItems(optsRef);
|
|
580
554
|
if (navId === undefined) {
|
|
581
555
|
if (focused < 0)
|
|
582
556
|
return;
|
|
@@ -589,32 +563,29 @@ function makeController(optsRef) {
|
|
|
589
563
|
selected.add(navId);
|
|
590
564
|
emitSelectionChange();
|
|
591
565
|
},
|
|
592
|
-
setSelection:
|
|
566
|
+
setSelection: (ids) => {
|
|
593
567
|
selected.clear();
|
|
594
|
-
for (
|
|
595
|
-
var id = ids_1[_i];
|
|
568
|
+
for (const id of ids)
|
|
596
569
|
selected.add(id);
|
|
597
|
-
}
|
|
598
570
|
emitSelectionChange();
|
|
599
571
|
},
|
|
600
|
-
clearSelection:
|
|
572
|
+
clearSelection: () => {
|
|
601
573
|
if (selected.size === 0 && !visualMode)
|
|
602
574
|
return;
|
|
603
575
|
selected.clear();
|
|
604
576
|
exitVisualMode();
|
|
605
577
|
emitSelectionChange();
|
|
606
578
|
},
|
|
607
|
-
selectAll:
|
|
608
|
-
|
|
609
|
-
for (
|
|
610
|
-
|
|
611
|
-
var id = getId(el);
|
|
579
|
+
selectAll: () => {
|
|
580
|
+
const items = resolveItems(optsRef);
|
|
581
|
+
for (const el of items) {
|
|
582
|
+
const id = getId(el);
|
|
612
583
|
if (id !== null)
|
|
613
584
|
selected.add(id);
|
|
614
585
|
}
|
|
615
586
|
emitSelectionChange();
|
|
616
587
|
},
|
|
617
|
-
isSelecting:
|
|
588
|
+
isSelecting: () => visualMode,
|
|
618
589
|
};
|
|
619
590
|
}
|
|
620
591
|
/**
|
|
@@ -622,18 +593,17 @@ function makeController(optsRef) {
|
|
|
622
593
|
* listNav to decide whether to consume the event.
|
|
623
594
|
*/
|
|
624
595
|
export function createListNav(options) {
|
|
625
|
-
|
|
596
|
+
const ref = { current: options };
|
|
626
597
|
return makeController(ref);
|
|
627
598
|
}
|
|
628
599
|
/**
|
|
629
600
|
* Svelte action — attaches a keydown listener (window by default) and handles
|
|
630
601
|
* j/k/ArrowDown/ArrowUp/Enter/Space/Escape navigation over children of `node`.
|
|
631
602
|
*/
|
|
632
|
-
export
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
var target = ref.current.global === false ? node : window;
|
|
603
|
+
export const listNav = (node, initial = {}) => {
|
|
604
|
+
const ref = { current: withDefaultGetItems(node, initial) };
|
|
605
|
+
const controller = makeController(ref);
|
|
606
|
+
let target = ref.current.global === false ? node : window;
|
|
637
607
|
function onKeydown(e) {
|
|
638
608
|
controller.handleKeydown(e);
|
|
639
609
|
}
|
|
@@ -646,27 +616,28 @@ export var listNav = function (node, initial) {
|
|
|
646
616
|
}
|
|
647
617
|
attach();
|
|
648
618
|
return {
|
|
649
|
-
update
|
|
650
|
-
|
|
651
|
-
var wasGlobal = ref.current.global !== false;
|
|
619
|
+
update(next = {}) {
|
|
620
|
+
const wasGlobal = ref.current.global !== false;
|
|
652
621
|
ref.current = withDefaultGetItems(node, next);
|
|
653
|
-
|
|
622
|
+
const isGlobal = ref.current.global !== false;
|
|
654
623
|
if (wasGlobal !== isGlobal) {
|
|
655
624
|
detach();
|
|
656
625
|
attach();
|
|
657
626
|
}
|
|
658
627
|
controller.refresh();
|
|
659
628
|
},
|
|
660
|
-
destroy
|
|
629
|
+
destroy() {
|
|
661
630
|
detach();
|
|
662
631
|
controller.clear();
|
|
663
632
|
},
|
|
664
633
|
};
|
|
665
634
|
};
|
|
666
635
|
function withDefaultGetItems(node, options) {
|
|
667
|
-
var _a;
|
|
668
636
|
if (options.getItems)
|
|
669
637
|
return options;
|
|
670
|
-
|
|
671
|
-
return
|
|
638
|
+
const selector = options.itemSelector ?? '[data-nav-id]';
|
|
639
|
+
return {
|
|
640
|
+
...options,
|
|
641
|
+
getItems: () => Array.from(node.querySelectorAll(selector)),
|
|
642
|
+
};
|
|
672
643
|
}
|