@hexdspace/react 0.1.37 → 0.1.39

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +418 -5
  2. package/dist/index.js +1741 -72
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -353,8 +353,8 @@ var NotificationHost = ({
353
353
  return isDark() ? "dark" : "light";
354
354
  }, [isDark]);
355
355
  const transition = useMemo2(() => {
356
- const config = resolvedTheme.transition;
357
- return config ? cssTransition(config) : Slide;
356
+ const config2 = resolvedTheme.transition;
357
+ return config2 ? cssTransition(config2) : Slide;
358
358
  }, [resolvedTheme.transition]);
359
359
  const style = useMemo2(
360
360
  () => ({
@@ -490,17 +490,17 @@ var AutoRefreshDecorator = class _AutoRefreshDecorator {
490
490
  this.refresh = refresh2;
491
491
  }
492
492
  static inFlightRefresh = null;
493
- async get(url, config) {
494
- return this.tryRequest(() => this.httpClient.get(url, config));
493
+ async get(url, config2) {
494
+ return this.tryRequest(() => this.httpClient.get(url, config2));
495
495
  }
496
- async post(url, data, config) {
497
- return this.tryRequest(() => this.httpClient.post(url, data, config));
496
+ async post(url, data, config2) {
497
+ return this.tryRequest(() => this.httpClient.post(url, data, config2));
498
498
  }
499
- async put(url, data, config) {
500
- return this.tryRequest(() => this.httpClient.put(url, data, config));
499
+ async put(url, data, config2) {
500
+ return this.tryRequest(() => this.httpClient.put(url, data, config2));
501
501
  }
502
- async delete(url, config) {
503
- return this.tryRequest(() => this.httpClient.delete(url, config));
502
+ async delete(url, config2) {
503
+ return this.tryRequest(() => this.httpClient.delete(url, config2));
504
504
  }
505
505
  async tryRequest(fn) {
506
506
  try {
@@ -538,21 +538,21 @@ var FetchHttpClient = class {
538
538
  constructor(baseURL = "/api") {
539
539
  this.baseURL = baseURL;
540
540
  }
541
- async get(url, config) {
542
- return this.doRequest("GET", url, void 0, config);
541
+ async get(url, config2) {
542
+ return this.doRequest("GET", url, void 0, config2);
543
543
  }
544
- async post(url, data, config) {
545
- return this.doRequest("POST", url, data, config);
544
+ async post(url, data, config2) {
545
+ return this.doRequest("POST", url, data, config2);
546
546
  }
547
- async put(url, data, config) {
548
- return this.doRequest("PUT", url, data, config);
547
+ async put(url, data, config2) {
548
+ return this.doRequest("PUT", url, data, config2);
549
549
  }
550
- async delete(url, config) {
551
- return this.doRequest("DELETE", url, void 0, config);
550
+ async delete(url, config2) {
551
+ return this.doRequest("DELETE", url, void 0, config2);
552
552
  }
553
- async doRequest(method, url, body, config) {
553
+ async doRequest(method, url, body, config2) {
554
554
  const isFormData = body instanceof FormData;
555
- const headers = { ...config?.headers };
555
+ const headers = { ...config2?.headers };
556
556
  let parsedBody;
557
557
  if (body !== void 0) {
558
558
  parsedBody = isFormData ? body : JSON.stringify(body);
@@ -564,9 +564,9 @@ var FetchHttpClient = class {
564
564
  const res = await fetch(this.resolve(url), {
565
565
  method,
566
566
  body: parsedBody,
567
- headers: { ...headers, ...config?.headers },
567
+ headers: { ...headers, ...config2?.headers },
568
568
  credentials: "include",
569
- signal: config?.signal
569
+ signal: config2?.signal
570
570
  });
571
571
  const data = await (res.headers.get("content-type")?.includes("application/json") ? res.json() : res.text());
572
572
  if (!res.ok) {
@@ -630,24 +630,24 @@ var MockHttpClient = class {
630
630
  reset() {
631
631
  this.handlers.clear();
632
632
  }
633
- get(url, config) {
634
- return this.invoke("GET", url, void 0, config);
633
+ get(url, config2) {
634
+ return this.invoke("GET", url, void 0, config2);
635
635
  }
636
- post(url, data, config) {
637
- return this.invoke("POST", url, data, config);
636
+ post(url, data, config2) {
637
+ return this.invoke("POST", url, data, config2);
638
638
  }
639
- put(url, data, config) {
640
- return this.invoke("PUT", url, data, config);
639
+ put(url, data, config2) {
640
+ return this.invoke("PUT", url, data, config2);
641
641
  }
642
- delete(url, config) {
643
- return this.invoke("DELETE", url, void 0, config);
642
+ delete(url, config2) {
643
+ return this.invoke("DELETE", url, void 0, config2);
644
644
  }
645
- invoke(method, url, data, config) {
645
+ invoke(method, url, data, config2) {
646
646
  const handler = this.handlers.get(getKey(method, url));
647
647
  if (!handler) {
648
648
  return Promise.reject(new Error(`No mock handler registered for ${method} ${url}`));
649
649
  }
650
- const result = handler({ url, data, config });
650
+ const result = handler({ url, data, config: config2 });
651
651
  return Promise.resolve(result);
652
652
  }
653
653
  };
@@ -903,7 +903,7 @@ function useAuthActions() {
903
903
  const logout = useCallback2(() => {
904
904
  authController2.logout().then(() => dispatch({ type: "LOGOUT" }));
905
905
  }, [dispatch, authController2]);
906
- const register = useCallback2(
906
+ const register2 = useCallback2(
907
907
  async (email, password, attributes) => {
908
908
  dispatch({ type: "REQUEST" });
909
909
  const res = await authController2.register(email, password, attributes);
@@ -912,7 +912,7 @@ function useAuthActions() {
912
912
  },
913
913
  [dispatch, authController2]
914
914
  );
915
- return { login, logout, register };
915
+ return { login, logout, register: register2 };
916
916
  }
917
917
  function dispatchLoginResult(res, dispatch) {
918
918
  if (res.ok) {
@@ -2032,8 +2032,8 @@ var InMemoryDialogRegistry = class {
2032
2032
 
2033
2033
  // src/feature/dialog/infra/controller/dialog-controller.ts
2034
2034
  var DialogController = class {
2035
- constructor(registry2, open2, resolve2, dismiss2, closeTop2) {
2036
- this.registry = registry2;
2035
+ constructor(registry3, open2, resolve2, dismiss2, closeTop2) {
2036
+ this.registry = registry3;
2037
2037
  this.open = open2;
2038
2038
  this.resolve = resolve2;
2039
2039
  this.dismiss = dismiss2;
@@ -2154,14 +2154,1661 @@ function useDialog() {
2154
2154
  );
2155
2155
  }
2156
2156
 
2157
+ // src/util/math-util.ts
2158
+ function clamp(n, min, max) {
2159
+ return Math.max(min, Math.min(max, n));
2160
+ }
2161
+
2162
+ // src/feature/selection/application/use-case/handle-selection-use-case.ts
2163
+ var DEFAULT_CLEAR_ARGS = {
2164
+ clearToggles: true,
2165
+ exitSelectMode: true,
2166
+ clearAnchor: true
2167
+ };
2168
+ var HandleSelectionUseCase = class {
2169
+ constructor(store) {
2170
+ this.store = store;
2171
+ }
2172
+ pushScope(scopeId) {
2173
+ const prev = this.store.get();
2174
+ const next = cloneState(prev);
2175
+ ensureScope(next, scopeId);
2176
+ removeFromStack(next.scopeStack, scopeId);
2177
+ next.scopeStack.push(scopeId);
2178
+ this.store.set(next);
2179
+ }
2180
+ popScope() {
2181
+ const prev = this.store.get();
2182
+ const next = cloneState(prev);
2183
+ const popped = next.scopeStack.pop() ?? null;
2184
+ this.store.set(next);
2185
+ return popped;
2186
+ }
2187
+ setActiveScope(scopeId) {
2188
+ const prev = this.store.get();
2189
+ const next = cloneState(prev);
2190
+ ensureScope(next, scopeId);
2191
+ removeFromStack(next.scopeStack, scopeId);
2192
+ next.scopeStack.push(scopeId);
2193
+ this.store.set(next);
2194
+ }
2195
+ ensureScope(scopeId) {
2196
+ const prev = this.store.get();
2197
+ const next = cloneState(prev);
2198
+ ensureScope(next, scopeId);
2199
+ this.store.set(next);
2200
+ }
2201
+ registerRegion(args) {
2202
+ const prev = this.store.get();
2203
+ const next = cloneState(prev);
2204
+ const scope = ensureScope(next, args.key.scopeId);
2205
+ const region = ensureRegion(scope, args.key.regionId);
2206
+ const hasInitialCursor = args.initialCursor !== void 0;
2207
+ if (hasInitialCursor) {
2208
+ region.cursor = args.initialCursor ?? null;
2209
+ }
2210
+ const items = args.getRegionItems();
2211
+ const previousItems = prev.items[toItemsKey(args.key)];
2212
+ const fallback = args.initialCursor ? items.includes(args.initialCursor) ? { kind: "id", id: args.initialCursor } : { kind: "first" } : void 0;
2213
+ reconcileRegionState(region, items, fallback, previousItems);
2214
+ if (args.makeActive) {
2215
+ scope.activeRegion = args.key.regionId;
2216
+ }
2217
+ next.items[toItemsKey(args.key)] = items;
2218
+ this.store.set(next);
2219
+ return (fallback2) => {
2220
+ this.refreshRegion(args.key, args.getRegionItems, fallback2);
2221
+ };
2222
+ }
2223
+ unregisterRegion(key) {
2224
+ const prev = this.store.get();
2225
+ const next = cloneState(prev);
2226
+ const scope = next.scopes[key.scopeId];
2227
+ if (!scope) {
2228
+ return;
2229
+ }
2230
+ const wasActive = scope.activeRegion === key.regionId;
2231
+ delete scope.regions[key.regionId];
2232
+ delete next.items[toItemsKey(key)];
2233
+ if (wasActive) {
2234
+ scope.activeRegion = null;
2235
+ if (scope.mode === "select") {
2236
+ scope.mode = "move";
2237
+ }
2238
+ }
2239
+ this.store.set(next);
2240
+ }
2241
+ setActiveRegion(key) {
2242
+ const prev = this.store.get();
2243
+ const next = cloneState(prev);
2244
+ const scope = ensureScope(next, key.scopeId);
2245
+ ensureRegion(scope, key.regionId);
2246
+ if (scope.activeRegion && scope.activeRegion !== key.regionId) {
2247
+ const prevRegion = scope.regions[scope.activeRegion];
2248
+ if (prevRegion) {
2249
+ prevRegion.cursor = null;
2250
+ }
2251
+ if (scope.mode === "select") {
2252
+ scope.mode = "move";
2253
+ for (const region of Object.values(scope.regions)) {
2254
+ region.selectAnchor = null;
2255
+ }
2256
+ }
2257
+ }
2258
+ scope.activeRegion = key.regionId;
2259
+ this.store.set(next);
2260
+ }
2261
+ setCursor(key, itemId) {
2262
+ const prev = this.store.get();
2263
+ const next = cloneState(prev);
2264
+ const scope = ensureScope(next, key.scopeId);
2265
+ const region = ensureRegion(scope, key.regionId);
2266
+ region.cursor = itemId;
2267
+ this.store.set(next);
2268
+ }
2269
+ moveCursor(key, delta) {
2270
+ const prev = this.store.get();
2271
+ const next = cloneState(prev);
2272
+ const scope = ensureScope(next, key.scopeId);
2273
+ const region = ensureRegion(scope, key.regionId);
2274
+ const items = getStoredItems(prev, key);
2275
+ if (items.length === 0) {
2276
+ region.cursor = null;
2277
+ this.store.set(next);
2278
+ return;
2279
+ }
2280
+ const index = region.cursor ? getIndex(items, region.cursor) : null;
2281
+ if (index === null) {
2282
+ if (delta === 0) {
2283
+ this.store.set(next);
2284
+ return;
2285
+ }
2286
+ region.cursor = delta > 0 ? items[0] : items[items.length - 1];
2287
+ this.store.set(next);
2288
+ return;
2289
+ }
2290
+ const nextIndex = clamp(index + delta, 0, items.length - 1);
2291
+ region.cursor = items[nextIndex];
2292
+ this.store.set(next);
2293
+ }
2294
+ enterSelectMode(key) {
2295
+ const prev = this.store.get();
2296
+ const next = cloneState(prev);
2297
+ const scope = ensureScope(next, key.scopeId);
2298
+ const region = ensureRegion(scope, key.regionId);
2299
+ if (!region.cursor) {
2300
+ const items = getStoredItems(prev, key);
2301
+ region.cursor = getFallbackCursor(items, { kind: "first" });
2302
+ }
2303
+ removeFromStack(next.scopeStack, key.scopeId);
2304
+ next.scopeStack.push(key.scopeId);
2305
+ scope.mode = "select";
2306
+ scope.activeRegion = key.regionId;
2307
+ region.selectAnchor = region.cursor;
2308
+ this.store.set(next);
2309
+ }
2310
+ exitSelectMode(scopeId) {
2311
+ const prev = this.store.get();
2312
+ const next = cloneState(prev);
2313
+ const scope = ensureScope(next, scopeId);
2314
+ scope.mode = "move";
2315
+ for (const region of Object.values(scope.regions)) {
2316
+ region.selectAnchor = null;
2317
+ }
2318
+ this.store.set(next);
2319
+ }
2320
+ toggleItem(key, itemId) {
2321
+ const prev = this.store.get();
2322
+ const targetId = itemId ?? prev.scopes[key.scopeId]?.regions[key.regionId]?.cursor ?? null;
2323
+ if (!targetId) {
2324
+ return;
2325
+ }
2326
+ const next = cloneState(prev);
2327
+ const scope = ensureScope(next, key.scopeId);
2328
+ const region = ensureRegion(scope, key.regionId);
2329
+ if (region.toggled.has(targetId)) {
2330
+ region.toggled.delete(targetId);
2331
+ } else {
2332
+ region.toggled.add(targetId);
2333
+ }
2334
+ this.store.set(next);
2335
+ }
2336
+ clearSelection(target, args) {
2337
+ const prev = this.store.get();
2338
+ const next = cloneState(prev);
2339
+ const opts = { ...DEFAULT_CLEAR_ARGS, ...args };
2340
+ if (target.kind === "activeScope") {
2341
+ const scopeId = getActiveScopeId(next);
2342
+ if (!scopeId) {
2343
+ return;
2344
+ }
2345
+ const scope = next.scopes[scopeId];
2346
+ if (!scope) {
2347
+ return;
2348
+ }
2349
+ clearScope(scope, opts);
2350
+ this.store.set(next);
2351
+ return;
2352
+ }
2353
+ if (target.kind === "scope") {
2354
+ const scope = next.scopes[target.scopeId];
2355
+ if (!scope) {
2356
+ return;
2357
+ }
2358
+ clearScope(scope, opts);
2359
+ this.store.set(next);
2360
+ return;
2361
+ }
2362
+ if (target.kind === "activeRegion") {
2363
+ const scopeId = getActiveScopeId(next);
2364
+ if (!scopeId) {
2365
+ return;
2366
+ }
2367
+ const scope = next.scopes[scopeId];
2368
+ const regionId = scope?.activeRegion;
2369
+ if (!scope || !regionId) {
2370
+ return;
2371
+ }
2372
+ clearRegion(scope, regionId, opts);
2373
+ this.store.set(next);
2374
+ return;
2375
+ }
2376
+ if (target.kind === "region") {
2377
+ const scope = next.scopes[target.key.scopeId];
2378
+ if (!scope) {
2379
+ return;
2380
+ }
2381
+ clearRegion(scope, target.key.regionId, opts);
2382
+ this.store.set(next);
2383
+ }
2384
+ }
2385
+ refreshRegion(key, getRegionItems, fallbackOverride) {
2386
+ const prev = this.store.get();
2387
+ const prevScope = prev.scopes[key.scopeId];
2388
+ if (!prevScope || !prevScope.regions[key.regionId]) {
2389
+ return;
2390
+ }
2391
+ const next = cloneState(prev);
2392
+ const scope = ensureScope(next, key.scopeId);
2393
+ const region = ensureRegion(scope, key.regionId);
2394
+ const items = getRegionItems();
2395
+ const previousItems = prev.items[toItemsKey(key)];
2396
+ const fallback = fallbackOverride ?? (region.cursor ? { kind: "nearestTo", id: region.cursor } : void 0);
2397
+ reconcileRegionState(region, items, fallback, previousItems);
2398
+ next.items[toItemsKey(key)] = items;
2399
+ this.store.set(next);
2400
+ }
2401
+ };
2402
+ function cloneState(prev) {
2403
+ const scopes2 = {};
2404
+ for (const [scopeId, scope] of Object.entries(prev.scopes)) {
2405
+ const regions = {};
2406
+ for (const [regionId, region] of Object.entries(scope.regions)) {
2407
+ regions[regionId] = {
2408
+ cursor: region.cursor,
2409
+ toggled: new Set(region.toggled),
2410
+ selectAnchor: region.selectAnchor
2411
+ };
2412
+ }
2413
+ scopes2[scopeId] = {
2414
+ activeRegion: scope.activeRegion,
2415
+ mode: scope.mode,
2416
+ regions
2417
+ };
2418
+ }
2419
+ return {
2420
+ scopeStack: [...prev.scopeStack],
2421
+ scopes: scopes2,
2422
+ items: { ...prev.items }
2423
+ };
2424
+ }
2425
+ function ensureScope(state, scopeId) {
2426
+ if (!state.scopes[scopeId]) {
2427
+ state.scopes[scopeId] = {
2428
+ activeRegion: null,
2429
+ mode: "move",
2430
+ regions: {}
2431
+ };
2432
+ }
2433
+ return state.scopes[scopeId];
2434
+ }
2435
+ function ensureRegion(scope, regionId) {
2436
+ if (!scope.regions[regionId]) {
2437
+ scope.regions[regionId] = {
2438
+ cursor: null,
2439
+ toggled: /* @__PURE__ */ new Set(),
2440
+ selectAnchor: null
2441
+ };
2442
+ }
2443
+ return scope.regions[regionId];
2444
+ }
2445
+ function removeFromStack(stack, scopeId) {
2446
+ const index = stack.indexOf(scopeId);
2447
+ if (index >= 0) {
2448
+ stack.splice(index, 1);
2449
+ }
2450
+ }
2451
+ function getActiveScopeId(state) {
2452
+ return state.scopeStack.length > 0 ? state.scopeStack[state.scopeStack.length - 1] : null;
2453
+ }
2454
+ function clearScope(scope, args) {
2455
+ if (args.exitSelectMode) {
2456
+ scope.mode = "move";
2457
+ }
2458
+ for (const region of Object.values(scope.regions)) {
2459
+ if (args.clearToggles) {
2460
+ region.toggled = /* @__PURE__ */ new Set();
2461
+ }
2462
+ if (args.clearAnchor) {
2463
+ region.selectAnchor = null;
2464
+ }
2465
+ }
2466
+ }
2467
+ function clearRegion(scope, regionId, args) {
2468
+ if (args.exitSelectMode) {
2469
+ scope.mode = "move";
2470
+ }
2471
+ const region = scope.regions[regionId];
2472
+ if (!region) {
2473
+ return;
2474
+ }
2475
+ if (args.clearToggles) {
2476
+ region.toggled = /* @__PURE__ */ new Set();
2477
+ }
2478
+ if (args.clearAnchor) {
2479
+ region.selectAnchor = null;
2480
+ }
2481
+ }
2482
+ function reconcileRegionState(region, items, fallback, previousItems) {
2483
+ if (items.length === 0) {
2484
+ region.cursor = null;
2485
+ region.selectAnchor = null;
2486
+ region.toggled = /* @__PURE__ */ new Set();
2487
+ return;
2488
+ }
2489
+ const itemSet = new Set(items);
2490
+ if (!region.cursor) {
2491
+ region.cursor = getFallbackCursor(items, fallback, previousItems);
2492
+ } else if (!itemSet.has(region.cursor)) {
2493
+ region.cursor = getFallbackCursor(items, fallback, previousItems);
2494
+ }
2495
+ if (region.selectAnchor && !itemSet.has(region.selectAnchor)) {
2496
+ region.selectAnchor = null;
2497
+ }
2498
+ if (region.toggled.size > 0) {
2499
+ for (const id of region.toggled) {
2500
+ if (!itemSet.has(id)) {
2501
+ region.toggled.delete(id);
2502
+ }
2503
+ }
2504
+ }
2505
+ }
2506
+ function getFallbackCursor(items, fallback, previousItems) {
2507
+ if (!fallback || fallback.kind === "none") {
2508
+ return null;
2509
+ }
2510
+ if (fallback.kind === "first") {
2511
+ return items[0] ?? null;
2512
+ }
2513
+ if (fallback.kind === "last") {
2514
+ return items[items.length - 1] ?? null;
2515
+ }
2516
+ if (fallback.kind === "id") {
2517
+ return items.includes(fallback.id) ? fallback.id : null;
2518
+ }
2519
+ if (fallback.kind === "nth") {
2520
+ const idx = clamp(fallback.idx, 0, items.length - 1);
2521
+ return items[idx] ?? null;
2522
+ }
2523
+ if (fallback.kind === "nearestTo") {
2524
+ return getNearestCursor(items, fallback.id, previousItems);
2525
+ }
2526
+ if (fallback.kind === "sameIndex") {
2527
+ return null;
2528
+ }
2529
+ throw new Error(`Invalid cursor fallback type of: ${JSON.stringify(fallback)} did not match any handler.`);
2530
+ }
2531
+ function getNearestCursor(items, id, previousItems) {
2532
+ if (items.includes(id)) {
2533
+ return id;
2534
+ }
2535
+ const prevList = previousItems ?? [];
2536
+ const prevIndex = prevList.indexOf(id);
2537
+ if (prevIndex < 0) {
2538
+ return null;
2539
+ }
2540
+ const itemSet = new Set(items);
2541
+ for (let offset = 1; offset < prevList.length; offset += 1) {
2542
+ const forwardIndex = prevIndex + offset;
2543
+ if (forwardIndex < prevList.length) {
2544
+ const forwardId = prevList[forwardIndex];
2545
+ if (forwardId && itemSet.has(forwardId)) {
2546
+ return forwardId;
2547
+ }
2548
+ }
2549
+ const backwardIndex = prevIndex - offset;
2550
+ if (backwardIndex >= 0) {
2551
+ const backwardId = prevList[backwardIndex];
2552
+ if (backwardId && itemSet.has(backwardId)) {
2553
+ return backwardId;
2554
+ }
2555
+ }
2556
+ }
2557
+ return null;
2558
+ }
2559
+ function getIndex(items, itemId) {
2560
+ const index = items.indexOf(itemId);
2561
+ return index >= 0 ? index : null;
2562
+ }
2563
+ function toItemsKey(key) {
2564
+ return `${key.scopeId}:${key.regionId}`;
2565
+ }
2566
+ function getStoredItems(state, key) {
2567
+ return state.items[toItemsKey(key)] ?? [];
2568
+ }
2569
+
2570
+ // src/feature/selection/application/use-case/query-selection-use-case.ts
2571
+ var QuerySelectionUseCase = class {
2572
+ constructor(store, combineMode = "xor") {
2573
+ this.store = store;
2574
+ this.combineMode = combineMode;
2575
+ }
2576
+ getActiveScope() {
2577
+ return getActiveScopeId2(this.store.get());
2578
+ }
2579
+ getActiveRegion(scopeId) {
2580
+ const scope = this.store.get().scopes[scopeId];
2581
+ if (!scope || !scope.activeRegion) {
2582
+ return null;
2583
+ }
2584
+ return { scopeId, regionId: scope.activeRegion };
2585
+ }
2586
+ getMode(scopeId) {
2587
+ return this.store.get().scopes[scopeId]?.mode ?? "move";
2588
+ }
2589
+ getCursor(key) {
2590
+ return this.store.get().scopes[key.scopeId]?.regions[key.regionId]?.cursor ?? null;
2591
+ }
2592
+ isSelected(key, itemId) {
2593
+ const state = this.store.get();
2594
+ const selection = getRegionSelection(state, key);
2595
+ if (!selection) {
2596
+ return false;
2597
+ }
2598
+ const scopeMode = state.scopes[key.scopeId]?.mode ?? "move";
2599
+ const items = getStoredItems2(state, key);
2600
+ return isSelectedItem(items, selection, itemId, scopeMode, this.combineMode);
2601
+ }
2602
+ getSelectedIds(key) {
2603
+ const state = this.store.get();
2604
+ const selection = getRegionSelection(state, key);
2605
+ if (!selection) {
2606
+ return [];
2607
+ }
2608
+ const scopeMode = state.scopes[key.scopeId]?.mode ?? "move";
2609
+ const items = getStoredItems2(state, key);
2610
+ return getSelectedIds(items, selection, scopeMode, this.combineMode);
2611
+ }
2612
+ getToggledIds(key) {
2613
+ const selection = getRegionSelection(this.store.get(), key);
2614
+ if (!selection) {
2615
+ return [];
2616
+ }
2617
+ const items = getStoredItems2(this.store.get(), key);
2618
+ return items.filter((id) => selection.toggled.has(id));
2619
+ }
2620
+ getRangeIds(key) {
2621
+ const state = this.store.get();
2622
+ const selection = getRegionSelection(state, key);
2623
+ if (!selection) {
2624
+ return [];
2625
+ }
2626
+ const scopeMode = state.scopes[key.scopeId]?.mode ?? "move";
2627
+ if (scopeMode !== "select") {
2628
+ return [];
2629
+ }
2630
+ const items = getStoredItems2(state, key);
2631
+ return getRangeIds(items, selection.selectAnchor, selection.cursor);
2632
+ }
2633
+ };
2634
+ function getRegionSelection(state, key) {
2635
+ return state.scopes[key.scopeId]?.regions[key.regionId] ?? null;
2636
+ }
2637
+ function getActiveScopeId2(state) {
2638
+ return state.scopeStack.length > 0 ? state.scopeStack[state.scopeStack.length - 1] : null;
2639
+ }
2640
+ function getStoredItems2(state, key) {
2641
+ return state.items[`${key.scopeId}:${key.regionId}`] ?? [];
2642
+ }
2643
+ function getRangeIds(items, anchor, cursor) {
2644
+ if (!anchor || !cursor) {
2645
+ return [];
2646
+ }
2647
+ const anchorIndex = items.indexOf(anchor);
2648
+ const cursorIndex = items.indexOf(cursor);
2649
+ if (anchorIndex < 0 || cursorIndex < 0) {
2650
+ return [];
2651
+ }
2652
+ const start = Math.min(anchorIndex, cursorIndex);
2653
+ const end = Math.max(anchorIndex, cursorIndex);
2654
+ return items.slice(start, end + 1);
2655
+ }
2656
+ function getSelectedIds(items, selection, mode, combineMode) {
2657
+ if (mode === "move") {
2658
+ if (selection.toggled.size === 0) {
2659
+ return [];
2660
+ }
2661
+ return items.filter((id) => selection.toggled.has(id));
2662
+ }
2663
+ const rangeIds = new Set(getRangeIds(items, selection.selectAnchor, selection.cursor));
2664
+ if (selection.toggled.size === 0 && rangeIds.size === 0) {
2665
+ return [];
2666
+ }
2667
+ return items.filter(
2668
+ (id) => combineMode === "union" ? rangeIds.has(id) || selection.toggled.has(id) : rangeIds.has(id) !== selection.toggled.has(id)
2669
+ );
2670
+ }
2671
+ function isSelectedItem(items, selection, itemId, mode, combineMode) {
2672
+ if (mode === "move") {
2673
+ return selection.toggled.has(itemId);
2674
+ }
2675
+ const rangeIds = new Set(getRangeIds(items, selection.selectAnchor, selection.cursor));
2676
+ return combineMode === "union" ? rangeIds.has(itemId) || selection.toggled.has(itemId) : rangeIds.has(itemId) !== selection.toggled.has(itemId);
2677
+ }
2678
+
2679
+ // src/feature/selection/infra/in-memory-selection-store.ts
2680
+ var InMemorySelectionStore = class {
2681
+ constructor(state = { scopeStack: [], scopes: {}, items: {} }) {
2682
+ this.state = state;
2683
+ }
2684
+ get() {
2685
+ return this.state;
2686
+ }
2687
+ set(next) {
2688
+ this.state = next;
2689
+ }
2690
+ };
2691
+
2692
+ // src/feature/selection/infra/controller/selection-controller.ts
2693
+ var SelectionController = class {
2694
+ handleSelection;
2695
+ querySelection;
2696
+ selectionStore;
2697
+ subscribeFn;
2698
+ version = 0;
2699
+ constructor(deps) {
2700
+ this.handleSelection = deps.handleSelection;
2701
+ this.querySelection = deps.querySelection;
2702
+ this.selectionStore = deps.selectionStore;
2703
+ this.subscribeFn = createSubscriptionAdapter(this.selectionStore, () => {
2704
+ this.version += 1;
2705
+ });
2706
+ }
2707
+ registerRegion(args) {
2708
+ return this.handleSelection.registerRegion(args);
2709
+ }
2710
+ pushScope(scopeId) {
2711
+ this.handleSelection.pushScope(scopeId);
2712
+ }
2713
+ popScope() {
2714
+ return this.handleSelection.popScope();
2715
+ }
2716
+ setActiveScope(scopeId) {
2717
+ this.handleSelection.setActiveScope(scopeId);
2718
+ }
2719
+ ensureScope(scopeId) {
2720
+ this.handleSelection.ensureScope(scopeId);
2721
+ }
2722
+ unregisterRegion(key) {
2723
+ this.handleSelection.unregisterRegion(key);
2724
+ }
2725
+ setActiveRegion(key) {
2726
+ this.handleSelection.setActiveRegion(key);
2727
+ }
2728
+ setCursor(key, itemId) {
2729
+ this.handleSelection.setCursor(key, itemId);
2730
+ }
2731
+ moveCursor(key, delta) {
2732
+ this.handleSelection.moveCursor(key, delta);
2733
+ }
2734
+ enterSelectMode(key) {
2735
+ this.handleSelection.enterSelectMode(key);
2736
+ }
2737
+ exitSelectMode(scopeId) {
2738
+ this.handleSelection.exitSelectMode(scopeId);
2739
+ }
2740
+ toggleItem(key, itemId) {
2741
+ this.handleSelection.toggleItem(key, itemId);
2742
+ }
2743
+ clearSelection(target, args) {
2744
+ this.handleSelection.clearSelection(target, args);
2745
+ }
2746
+ getActiveScope() {
2747
+ return this.querySelection.getActiveScope();
2748
+ }
2749
+ getActiveRegion(scopeId) {
2750
+ return this.querySelection.getActiveRegion(scopeId);
2751
+ }
2752
+ getActiveItem(key) {
2753
+ const resolved = this.resolveKey(key);
2754
+ if (!resolved) {
2755
+ return null;
2756
+ }
2757
+ return this.querySelection.getCursor(resolved);
2758
+ }
2759
+ getItems(key) {
2760
+ const resolved = this.resolveKey(key);
2761
+ if (!resolved) {
2762
+ return [];
2763
+ }
2764
+ return this.selectionStore.get().items[toItemsKey2(resolved)] ?? [];
2765
+ }
2766
+ getSelectedIds(key) {
2767
+ const resolved = this.resolveKey(key);
2768
+ if (!resolved) {
2769
+ return [];
2770
+ }
2771
+ return this.querySelection.getSelectedIds(resolved);
2772
+ }
2773
+ getToggledIds(key) {
2774
+ const resolved = this.resolveKey(key);
2775
+ if (!resolved) {
2776
+ return [];
2777
+ }
2778
+ return this.querySelection.getToggledIds(resolved);
2779
+ }
2780
+ getRangeIds(key) {
2781
+ const resolved = this.resolveKey(key);
2782
+ if (!resolved) {
2783
+ return [];
2784
+ }
2785
+ return this.querySelection.getRangeIds(resolved);
2786
+ }
2787
+ isSelected(key, itemId) {
2788
+ return this.querySelection.isSelected(key, itemId);
2789
+ }
2790
+ getMode(scopeId) {
2791
+ return this.querySelection.getMode(scopeId);
2792
+ }
2793
+ getQuery() {
2794
+ return this.querySelection;
2795
+ }
2796
+ getStore() {
2797
+ return this.selectionStore;
2798
+ }
2799
+ getHandle() {
2800
+ return this.handleSelection;
2801
+ }
2802
+ subscribe(listener) {
2803
+ return this.subscribeFn(listener);
2804
+ }
2805
+ getVersion() {
2806
+ return this.version;
2807
+ }
2808
+ resolveKey(key) {
2809
+ if (key) {
2810
+ return key;
2811
+ }
2812
+ const scopeId = this.querySelection.getActiveScope();
2813
+ if (!scopeId) {
2814
+ return null;
2815
+ }
2816
+ return this.querySelection.getActiveRegion(scopeId);
2817
+ }
2818
+ };
2819
+ function toItemsKey2(key) {
2820
+ return `${key.scopeId}:${key.regionId}`;
2821
+ }
2822
+ var createSelectionController = controllerFactory(
2823
+ (deps) => new SelectionController(deps),
2824
+ (overrides) => {
2825
+ const hasCustomUseCases = !!overrides.handleSelection || !!overrides.querySelection;
2826
+ if (hasCustomUseCases && !overrides.selectionStore) {
2827
+ throw new Error("createSelectionController requires selectionStore when overriding use cases");
2828
+ }
2829
+ const selectionStore = overrides.selectionStore ?? new InMemorySelectionStore();
2830
+ const handleSelection = overrides.handleSelection ?? new HandleSelectionUseCase(selectionStore);
2831
+ const querySelection = overrides.querySelection ?? new QuerySelectionUseCase(selectionStore);
2832
+ return {
2833
+ handleSelection,
2834
+ querySelection,
2835
+ selectionStore
2836
+ };
2837
+ }
2838
+ );
2839
+ function createSubscriptionAdapter(store, onChange) {
2840
+ const maybeStore = store;
2841
+ if (typeof maybeStore.subscribe === "function") {
2842
+ return (listener) => maybeStore.subscribe?.(() => {
2843
+ onChange();
2844
+ listener();
2845
+ }) ?? (() => {
2846
+ });
2847
+ }
2848
+ const listeners = /* @__PURE__ */ new Set();
2849
+ const originalSet = store.set.bind(store);
2850
+ store.set = (next) => {
2851
+ originalSet(next);
2852
+ onChange();
2853
+ for (const listener of listeners) {
2854
+ listener();
2855
+ }
2856
+ };
2857
+ return (listener) => {
2858
+ listeners.add(listener);
2859
+ return () => {
2860
+ listeners.delete(listener);
2861
+ };
2862
+ };
2863
+ }
2864
+
2865
+ // src/feature/selection/infra/web/react/SelectionControllerProvider.tsx
2866
+ import { createContext as createContext3, useContext as useContext4 } from "react";
2867
+ import { jsx as jsx12 } from "react/jsx-runtime";
2868
+ var SelectionControllerCtx = createContext3(null);
2869
+ function SelectionControllerProvider({ children, controller }) {
2870
+ return /* @__PURE__ */ jsx12(SelectionControllerCtx.Provider, { value: controller, children });
2871
+ }
2872
+ function useSelectionController() {
2873
+ const controller = useContext4(SelectionControllerCtx);
2874
+ if (!controller) throw new Error("useSelectionController must be used within <SelectionControllerProvider>");
2875
+ return controller;
2876
+ }
2877
+
2878
+ // src/feature/keyboard/entity/scope-manager.ts
2879
+ var ScopeManager = class {
2880
+ scopes = /* @__PURE__ */ new Map();
2881
+ define(id, priority, exclusive = false) {
2882
+ if (!this.scopes.has(id)) {
2883
+ this.scopes.set(id, { id, enabled: false, refCount: 0, priority, exclusive });
2884
+ }
2885
+ }
2886
+ enable(id) {
2887
+ const s = this.scopes.get(id);
2888
+ if (!s) return;
2889
+ const nextRefCount = (s.refCount ?? 0) + 1;
2890
+ s.refCount = nextRefCount;
2891
+ s.enabled = nextRefCount > 0;
2892
+ this.scopes.set(id, s);
2893
+ }
2894
+ disable(id) {
2895
+ const s = this.scopes.get(id);
2896
+ if (!s) return;
2897
+ const nextRefCount = Math.max(0, (s.refCount ?? 0) - 1);
2898
+ s.refCount = nextRefCount;
2899
+ s.enabled = nextRefCount > 0;
2900
+ this.scopes.set(id, s);
2901
+ }
2902
+ isAnyExclusiveActiveAbove(priority) {
2903
+ for (const s of this.scopes.values()) {
2904
+ if (s.enabled && s.exclusive && s.priority > priority) return true;
2905
+ }
2906
+ return false;
2907
+ }
2908
+ isScopeActive(id) {
2909
+ const s = this.scopes.get(id);
2910
+ if (!s || !s.enabled) return false;
2911
+ return !this.isAnyExclusiveActiveAbove(s.priority);
2912
+ }
2913
+ getState(id) {
2914
+ return this.scopes.get(id) ?? null;
2915
+ }
2916
+ };
2917
+
2918
+ // src/feature/keyboard/application/use-case/enable-scope-use-case.ts
2919
+ var EnableScopeUseCase = class {
2920
+ constructor(scopes2) {
2921
+ this.scopes = scopes2;
2922
+ }
2923
+ enableScope(id) {
2924
+ this.scopes.enable(id);
2925
+ }
2926
+ };
2927
+
2928
+ // src/feature/keyboard/application/use-case/disable-scope-use-case.ts
2929
+ var DisableScopeUseCase = class {
2930
+ constructor(scopes2) {
2931
+ this.scopes = scopes2;
2932
+ }
2933
+ disableScope(id) {
2934
+ this.scopes.disable(id);
2935
+ }
2936
+ };
2937
+
2938
+ // src/feature/keyboard/application/use-case/list-shortcuts-use-case.ts
2939
+ var ListShortcutsUseCase = class {
2940
+ constructor(registry3) {
2941
+ this.registry = registry3;
2942
+ }
2943
+ listShortcutsByScope() {
2944
+ return this.registry.snapshotGroupedByScope();
2945
+ }
2946
+ };
2947
+
2948
+ // src/feature/keyboard/application/use-case/register-shortcut-use-case.ts
2949
+ import { v4 as uuid2 } from "uuid";
2950
+ var RegisterShortcutUseCase = class {
2951
+ constructor(engine2, scopes2, registry3) {
2952
+ this.engine = engine2;
2953
+ this.scopes = scopes2;
2954
+ this.registry = registry3;
2955
+ }
2956
+ store = /* @__PURE__ */ new Map();
2957
+ registerShortcut(regInput) {
2958
+ const id = uuid2();
2959
+ const reg = { preventDefault: true, ignoreTyping: true, ...regInput, id };
2960
+ const guard = (e) => {
2961
+ if (reg.ignoreTyping && isTyping(e)) return false;
2962
+ return this.scopes.isScopeActive(reg.scopeId);
2963
+ };
2964
+ const handle = this.engine.register(reg.bindings, { guard, preventDefault: reg.preventDefault });
2965
+ this.registry.addMany(
2966
+ reg.bindings.map((b) => ({
2967
+ registrationId: id,
2968
+ scopeId: reg.scopeId,
2969
+ combos: b.combos,
2970
+ description: b.description,
2971
+ group: b.group
2972
+ }))
2973
+ );
2974
+ this.store.set(id, { handle, reg });
2975
+ return id;
2976
+ }
2977
+ unregister(id) {
2978
+ const item = this.store.get(id);
2979
+ if (!item) return;
2980
+ item.handle.dispose();
2981
+ this.store.delete(id);
2982
+ this.registry.removeByRegistration(id);
2983
+ }
2984
+ };
2985
+ function isTyping(e) {
2986
+ const el = e.target;
2987
+ if (!el) return false;
2988
+ const inText = el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable;
2989
+ if (!inText) return false;
2990
+ const k = e.key;
2991
+ const allow = k === "Escape" || k === "Tab" || k.startsWith("Arrow") || k === "PageUp" || k === "PageDown" || k === "Home" || k === "End" || e.ctrlKey || e.metaKey || e.altKey;
2992
+ return !allow;
2993
+ }
2994
+
2995
+ // src/feature/keyboard/application/use-case/unregister-shortcut-use-case.ts
2996
+ var UnregisterShortcutUseCase = class {
2997
+ constructor(registry3) {
2998
+ this.registry = registry3;
2999
+ }
3000
+ unregisterShortcut(id) {
3001
+ this.registry.unregister(id);
3002
+ }
3003
+ };
3004
+
3005
+ // src/feature/keyboard/infra/web/react/KeyboardProvider.tsx
3006
+ import { createContext as createContext4, useContext as useContext5, useMemo as useMemo4 } from "react";
3007
+
3008
+ // src/feature/keyboard/infra/providers/in-memory-shortcuts-registry.ts
3009
+ function compareStringArrays(a, b) {
3010
+ const len = Math.min(a.length, b.length);
3011
+ for (let i = 0; i < len; i++) {
3012
+ const c = a[i].localeCompare(b[i]);
3013
+ if (c !== 0) return c;
3014
+ }
3015
+ return a.length - b.length;
3016
+ }
3017
+ var InMemoryShortcutRegistry = class {
3018
+ byReg = /* @__PURE__ */ new Map();
3019
+ addMany(items) {
3020
+ for (const it of items) {
3021
+ const arr = this.byReg.get(it.registrationId) ?? [];
3022
+ arr.push(it);
3023
+ this.byReg.set(it.registrationId, arr);
3024
+ }
3025
+ }
3026
+ removeByRegistration(registrationId) {
3027
+ this.byReg.delete(registrationId);
3028
+ }
3029
+ snapshotGroupedByScope() {
3030
+ const grouped = {};
3031
+ for (const items of this.byReg.values()) {
3032
+ for (const it of items) {
3033
+ const existing = grouped[it.scopeId];
3034
+ if (existing) {
3035
+ existing.push({ ...it });
3036
+ continue;
3037
+ }
3038
+ grouped[it.scopeId] = [{ ...it }];
3039
+ }
3040
+ }
3041
+ for (const scopeId of Object.keys(grouped)) {
3042
+ grouped[scopeId].sort(
3043
+ (a, b) => (a.group ?? "").localeCompare(b.group ?? "") || a.description.localeCompare(b.description) || compareStringArrays(a.combos, b.combos)
3044
+ );
3045
+ }
3046
+ return grouped;
3047
+ }
3048
+ };
3049
+
3050
+ // src/feature/keyboard/infra/providers/tinykeys-hotkey-engine.ts
3051
+ import { tinykeys } from "tinykeys";
3052
+ var TinykeysHotkeyEngine = class {
3053
+ register(bindings, opts = {}) {
3054
+ const target = opts.target ?? window;
3055
+ const codeMap = {};
3056
+ const charMap = [];
3057
+ const wrap = (fn) => (e) => {
3058
+ if (opts.guard && !opts.guard(e)) return;
3059
+ if (opts.preventDefault) e.preventDefault();
3060
+ fn(e);
3061
+ };
3062
+ for (const b of bindings) {
3063
+ for (const combo of b.combos) {
3064
+ const m = combo.match(/^char:(.+)$/i);
3065
+ if (m) {
3066
+ charMap.push({ char: m[1], handler: b.handler });
3067
+ } else {
3068
+ codeMap[combo] = wrap(b.handler);
3069
+ }
3070
+ }
3071
+ }
3072
+ const disposers = [];
3073
+ if (Object.keys(codeMap).length) {
3074
+ const tk = tinykeys;
3075
+ const unbind = tk(target, codeMap);
3076
+ disposers.push(unbind);
3077
+ }
3078
+ if (charMap.length) {
3079
+ const onKeyDown = (e) => {
3080
+ for (const { char, handler } of charMap) {
3081
+ if (e.key === char) {
3082
+ if (opts.guard && !opts.guard(e)) return;
3083
+ if (opts.preventDefault) e.preventDefault();
3084
+ handler(e);
3085
+ return;
3086
+ }
3087
+ }
3088
+ };
3089
+ const et = target;
3090
+ et.addEventListener("keydown", onKeyDown);
3091
+ disposers.push(() => et.removeEventListener("keydown", onKeyDown));
3092
+ }
3093
+ return {
3094
+ dispose: () => {
3095
+ for (const dispose of disposers) {
3096
+ dispose();
3097
+ }
3098
+ }
3099
+ };
3100
+ }
3101
+ };
3102
+
3103
+ // src/feature/keyboard/infra/controllers/keyboard-controller.ts
3104
+ var KeyboardController = class {
3105
+ constructor(scopes2, register2, unregister2, enableScope2, disableScope2, listShortcuts2, config2) {
3106
+ this.scopes = scopes2;
3107
+ this.register = register2;
3108
+ this.unregister = unregister2;
3109
+ this.enableScope = enableScope2;
3110
+ this.disableScope = disableScope2;
3111
+ this.listShortcuts = listShortcuts2;
3112
+ for (const s of config2?.predefinedScopes ?? []) {
3113
+ this.scopes.define(s.id, s.priority, !!s.exclusive);
3114
+ if (s.enabled) this.scopes.enable(s.id);
3115
+ }
3116
+ }
3117
+ handleDefineScope(id, priority, exclusive = false) {
3118
+ this.scopes.define(id, priority, exclusive);
3119
+ }
3120
+ handleEnableScope(id) {
3121
+ this.enableScope.enableScope(id);
3122
+ }
3123
+ handleDisableScope(id) {
3124
+ this.disableScope.disableScope(id);
3125
+ }
3126
+ handleRegisterShortcut(params) {
3127
+ return this.register.registerShortcut(params);
3128
+ }
3129
+ handleUnregisterShortcut(id) {
3130
+ this.unregister.unregisterShortcut(id);
3131
+ }
3132
+ handleListShortcutsByScope() {
3133
+ return this.listShortcuts.listShortcutsByScope();
3134
+ }
3135
+ };
3136
+ var scopes = new ScopeManager();
3137
+ var engine = new TinykeysHotkeyEngine();
3138
+ var registry2 = new InMemoryShortcutRegistry();
3139
+ var register = new RegisterShortcutUseCase(engine, scopes, registry2);
3140
+ var unregister = new UnregisterShortcutUseCase(register);
3141
+ var enableScope = new EnableScopeUseCase(scopes);
3142
+ var disableScope = new DisableScopeUseCase(scopes);
3143
+ var listShortcuts = new ListShortcutsUseCase(registry2);
3144
+ var config = {
3145
+ predefinedScopes: [
3146
+ { id: "global", priority: 10, exclusive: false, enabled: true },
3147
+ { id: "page", priority: 50, exclusive: false, enabled: false },
3148
+ { id: "dialog", priority: 100, exclusive: true, enabled: false }
3149
+ ]
3150
+ };
3151
+ var keyboardController = new KeyboardController(
3152
+ scopes,
3153
+ register,
3154
+ unregister,
3155
+ enableScope,
3156
+ disableScope,
3157
+ listShortcuts,
3158
+ config
3159
+ );
3160
+
3161
+ // src/feature/keyboard/infra/web/react/KeyboardProvider.tsx
3162
+ import { jsx as jsx13 } from "react/jsx-runtime";
3163
+ var KeyboardCtx = createContext4(null);
3164
+ var KeyboardProvider = ({ children }) => {
3165
+ const controller = useMemo4(() => keyboardController, []);
3166
+ return /* @__PURE__ */ jsx13(KeyboardCtx.Provider, { value: { controller }, children });
3167
+ };
3168
+ function useKeyboardController() {
3169
+ const ctx = useContext5(KeyboardCtx);
3170
+ if (!ctx) throw new Error("useKeyboardController must be used within <KeyboardProvider>");
3171
+ return ctx.controller;
3172
+ }
3173
+
3174
+ // src/feature/keyboard/infra/web/react/useShortcut.tsx
3175
+ import { useEffect as useEffect5, useMemo as useMemo5 } from "react";
3176
+ function useShortcut(scopeId, bindings, opts) {
3177
+ const controller = useKeyboardController();
3178
+ const stableBindings = useMemo5(() => bindings, [bindings]);
3179
+ useEffect5(() => {
3180
+ const id = controller.handleRegisterShortcut({
3181
+ scopeId,
3182
+ bindings: stableBindings,
3183
+ ignoreTyping: opts?.ignoreTyping ?? true,
3184
+ preventDefault: opts?.preventDefault ?? true
3185
+ });
3186
+ return () => controller.handleUnregisterShortcut(id);
3187
+ }, [controller, stableBindings, opts?.ignoreTyping, opts?.preventDefault, scopeId]);
3188
+ }
3189
+
3190
+ // src/feature/keyboard/infra/web/react/useShortcutScope.tsx
3191
+ import { useEffect as useEffect6 } from "react";
3192
+ function useShortcutScope(scopeId, enabled = true) {
3193
+ const controller = useKeyboardController();
3194
+ useEffect6(() => {
3195
+ if (!enabled) return void 0;
3196
+ controller.handleEnableScope(scopeId);
3197
+ return () => controller.handleDisableScope(scopeId);
3198
+ }, [controller, scopeId, enabled]);
3199
+ }
3200
+
3201
+ // src/feature/vim-navigation/entity/vim-region-registry.ts
3202
+ var VimRegionRegistry = class {
3203
+ byScope = /* @__PURE__ */ new Map();
3204
+ register(meta) {
3205
+ const scopeMap = this.byScope.get(meta.key.scopeId) ?? /* @__PURE__ */ new Map();
3206
+ scopeMap.set(meta.key.regionId, meta);
3207
+ this.byScope.set(meta.key.scopeId, scopeMap);
3208
+ }
3209
+ unregister(key) {
3210
+ const scopeMap = this.byScope.get(key.scopeId);
3211
+ if (!scopeMap) return;
3212
+ scopeMap.delete(key.regionId);
3213
+ if (scopeMap.size === 0) {
3214
+ this.byScope.delete(key.scopeId);
3215
+ }
3216
+ }
3217
+ get(key) {
3218
+ return this.byScope.get(key.scopeId)?.get(key.regionId) ?? null;
3219
+ }
3220
+ list(scopeId) {
3221
+ const scopeMap = this.byScope.get(scopeId);
3222
+ if (!scopeMap) return [];
3223
+ return Array.from(scopeMap.values()).sort((a, b) => a.orderIndex - b.orderIndex);
3224
+ }
3225
+ neighbor(scopeId, current, dir, count) {
3226
+ const regs = this.list(scopeId);
3227
+ if (regs.length === 0) return null;
3228
+ const currentIndex = regs.findIndex((r) => r.key.regionId === current.regionId);
3229
+ const start = currentIndex >= 0 ? currentIndex : 0;
3230
+ const step = dir === "right" ? 1 : -1;
3231
+ const moveBy = Math.max(1, count);
3232
+ const raw = start + step * moveBy;
3233
+ const nextIndex = clamp2(raw, 0, regs.length - 1);
3234
+ return regs[nextIndex]?.key ?? null;
3235
+ }
3236
+ };
3237
+ function clamp2(n, min, max) {
3238
+ return Math.max(min, Math.min(max, n));
3239
+ }
3240
+
3241
+ // src/feature/vim-navigation/application/use-case/vim-handle-key-use-case.ts
3242
+ var VimHandleKeyUseCase = class {
3243
+ constructor(selection, query, getItems, regions) {
3244
+ this.selection = selection;
3245
+ this.query = query;
3246
+ this.getItems = getItems;
3247
+ this.regions = regions;
3248
+ }
3249
+ stateByScope = /* @__PURE__ */ new Map();
3250
+ reset(scopeId) {
3251
+ if (!scopeId) {
3252
+ this.stateByScope.clear();
3253
+ return;
3254
+ }
3255
+ this.stateByScope.delete(scopeId);
3256
+ }
3257
+ handleChar(char, e) {
3258
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
3259
+ const scopeId = this.query.getActiveScope();
3260
+ if (!scopeId) return;
3261
+ const activeKey = this.query.getActiveRegion(scopeId);
3262
+ if (!activeKey) return;
3263
+ const st = this.stateByScope.get(scopeId) ?? { count: "", pending: null };
3264
+ this.stateByScope.set(scopeId, st);
3265
+ if (isDigit(char)) {
3266
+ if (char === "0" && st.count.length === 0) return;
3267
+ st.count += char;
3268
+ return;
3269
+ }
3270
+ if (st.pending === "g") {
3271
+ st.pending = null;
3272
+ if (char === "g") {
3273
+ const count = consumeCount(st, 1);
3274
+ this.goToIndex(activeKey, count);
3275
+ this.afterCursorChange(activeKey);
3276
+ return;
3277
+ }
3278
+ }
3279
+ if (char === "g") {
3280
+ st.pending = "g";
3281
+ return;
3282
+ }
3283
+ if (char === "v") {
3284
+ const mode = this.query.getMode(scopeId);
3285
+ if (mode === "move") {
3286
+ this.selection.enterSelectMode(activeKey);
3287
+ this.afterRegionChange(activeKey);
3288
+ } else {
3289
+ this.selection.exitSelectMode(scopeId);
3290
+ this.afterRegionChange(activeKey);
3291
+ }
3292
+ st.count = "";
3293
+ st.pending = null;
3294
+ return;
3295
+ }
3296
+ if (char === " ") {
3297
+ this.selection.toggleItem(activeKey);
3298
+ st.count = "";
3299
+ st.pending = null;
3300
+ return;
3301
+ }
3302
+ if (char === "h" || char === "j" || char === "k" || char === "l") {
3303
+ const dir = mapHjkl(char);
3304
+ const count = consumeCount(st, 1);
3305
+ this.applyDirectional(scopeId, activeKey, dir, count);
3306
+ st.pending = null;
3307
+ return;
3308
+ }
3309
+ if (char === "G") {
3310
+ const count = consumeCount(st, 0);
3311
+ if (count > 0) {
3312
+ this.goToIndex(activeKey, count);
3313
+ } else {
3314
+ this.goToLast(activeKey);
3315
+ }
3316
+ this.afterCursorChange(activeKey);
3317
+ st.pending = null;
3318
+ return;
3319
+ }
3320
+ st.count = "";
3321
+ st.pending = null;
3322
+ }
3323
+ handleEscape(e) {
3324
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
3325
+ const scopeId = this.query.getActiveScope();
3326
+ if (!scopeId) return;
3327
+ this.selection.exitSelectMode(scopeId);
3328
+ const activeKey = this.query.getActiveRegion(scopeId);
3329
+ if (activeKey) {
3330
+ this.afterRegionChange(activeKey);
3331
+ }
3332
+ const st = this.stateByScope.get(scopeId);
3333
+ if (st) {
3334
+ st.count = "";
3335
+ st.pending = null;
3336
+ }
3337
+ }
3338
+ applyDirectional(scopeId, activeKey, dir, count) {
3339
+ const meta = this.regions.get(activeKey);
3340
+ if (meta?.intra.has(dir)) {
3341
+ const delta = directionToDelta(dir, count);
3342
+ this.selection.moveCursor(activeKey, delta);
3343
+ this.afterCursorChange(activeKey);
3344
+ return;
3345
+ }
3346
+ if (dir === "left" || dir === "right") {
3347
+ const interDir = dir;
3348
+ if (meta && !meta.inter.has(interDir)) return;
3349
+ const nextKey = this.regions.neighbor(scopeId, activeKey, interDir, count);
3350
+ if (!nextKey) return;
3351
+ const prevIndex = this.getCursorIndex(activeKey);
3352
+ this.selection.setActiveRegion(nextKey);
3353
+ const enteredMeta = this.regions.get(nextKey);
3354
+ const fallback = enteredMeta?.enterCursorFallback;
3355
+ const nextCursor = this.query.getCursor(nextKey);
3356
+ if (!nextCursor && fallback) {
3357
+ const resolved = this.resolveInterRegionFallback(fallback, prevIndex);
3358
+ if (resolved) {
3359
+ this.applyFallbackCursor(nextKey, resolved);
3360
+ }
3361
+ }
3362
+ this.afterRegionChange(nextKey);
3363
+ }
3364
+ }
3365
+ goToLast(key) {
3366
+ const items = this.getItems(key);
3367
+ if (items.length === 0) {
3368
+ this.selection.setCursor(key, null);
3369
+ return;
3370
+ }
3371
+ const last = items[items.length - 1] ?? null;
3372
+ this.selection.setCursor(key, last);
3373
+ }
3374
+ goToIndex(key, index1) {
3375
+ const items = this.getItems(key);
3376
+ if (items.length === 0) {
3377
+ this.selection.setCursor(key, null);
3378
+ return;
3379
+ }
3380
+ const idx0 = clamp3(index1 - 1, 0, items.length - 1);
3381
+ this.selection.setCursor(key, items[idx0] ?? null);
3382
+ }
3383
+ afterRegionChange(key) {
3384
+ const meta = this.regions.get(key);
3385
+ meta?.focusContainer?.();
3386
+ meta?.scrollToCursor?.();
3387
+ }
3388
+ afterCursorChange(key) {
3389
+ const meta = this.regions.get(key);
3390
+ meta?.scrollToCursor?.();
3391
+ if (meta?.focusOnCursorChange !== false) {
3392
+ meta?.focusContainer?.();
3393
+ }
3394
+ }
3395
+ applyFallbackCursor(key, fallback) {
3396
+ const items = this.getItems(key);
3397
+ const nextCursor = getFallbackCursor2(items, fallback);
3398
+ if (nextCursor === null) {
3399
+ return;
3400
+ }
3401
+ this.selection.setCursor(key, nextCursor);
3402
+ }
3403
+ getCursorIndex(key) {
3404
+ const cursor = this.query.getCursor(key);
3405
+ if (!cursor) {
3406
+ return null;
3407
+ }
3408
+ const items = this.getItems(key);
3409
+ const index = items.indexOf(cursor);
3410
+ return index >= 0 ? index : null;
3411
+ }
3412
+ resolveInterRegionFallback(fallback, prevIndex) {
3413
+ if (fallback.kind === "sameIndex") {
3414
+ if (prevIndex === null) {
3415
+ return null;
3416
+ }
3417
+ return { kind: "nth", idx: prevIndex };
3418
+ }
3419
+ if (fallback.kind !== "nth") {
3420
+ return fallback;
3421
+ }
3422
+ if (fallback.idx >= 0) {
3423
+ return fallback;
3424
+ }
3425
+ if (prevIndex === null) {
3426
+ return null;
3427
+ }
3428
+ return { kind: "nth", idx: prevIndex };
3429
+ }
3430
+ };
3431
+ function isDigit(ch) {
3432
+ return ch.length === 1 && ch >= "0" && ch <= "9";
3433
+ }
3434
+ function consumeCount(st, def) {
3435
+ if (!st.count) return def;
3436
+ const n = Number(st.count);
3437
+ st.count = "";
3438
+ if (!Number.isFinite(n) || n < 0) return def;
3439
+ return Math.floor(n);
3440
+ }
3441
+ function mapHjkl(ch) {
3442
+ switch (ch) {
3443
+ case "h":
3444
+ return "left";
3445
+ case "j":
3446
+ return "down";
3447
+ case "k":
3448
+ return "up";
3449
+ case "l":
3450
+ return "right";
3451
+ }
3452
+ }
3453
+ function getFallbackCursor2(items, fallback) {
3454
+ if (fallback.kind === "none") {
3455
+ return null;
3456
+ }
3457
+ if (fallback.kind === "first") {
3458
+ return items[0] ?? null;
3459
+ }
3460
+ if (fallback.kind === "last") {
3461
+ return items[items.length - 1] ?? null;
3462
+ }
3463
+ if (fallback.kind === "id") {
3464
+ return items.includes(fallback.id) ? fallback.id : null;
3465
+ }
3466
+ if (fallback.kind === "nth") {
3467
+ const idx = clamp3(fallback.idx, 0, items.length - 1);
3468
+ return items[idx] ?? null;
3469
+ }
3470
+ if (fallback.kind === "nearestTo") {
3471
+ return getNearestCursor2(items, fallback.id);
3472
+ }
3473
+ throw new Error(`Invalid cursor fallback type of: ${JSON.stringify(fallback)} did not match any handler.`);
3474
+ }
3475
+ function getNearestCursor2(items, id) {
3476
+ if (items.includes(id)) {
3477
+ return id;
3478
+ }
3479
+ return null;
3480
+ }
3481
+ function directionToDelta(dir, count) {
3482
+ const n = Math.max(1, count);
3483
+ switch (dir) {
3484
+ case "down":
3485
+ return n;
3486
+ case "up":
3487
+ return -n;
3488
+ case "right":
3489
+ return n;
3490
+ case "left":
3491
+ return -n;
3492
+ }
3493
+ }
3494
+ function clamp3(n, min, max) {
3495
+ return Math.max(min, Math.min(max, n));
3496
+ }
3497
+
3498
+ // src/feature/vim-navigation/infra/controller/vim-controller.ts
3499
+ var VimController = class {
3500
+ regions;
3501
+ handleKey;
3502
+ selection;
3503
+ constructor(selection, regions = new VimRegionRegistry(), handleKey = new VimHandleKeyUseCase(
3504
+ selection.getHandle(),
3505
+ selection.getQuery(),
3506
+ (key) => selection.getItems(key),
3507
+ regions
3508
+ )) {
3509
+ this.regions = regions;
3510
+ this.handleKey = handleKey;
3511
+ this.selection = selection;
3512
+ }
3513
+ handleRegisterRegionMeta(meta) {
3514
+ this.regions.register(meta);
3515
+ }
3516
+ handleUnregisterRegionMeta(key) {
3517
+ this.regions.unregister(key);
3518
+ }
3519
+ handleCharKey(char, e) {
3520
+ this.handleKey.handleChar(char, e);
3521
+ }
3522
+ handleEscape(e) {
3523
+ this.handleKey.handleEscape(e);
3524
+ }
3525
+ resetActiveScope() {
3526
+ const scopeId = this.selection.getActiveScope();
3527
+ if (!scopeId) return;
3528
+ this.handleKey.reset(scopeId);
3529
+ }
3530
+ getSelection() {
3531
+ return this.selection;
3532
+ }
3533
+ };
3534
+ var createVimController = controllerFactory(
3535
+ (deps) => new VimController(deps.selection, deps.regions, deps.handleKey),
3536
+ (overrides) => {
3537
+ const selection = overrides.selection;
3538
+ if (!selection) {
3539
+ throw new Error("createVimController requires selection");
3540
+ }
3541
+ const regions = overrides.regions ?? new VimRegionRegistry();
3542
+ const handleKey = overrides.handleKey ?? new VimHandleKeyUseCase(
3543
+ selection.getHandle(),
3544
+ selection.getQuery(),
3545
+ (key) => selection.getItems(key),
3546
+ regions
3547
+ );
3548
+ return {
3549
+ selection,
3550
+ regions,
3551
+ handleKey
3552
+ };
3553
+ }
3554
+ );
3555
+
3556
+ // src/feature/vim-navigation/infra/web/react/VimControllerProvider.tsx
3557
+ import { createContext as createContext5, useContext as useContext6 } from "react";
3558
+ import { jsx as jsx14 } from "react/jsx-runtime";
3559
+ var VimControllerCtx = createContext5(null);
3560
+ function VimControllerProvider({ children, controller }) {
3561
+ return /* @__PURE__ */ jsx14(VimControllerCtx.Provider, { value: controller, children });
3562
+ }
3563
+ function useVimController() {
3564
+ const controller = useContext6(VimControllerCtx);
3565
+ if (!controller) throw new Error("useVimController must be used within <VimControllerProvider>");
3566
+ return controller;
3567
+ }
3568
+
3569
+ // src/feature/vim-navigation/infra/web/react/hook/use-vim-bindings.tsx
3570
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef3, useState as useState5 } from "react";
3571
+ var scopeBindings = /* @__PURE__ */ new Map();
3572
+ function notify(entry) {
3573
+ for (const notifySub of entry.subscribers.values()) {
3574
+ notifySub(entry.ownerId);
3575
+ }
3576
+ }
3577
+ function useVimBindings(keyboardScopeId, options = {}) {
3578
+ const vim = useVimController();
3579
+ const enabled = options.enabled ?? true;
3580
+ const ownerIdRef = useRef3(Symbol("vim-binding-owner"));
3581
+ const [isOwner, setIsOwner] = useState5(false);
3582
+ const handleOwnerChange = useCallback5((ownerId) => {
3583
+ setIsOwner(ownerId === ownerIdRef.current);
3584
+ }, []);
3585
+ useEffect7(() => {
3586
+ if (!enabled) {
3587
+ setIsOwner(false);
3588
+ return;
3589
+ }
3590
+ const entry = scopeBindings.get(keyboardScopeId) ?? {
3591
+ ownerId: null,
3592
+ subscribers: /* @__PURE__ */ new Map()
3593
+ };
3594
+ scopeBindings.set(keyboardScopeId, entry);
3595
+ entry.subscribers.set(ownerIdRef.current, handleOwnerChange);
3596
+ if (!entry.ownerId) {
3597
+ entry.ownerId = ownerIdRef.current;
3598
+ }
3599
+ notify(entry);
3600
+ return () => {
3601
+ entry.subscribers.delete(ownerIdRef.current);
3602
+ if (entry.ownerId === ownerIdRef.current) {
3603
+ const nextOwner = entry.subscribers.keys().next().value ?? null;
3604
+ entry.ownerId = nextOwner;
3605
+ if (!nextOwner && entry.subscribers.size === 0) {
3606
+ scopeBindings.delete(keyboardScopeId);
3607
+ return;
3608
+ }
3609
+ notify(entry);
3610
+ return;
3611
+ }
3612
+ if (entry.subscribers.size === 0) {
3613
+ scopeBindings.delete(keyboardScopeId);
3614
+ }
3615
+ };
3616
+ }, [enabled, handleOwnerChange, keyboardScopeId]);
3617
+ const bindings = useMemo6(() => {
3618
+ if (!enabled || !isOwner) {
3619
+ return [];
3620
+ }
3621
+ const charCombo = (c) => `char:${c}`;
3622
+ const onChar = (c) => (e) => vim.handleCharKey(c, e);
3623
+ const digitBindings = [];
3624
+ for (let i = 0; i <= 9; i += 1) {
3625
+ const d = String(i);
3626
+ digitBindings.push({
3627
+ combos: [charCombo(d)],
3628
+ handler: onChar(d),
3629
+ description: `vim: count ${d}`,
3630
+ group: "vim"
3631
+ });
3632
+ }
3633
+ return [
3634
+ ...digitBindings,
3635
+ { combos: [charCombo("h")], handler: onChar("h"), description: "vim: left", group: "vim" },
3636
+ { combos: [charCombo("j")], handler: onChar("j"), description: "vim: down", group: "vim" },
3637
+ { combos: [charCombo("k")], handler: onChar("k"), description: "vim: up", group: "vim" },
3638
+ { combos: [charCombo("l")], handler: onChar("l"), description: "vim: right", group: "vim" },
3639
+ { combos: [charCombo("g")], handler: onChar("g"), description: "vim: g prefix", group: "vim" },
3640
+ { combos: [charCombo("G")], handler: onChar("G"), description: "vim: G", group: "vim" },
3641
+ {
3642
+ combos: [charCombo("v"), charCombo("V")],
3643
+ handler: onChar("v"),
3644
+ description: "vim: toggle select",
3645
+ group: "vim"
3646
+ },
3647
+ { combos: [charCombo(" ")], handler: onChar(" "), description: "vim: toggle item", group: "vim" },
3648
+ { combos: ["Escape"], handler: (e) => vim.handleEscape(e), description: "vim: escape", group: "vim" }
3649
+ ];
3650
+ }, [enabled, isOwner, vim]);
3651
+ useShortcut(keyboardScopeId, bindings, { ignoreTyping: true, preventDefault: true });
3652
+ }
3653
+
3654
+ // src/feature/vim-navigation/infra/web/react/hook/use-vim-region.tsx
3655
+ import { useEffect as useEffect9, useMemo as useMemo8, useRef as useRef4, useSyncExternalStore } from "react";
3656
+
3657
+ // src/feature/vim-navigation/infra/web/react/hook/use-vim-region-meta.tsx
3658
+ import { useEffect as useEffect8, useMemo as useMemo7 } from "react";
3659
+ function useVimRegionMeta(containerRef, args) {
3660
+ const vim = useVimController();
3661
+ const selection = vim.getSelection();
3662
+ const intraSet = useMemo7(() => new Set(args.intra ?? ["up", "down"]), [args.intra]);
3663
+ const interSet = useMemo7(() => new Set(args.inter ?? ["left", "right"]), [args.inter]);
3664
+ const itemAttr = args.itemDataAttr ?? "data-vim-item-id";
3665
+ const focusContainer = useMemo7(() => {
3666
+ return () => {
3667
+ const el = containerRef.current;
3668
+ if (!el) return;
3669
+ const activeId = selection.getActiveItem(args.key);
3670
+ const selector = activeId ? `[${itemAttr}="${cssEscape(activeId)}"]` : `[${itemAttr}]`;
3671
+ const itemEl = el.querySelector(selector);
3672
+ itemEl?.focus();
3673
+ };
3674
+ }, [args.key, containerRef, itemAttr, selection]);
3675
+ const scrollToCursor = useMemo7(() => {
3676
+ return () => {
3677
+ const el = containerRef.current;
3678
+ if (!el) return;
3679
+ const cursor = selection.getActiveItem(args.key);
3680
+ if (!cursor) return;
3681
+ const selector = `[${itemAttr}="${cssEscape(cursor)}"]`;
3682
+ const itemEl = el.querySelector(selector);
3683
+ if (!itemEl) return;
3684
+ itemEl.scrollIntoView({ block: "nearest" });
3685
+ };
3686
+ }, [args.key, containerRef, selection, itemAttr]);
3687
+ useEffect8(() => {
3688
+ const meta = {
3689
+ key: args.key,
3690
+ orderIndex: args.orderIndex,
3691
+ intra: intraSet,
3692
+ inter: interSet,
3693
+ focusContainer,
3694
+ scrollToCursor,
3695
+ enterCursorFallback: args.enterCursorFallback,
3696
+ focusOnCursorChange: args.focusOnCursorChange ?? true
3697
+ };
3698
+ vim.handleRegisterRegionMeta(meta);
3699
+ return () => {
3700
+ vim.handleUnregisterRegionMeta(args.key);
3701
+ };
3702
+ }, []);
3703
+ function getItemProps(itemId) {
3704
+ return { [itemAttr]: itemId };
3705
+ }
3706
+ return { getItemProps };
3707
+ }
3708
+ function cssEscape(value) {
3709
+ const cssObj = typeof CSS === "undefined" ? void 0 : CSS;
3710
+ if (cssObj && typeof cssObj.escape === "function") {
3711
+ return cssObj.escape(value);
3712
+ }
3713
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3714
+ }
3715
+
3716
+ // src/feature/vim-navigation/infra/web/react/hook/use-vim-region.tsx
3717
+ function useVimRegion(containerRef, args) {
3718
+ const vim = useVimController();
3719
+ const selection = vim.getSelection();
3720
+ const keyboard = useKeyboardController();
3721
+ const refreshRef = useRef4(null);
3722
+ const getRegionItemsRef = useRef4(args.getRegionItems);
3723
+ const bindKeys = args.bindKeys ?? true;
3724
+ const keyboardScopeId = args.keyboardScopeId ?? args.key.scopeId;
3725
+ useEffect9(() => {
3726
+ keyboard.handleDefineScope(keyboardScopeId, 50, false);
3727
+ if (!bindKeys) {
3728
+ return;
3729
+ }
3730
+ keyboard.handleEnableScope(keyboardScopeId);
3731
+ return () => keyboard.handleDisableScope(keyboardScopeId);
3732
+ }, [bindKeys, keyboard, keyboardScopeId]);
3733
+ useVimBindings(keyboardScopeId, { enabled: bindKeys });
3734
+ useSyncExternalStore(
3735
+ (listener) => selection.subscribe(listener),
3736
+ () => selection.getVersion(),
3737
+ () => selection.getVersion()
3738
+ );
3739
+ const metaArgs = useMemo8(
3740
+ () => ({
3741
+ key: args.key,
3742
+ orderIndex: args.orderIndex,
3743
+ intra: args.intra,
3744
+ inter: args.inter,
3745
+ enterCursorFallback: args.enterCursorFallback,
3746
+ itemDataAttr: args.itemDataAttr,
3747
+ focusOnCursorChange: args.focusOnCursorChange
3748
+ }),
3749
+ [
3750
+ args.enterCursorFallback,
3751
+ args.focusOnCursorChange,
3752
+ args.inter,
3753
+ args.intra,
3754
+ args.itemDataAttr,
3755
+ args.key,
3756
+ args.orderIndex
3757
+ ]
3758
+ );
3759
+ const { getItemProps } = useVimRegionMeta(containerRef, metaArgs);
3760
+ useEffect9(() => {
3761
+ getRegionItemsRef.current = args.getRegionItems;
3762
+ }, [args.getRegionItems]);
3763
+ useEffect9(() => {
3764
+ const refresh2 = selection.registerRegion({
3765
+ key: args.key,
3766
+ getRegionItems: () => getRegionItemsRef.current(),
3767
+ makeActive: args.makeActive,
3768
+ initialCursor: args.initialCursor
3769
+ });
3770
+ refreshRef.current = refresh2;
3771
+ if (!selection.getActiveScope()) {
3772
+ selection.setActiveScope(args.key.scopeId);
3773
+ }
3774
+ refresh2();
3775
+ return () => {
3776
+ selection.unregisterRegion(args.key);
3777
+ refreshRef.current = null;
3778
+ };
3779
+ }, [args.initialCursor, args.key, args.makeActive, selection]);
3780
+ useEffect9(() => {
3781
+ if (!args.refreshDeps) {
3782
+ return;
3783
+ }
3784
+ refreshRef.current?.();
3785
+ }, args.refreshDeps ?? []);
3786
+ const activeId = selection.getActiveItem(args.key);
3787
+ const selectedIds = selection.getSelectedIds(args.key);
3788
+ const rangeIds = selection.getRangeIds(args.key);
3789
+ const toggledIds = selection.getToggledIds(args.key);
3790
+ const mode = selection.getMode(args.key.scopeId);
3791
+ const isSelected = (itemId) => selection.isSelected(args.key, itemId);
3792
+ return {
3793
+ getItemProps,
3794
+ refresh: (fallback) => refreshRef.current?.(fallback),
3795
+ activeId,
3796
+ selectedIds,
3797
+ rangeIds,
3798
+ toggledIds,
3799
+ mode,
3800
+ isSelected
3801
+ };
3802
+ }
3803
+
2157
3804
  // src/ui/components/Field.tsx
2158
3805
  import * as React6 from "react";
2159
3806
 
2160
3807
  // src/ui/components/Label.tsx
2161
3808
  import * as React5 from "react";
2162
- import { jsx as jsx12 } from "react/jsx-runtime";
3809
+ import { jsx as jsx15 } from "react/jsx-runtime";
2163
3810
  var Label = React5.forwardRef(
2164
- ({ className, htmlFor, children, ...props }, ref) => /* @__PURE__ */ jsx12(
3811
+ ({ className, htmlFor, children, ...props }, ref) => /* @__PURE__ */ jsx15(
2165
3812
  "label",
2166
3813
  {
2167
3814
  ref,
@@ -2175,7 +3822,7 @@ var Label = React5.forwardRef(
2175
3822
  Label.displayName = "Label";
2176
3823
 
2177
3824
  // src/ui/components/Field.tsx
2178
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
3825
+ import { jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
2179
3826
  function Field({ label, hint, error, required, disabled, id, children, className, ...props }) {
2180
3827
  const reactId = React6.useId();
2181
3828
  const controlId = id ?? `field-${reactId}`;
@@ -2195,7 +3842,7 @@ function Field({ label, hint, error, required, disabled, id, children, className
2195
3842
  "aria-describedby": ariaDescribedBy
2196
3843
  });
2197
3844
  return /* @__PURE__ */ jsxs7("div", { className: cn("grid gap-1.5", className), ...props, children: [
2198
- label ? /* @__PURE__ */ jsx13("div", { className: "flex items-center justify-between gap-3", children: /* @__PURE__ */ jsxs7(Label, { htmlFor: controlId, children: [
3845
+ label ? /* @__PURE__ */ jsx16("div", { className: "flex items-center justify-between gap-3", children: /* @__PURE__ */ jsxs7(Label, { htmlFor: controlId, children: [
2199
3846
  label,
2200
3847
  resolvedRequired ? /* @__PURE__ */ jsxs7("span", { "aria-hidden": "true", className: "text-(--muted)", children: [
2201
3848
  " ",
@@ -2203,15 +3850,15 @@ function Field({ label, hint, error, required, disabled, id, children, className
2203
3850
  ] }) : null
2204
3851
  ] }) }) : null,
2205
3852
  control,
2206
- error ? /* @__PURE__ */ jsx13("p", { id: errorId, className: "text-xs leading-5 text-(--danger-contrast)", role: "alert", children: error }) : null,
2207
- hint ? /* @__PURE__ */ jsx13("p", { id: hintId, className: "text-xs leading-5 text-(--muted)", children: hint }) : null
3853
+ error ? /* @__PURE__ */ jsx16("p", { id: errorId, className: "text-xs leading-5 text-(--danger-contrast)", role: "alert", children: error }) : null,
3854
+ hint ? /* @__PURE__ */ jsx16("p", { id: hintId, className: "text-xs leading-5 text-(--muted)", children: hint }) : null
2208
3855
  ] });
2209
3856
  }
2210
3857
 
2211
3858
  // src/ui/components/Input.tsx
2212
3859
  import { cva as cva3 } from "class-variance-authority";
2213
3860
  import * as React7 from "react";
2214
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
3861
+ import { jsx as jsx17, jsxs as jsxs8 } from "react/jsx-runtime";
2215
3862
  var controlShellBase = cn(
2216
3863
  "relative inline-flex items-center gap-2",
2217
3864
  "rounded-[var(--radius-input)]",
@@ -2291,7 +3938,7 @@ var Input = React7.forwardRef(
2291
3938
  "data-invalid": invalid ? "" : void 0,
2292
3939
  "data-disabled": isDisabled ? "" : void 0,
2293
3940
  children: [
2294
- showLeftSlot ? /* @__PURE__ */ jsx14(
3941
+ showLeftSlot ? /* @__PURE__ */ jsx17(
2295
3942
  "span",
2296
3943
  {
2297
3944
  className: cn(slotBase, reserveLeftSlot && slotReserve),
@@ -2299,7 +3946,7 @@ var Input = React7.forwardRef(
2299
3946
  children: leftSlot
2300
3947
  }
2301
3948
  ) : null,
2302
- /* @__PURE__ */ jsx14(
3949
+ /* @__PURE__ */ jsx17(
2303
3950
  "input",
2304
3951
  {
2305
3952
  ref,
@@ -2309,7 +3956,7 @@ var Input = React7.forwardRef(
2309
3956
  ...props
2310
3957
  }
2311
3958
  ),
2312
- showRightSlot ? /* @__PURE__ */ jsx14(
3959
+ showRightSlot ? /* @__PURE__ */ jsx17(
2313
3960
  "span",
2314
3961
  {
2315
3962
  className: cn(slotBase, reserveRightSlot && slotReserve),
@@ -2328,7 +3975,7 @@ Input.displayName = "Input";
2328
3975
  import { cva as cva4 } from "class-variance-authority";
2329
3976
  import { Slot as Slot2 } from "radix-ui";
2330
3977
  import * as React8 from "react";
2331
- import { jsx as jsx15 } from "react/jsx-runtime";
3978
+ import { jsx as jsx18 } from "react/jsx-runtime";
2332
3979
  var skeletonBase = cn(
2333
3980
  "relative isolate inline-flex overflow-hidden",
2334
3981
  "bg-[color:color-mix(in_oklab,var(--surface-2),var(--border)_25%)]",
@@ -2386,7 +4033,7 @@ var Skeleton = React8.forwardRef(
2386
4033
  ({ variant, size, radius, fullWidth, animation, preset, animate, asChild, className, children, ...props }, ref) => {
2387
4034
  const resolvedAnimation = animation ?? (animate === false ? "none" : animate ? "pulse" : void 0);
2388
4035
  const Component = asChild ? Slot2.Root : "div";
2389
- return /* @__PURE__ */ jsx15(
4036
+ return /* @__PURE__ */ jsx18(
2390
4037
  Component,
2391
4038
  {
2392
4039
  ref,
@@ -2403,7 +4050,7 @@ var Skeleton = React8.forwardRef(
2403
4050
  ),
2404
4051
  "aria-hidden": props["aria-hidden"] ?? true,
2405
4052
  ...props,
2406
- children: asChild ? /* @__PURE__ */ jsx15(Slot2.Slottable, { children }) : children
4053
+ children: asChild ? /* @__PURE__ */ jsx18(Slot2.Slottable, { children }) : children
2407
4054
  }
2408
4055
  );
2409
4056
  }
@@ -2413,7 +4060,7 @@ Skeleton.displayName = "Skeleton";
2413
4060
  // src/ui/components/Textarea.tsx
2414
4061
  import { cva as cva5 } from "class-variance-authority";
2415
4062
  import * as React9 from "react";
2416
- import { jsx as jsx16 } from "react/jsx-runtime";
4063
+ import { jsx as jsx19 } from "react/jsx-runtime";
2417
4064
  var controlShellBase2 = cn(
2418
4065
  "relative inline-flex w-full",
2419
4066
  "rounded-[var(--radius-input)]",
@@ -2508,13 +4155,13 @@ var Textarea = React9.forwardRef(
2508
4155
  React9.useLayoutEffect(() => {
2509
4156
  resize();
2510
4157
  }, [resize, value]);
2511
- return /* @__PURE__ */ jsx16(
4158
+ return /* @__PURE__ */ jsx19(
2512
4159
  "div",
2513
4160
  {
2514
4161
  className: cn(textareaShellVariants({ variant, size, fullWidth }), className),
2515
4162
  "data-invalid": invalid ? "" : void 0,
2516
4163
  "data-disabled": isDisabled ? "" : void 0,
2517
- children: /* @__PURE__ */ jsx16(
4164
+ children: /* @__PURE__ */ jsx19(
2518
4165
  "textarea",
2519
4166
  {
2520
4167
  ref: mergeRefs(innerRef, ref),
@@ -2541,7 +4188,7 @@ Textarea.displayName = "Textarea";
2541
4188
  import { AnimatePresence as AnimatePresence2, motion as motion2 } from "motion/react";
2542
4189
  import { Tooltip as TooltipPrimitive } from "radix-ui";
2543
4190
  import * as React10 from "react";
2544
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
4191
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
2545
4192
  var tooltipContentBase = cn(
2546
4193
  "panel shadow-none px-4 py-2.5 text-xs z-5000 select-none",
2547
4194
  "origin-center",
@@ -2561,9 +4208,9 @@ function Tooltip({
2561
4208
  }) {
2562
4209
  const [open2, setOpen] = React10.useState(false);
2563
4210
  const motionDuration = useMotionDuration("--motion-fast", 0.14);
2564
- return /* @__PURE__ */ jsx17(TooltipPrimitive.Provider, { delayDuration: delayMs, children: /* @__PURE__ */ jsxs9(TooltipPrimitive.Root, { onOpenChange: setOpen, children: [
2565
- /* @__PURE__ */ jsx17(TooltipPrimitive.Trigger, { asChild: true, children }),
2566
- /* @__PURE__ */ jsx17(TooltipPrimitive.Portal, { forceMount: true, children: /* @__PURE__ */ jsx17(AnimatePresence2, { children: open2 ? /* @__PURE__ */ jsx17(TooltipPrimitive.Content, { asChild: true, forceMount: true, side, sideOffset, children: /* @__PURE__ */ jsxs9(
4211
+ return /* @__PURE__ */ jsx20(TooltipPrimitive.Provider, { delayDuration: delayMs, children: /* @__PURE__ */ jsxs9(TooltipPrimitive.Root, { onOpenChange: setOpen, children: [
4212
+ /* @__PURE__ */ jsx20(TooltipPrimitive.Trigger, { asChild: true, children }),
4213
+ /* @__PURE__ */ jsx20(TooltipPrimitive.Portal, { forceMount: true, children: /* @__PURE__ */ jsx20(AnimatePresence2, { children: open2 ? /* @__PURE__ */ jsx20(TooltipPrimitive.Content, { asChild: true, forceMount: true, side, sideOffset, children: /* @__PURE__ */ jsxs9(
2567
4214
  motion2.div,
2568
4215
  {
2569
4216
  className: tooltipContentBase,
@@ -2591,7 +4238,7 @@ function Tooltip({
2591
4238
  },
2592
4239
  children: [
2593
4240
  content,
2594
- /* @__PURE__ */ jsx17(TooltipPrimitive.Arrow, { style: { fill: "var(--surface-2)" } })
4241
+ /* @__PURE__ */ jsx20(TooltipPrimitive.Arrow, { style: { fill: "var(--surface-2)" } })
2595
4242
  ]
2596
4243
  }
2597
4244
  ) }) : null }) })
@@ -2603,7 +4250,7 @@ import { cva as cva6 } from "class-variance-authority";
2603
4250
  import { AnimatePresence as AnimatePresence3, motion as motion3 } from "motion/react";
2604
4251
  import { Popover as PopoverPrimitive } from "radix-ui";
2605
4252
  import * as React11 from "react";
2606
- import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
4253
+ import { jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
2607
4254
  var popoverContentVariants = cva6(
2608
4255
  cn(
2609
4256
  "p-3 text-sm z-5000",
@@ -2675,7 +4322,7 @@ function Popover({
2675
4322
  onOpenChange?.(nextOpen);
2676
4323
  };
2677
4324
  return /* @__PURE__ */ jsxs10(PopoverPrimitive.Root, { open: resolvedOpen, onOpenChange: handleOpenChange, modal, children: [
2678
- /* @__PURE__ */ jsx18(
4325
+ /* @__PURE__ */ jsx21(
2679
4326
  PopoverPrimitive.Trigger,
2680
4327
  {
2681
4328
  asChild: true,
@@ -2684,7 +4331,7 @@ function Popover({
2684
4331
  children: trigger
2685
4332
  }
2686
4333
  ),
2687
- /* @__PURE__ */ jsx18(PopoverPrimitive.Portal, { forceMount: true, container: portalContainer, children: /* @__PURE__ */ jsx18(AnimatePresence3, { children: resolvedOpen ? /* @__PURE__ */ jsx18(
4334
+ /* @__PURE__ */ jsx21(PopoverPrimitive.Portal, { forceMount: true, container: portalContainer, children: /* @__PURE__ */ jsx21(AnimatePresence3, { children: resolvedOpen ? /* @__PURE__ */ jsx21(
2688
4335
  PopoverPrimitive.Content,
2689
4336
  {
2690
4337
  asChild: true,
@@ -2719,7 +4366,7 @@ function Popover({
2719
4366
  style,
2720
4367
  children: [
2721
4368
  children,
2722
- withArrow ? /* @__PURE__ */ jsx18(
4369
+ withArrow ? /* @__PURE__ */ jsx21(
2723
4370
  PopoverPrimitive.Arrow,
2724
4371
  {
2725
4372
  className: arrowClassName,
@@ -2761,7 +4408,7 @@ function useComposedRefs(...refs) {
2761
4408
  }
2762
4409
 
2763
4410
  // src/ui/components/DropdownMenu.tsx
2764
- import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
4411
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
2765
4412
  var dropdownMenuContentVariants = cva7(
2766
4413
  cn(
2767
4414
  "min-w-[14rem] p-2 text-sm z-5000",
@@ -3051,7 +4698,7 @@ function DropdownMenu({
3051
4698
  onKeyDown: handleContentKeyDown,
3052
4699
  ...keepMounted ? { onPointerDownOutside, onInteractOutside } : void 0
3053
4700
  };
3054
- const contentNode = /* @__PURE__ */ jsx19(
4701
+ const contentNode = /* @__PURE__ */ jsx22(
3055
4702
  DropdownMenuPrimitive.Content,
3056
4703
  {
3057
4704
  asChild: true,
@@ -3064,7 +4711,7 @@ function DropdownMenu({
3064
4711
  "aria-label": ariaLabelledBy ? void 0 : ariaLabel,
3065
4712
  "aria-labelledby": ariaLabelledBy,
3066
4713
  ...resolvedContentProps,
3067
- children: /* @__PURE__ */ jsx19(
4714
+ children: /* @__PURE__ */ jsx22(
3068
4715
  motion4.div,
3069
4716
  {
3070
4717
  "aria-hidden": keepMounted ? !resolvedOpen : void 0,
@@ -3094,15 +4741,15 @@ function DropdownMenu({
3094
4741
  )
3095
4742
  }
3096
4743
  );
3097
- return /* @__PURE__ */ jsx19(DropdownMenuOrientationContext.Provider, { value: resolvedItemsOrientation, children: /* @__PURE__ */ jsxs11(DropdownMenuPrimitive.Root, { open: resolvedOpen, onOpenChange: handleOpenChange, modal, children: [
3098
- /* @__PURE__ */ jsx19(DropdownMenuPrimitive.Trigger, { asChild: true, children: resolvedTrigger }),
3099
- /* @__PURE__ */ jsx19(DropdownMenuPrimitive.Portal, { forceMount: true, container: portalContainer, children: keepMounted ? contentNode : /* @__PURE__ */ jsx19(AnimatePresence4, { children: resolvedOpen ? contentNode : null }) })
4744
+ return /* @__PURE__ */ jsx22(DropdownMenuOrientationContext.Provider, { value: resolvedItemsOrientation, children: /* @__PURE__ */ jsxs11(DropdownMenuPrimitive.Root, { open: resolvedOpen, onOpenChange: handleOpenChange, modal, children: [
4745
+ /* @__PURE__ */ jsx22(DropdownMenuPrimitive.Trigger, { asChild: true, children: resolvedTrigger }),
4746
+ /* @__PURE__ */ jsx22(DropdownMenuPrimitive.Portal, { forceMount: true, container: portalContainer, children: keepMounted ? contentNode : /* @__PURE__ */ jsx22(AnimatePresence4, { children: resolvedOpen ? contentNode : null }) })
3100
4747
  ] }) });
3101
4748
  }
3102
4749
  var DropdownMenuItem = React13.forwardRef(
3103
4750
  ({ className, inset, destructive, ...props }, ref) => {
3104
4751
  const orientation = React13.useContext(DropdownMenuOrientationContext);
3105
- return /* @__PURE__ */ jsx19(
4752
+ return /* @__PURE__ */ jsx22(
3106
4753
  DropdownMenuPrimitive.Item,
3107
4754
  {
3108
4755
  ref,
@@ -3116,14 +4763,14 @@ DropdownMenuItem.displayName = "DropdownMenuItem";
3116
4763
  var DropdownMenuSeparator = React13.forwardRef(({ className, ...props }, ref) => {
3117
4764
  const orientation = React13.useContext(DropdownMenuOrientationContext);
3118
4765
  const orientationClassName = orientation === "horizontal" ? "mx-1 w-px self-stretch bg-(--divider)" : "my-1 h-px bg-(--divider)";
3119
- return /* @__PURE__ */ jsx19(DropdownMenuPrimitive.Separator, { ref, className: cn(orientationClassName, className), ...props });
4766
+ return /* @__PURE__ */ jsx22(DropdownMenuPrimitive.Separator, { ref, className: cn(orientationClassName, className), ...props });
3120
4767
  });
3121
4768
  DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
3122
4769
 
3123
4770
  // src/ui/components/Kbd.tsx
3124
4771
  import { cva as cva8 } from "class-variance-authority";
3125
4772
  import * as React14 from "react";
3126
- import { jsx as jsx20 } from "react/jsx-runtime";
4773
+ import { jsx as jsx23 } from "react/jsx-runtime";
3127
4774
  var kbdVariants = cva8(
3128
4775
  cn(
3129
4776
  "inline-flex min-w-7 items-center justify-center gap-1",
@@ -3145,7 +4792,7 @@ var kbdVariants = cva8(
3145
4792
  }
3146
4793
  }
3147
4794
  );
3148
- var Kbd = React14.forwardRef(({ className, variant, ...props }, ref) => /* @__PURE__ */ jsx20("span", { ref, className: cn(kbdVariants({ variant }), className), ...props }));
4795
+ var Kbd = React14.forwardRef(({ className, variant, ...props }, ref) => /* @__PURE__ */ jsx23("span", { ref, className: cn(kbdVariants({ variant }), className), ...props }));
3149
4796
  Kbd.displayName = "Kbd";
3150
4797
  export {
3151
4798
  AuthController,
@@ -3163,27 +4810,42 @@ export {
3163
4810
  DialogContent,
3164
4811
  DialogController,
3165
4812
  DialogHost,
4813
+ DisableScopeUseCase,
3166
4814
  DropdownMenu,
3167
4815
  DropdownMenuItem,
3168
4816
  DropdownMenuSeparator,
4817
+ EnableScopeUseCase,
3169
4818
  Field,
3170
4819
  HttpError,
4820
+ InMemorySelectionStore,
3171
4821
  InfoDialog,
3172
4822
  Input,
3173
4823
  Kbd,
4824
+ KeyboardProvider,
4825
+ ListShortcutsUseCase,
3174
4826
  MockAuthHttpClient,
3175
4827
  MockHttpClient,
3176
4828
  NotificationHost,
3177
4829
  NotifierController,
3178
4830
  Popover,
3179
4831
  RedirectIfAuthed,
4832
+ RegisterShortcutUseCase,
3180
4833
  RequireAuth,
4834
+ ScopeManager,
4835
+ SelectionController,
4836
+ SelectionControllerProvider,
3181
4837
  Skeleton,
3182
4838
  Textarea,
3183
4839
  Tooltip,
4840
+ UnregisterShortcutUseCase,
4841
+ VimController,
4842
+ VimControllerProvider,
4843
+ VimRegionRegistry,
3184
4844
  authController,
3185
4845
  controllerFactory,
3186
4846
  createAuthController,
4847
+ createSelectionController,
4848
+ createVimController,
3187
4849
  dialogController,
3188
4850
  httpClient as fetchHttpClient,
3189
4851
  getDialogTemplate,
@@ -3199,5 +4861,12 @@ export {
3199
4861
  useAuthDispatch,
3200
4862
  useAuthedUser,
3201
4863
  useDialog,
3202
- useResponsiveMutation
4864
+ useKeyboardController,
4865
+ useResponsiveMutation,
4866
+ useSelectionController,
4867
+ useShortcut,
4868
+ useShortcutScope,
4869
+ useVimBindings,
4870
+ useVimController,
4871
+ useVimRegion
3203
4872
  };