@omerdlw/base-framework 1.0.3 → 1.0.5

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 (204) hide show
  1. package/dist/index.js +5415 -5319
  2. package/dist/index.js.map +1 -1
  3. package/dist/kernel/compose.d.ts.map +1 -1
  4. package/dist/kernel/handlers.d.ts.map +1 -1
  5. package/dist/kernel/hooks.d.ts.map +1 -1
  6. package/dist/kernel/index.js.map +1 -1
  7. package/dist/kernel/operations.d.ts.map +1 -1
  8. package/dist/kernel/schema.d.ts +0 -2
  9. package/dist/kernel/schema.d.ts.map +1 -1
  10. package/dist/kernel/store.d.ts.map +1 -1
  11. package/dist/kernel/types.d.ts.map +1 -1
  12. package/dist/kernel/utils.d.ts +0 -2
  13. package/dist/kernel/utils.d.ts.map +1 -1
  14. package/dist/modules/ambient/color.d.ts +16 -0
  15. package/dist/modules/ambient/color.d.ts.map +1 -0
  16. package/dist/modules/ambient/constants.d.ts +0 -17
  17. package/dist/modules/ambient/constants.d.ts.map +1 -1
  18. package/dist/modules/ambient/context.d.ts +1 -7
  19. package/dist/modules/ambient/context.d.ts.map +1 -1
  20. package/dist/modules/ambient/dom.d.ts +19 -0
  21. package/dist/modules/ambient/dom.d.ts.map +1 -0
  22. package/dist/modules/ambient/extractor.d.ts +5 -0
  23. package/dist/modules/ambient/extractor.d.ts.map +1 -0
  24. package/dist/modules/ambient/hooks.d.ts +9 -0
  25. package/dist/modules/ambient/hooks.d.ts.map +1 -0
  26. package/dist/modules/ambient/index.d.ts +7 -4
  27. package/dist/modules/ambient/index.d.ts.map +1 -1
  28. package/dist/modules/ambient/index.js +66 -60
  29. package/dist/modules/ambient/index.js.map +1 -1
  30. package/dist/modules/ambient/module.d.ts +5 -1
  31. package/dist/modules/ambient/module.d.ts.map +1 -1
  32. package/dist/modules/background/constants.d.ts +1 -10
  33. package/dist/modules/background/constants.d.ts.map +1 -1
  34. package/dist/modules/background/context.d.ts +1 -13
  35. package/dist/modules/background/context.d.ts.map +1 -1
  36. package/dist/modules/background/hooks.d.ts +13 -118
  37. package/dist/modules/background/hooks.d.ts.map +1 -1
  38. package/dist/modules/background/index.d.ts +8 -3
  39. package/dist/modules/background/index.d.ts.map +1 -1
  40. package/dist/modules/background/index.js +975 -991
  41. package/dist/modules/background/index.js.map +1 -1
  42. package/dist/modules/background/module.d.ts +1 -0
  43. package/dist/modules/background/module.d.ts.map +1 -1
  44. package/dist/modules/background/overlay.d.ts +93 -2
  45. package/dist/modules/background/overlay.d.ts.map +1 -1
  46. package/dist/modules/background/playback.d.ts +10 -0
  47. package/dist/modules/background/playback.d.ts.map +1 -0
  48. package/dist/modules/background/state.d.ts +9 -0
  49. package/dist/modules/background/state.d.ts.map +1 -0
  50. package/dist/modules/background/{utils.d.ts → visual.d.ts} +6 -14
  51. package/dist/modules/background/visual.d.ts.map +1 -0
  52. package/dist/modules/background/youtube/player.d.ts +28 -0
  53. package/dist/modules/background/youtube/player.d.ts.map +1 -0
  54. package/dist/modules/context-menu/context.d.ts +1 -10
  55. package/dist/modules/context-menu/context.d.ts.map +1 -1
  56. package/dist/modules/context-menu/dom.d.ts +8 -0
  57. package/dist/modules/context-menu/dom.d.ts.map +1 -0
  58. package/dist/modules/context-menu/hooks.d.ts +11 -11
  59. package/dist/modules/context-menu/hooks.d.ts.map +1 -1
  60. package/dist/modules/context-menu/index.d.ts +8 -3
  61. package/dist/modules/context-menu/index.d.ts.map +1 -1
  62. package/dist/modules/context-menu/index.js +97 -101
  63. package/dist/modules/context-menu/index.js.map +1 -1
  64. package/dist/modules/context-menu/{utils.d.ts → items.d.ts} +2 -9
  65. package/dist/modules/context-menu/items.d.ts.map +1 -0
  66. package/dist/modules/context-menu/module.d.ts.map +1 -1
  67. package/dist/modules/context-menu/overlay.d.ts +13 -1
  68. package/dist/modules/context-menu/overlay.d.ts.map +1 -1
  69. package/dist/modules/context-menu/state.d.ts +1 -0
  70. package/dist/modules/context-menu/state.d.ts.map +1 -1
  71. package/dist/modules/controls/{utils.d.ts → entries.d.ts} +6 -5
  72. package/dist/modules/controls/entries.d.ts.map +1 -0
  73. package/dist/modules/controls/hooks.d.ts +0 -7
  74. package/dist/modules/controls/hooks.d.ts.map +1 -1
  75. package/dist/modules/controls/index.d.ts +5 -3
  76. package/dist/modules/controls/index.d.ts.map +1 -1
  77. package/dist/modules/controls/index.js +66 -61
  78. package/dist/modules/controls/index.js.map +1 -1
  79. package/dist/modules/controls/layout.d.ts +7 -0
  80. package/dist/modules/controls/layout.d.ts.map +1 -0
  81. package/dist/modules/controls/overlay.d.ts +9 -0
  82. package/dist/modules/controls/overlay.d.ts.map +1 -1
  83. package/dist/modules/dock/cards.d.ts.map +1 -1
  84. package/dist/modules/dock/dom.d.ts +24 -0
  85. package/dist/modules/dock/dom.d.ts.map +1 -0
  86. package/dist/modules/dock/helpers.d.ts +25 -0
  87. package/dist/modules/dock/helpers.d.ts.map +1 -0
  88. package/dist/modules/dock/hooks.d.ts +3 -918
  89. package/dist/modules/dock/hooks.d.ts.map +1 -1
  90. package/dist/modules/dock/index.d.ts +3 -2
  91. package/dist/modules/dock/index.d.ts.map +1 -1
  92. package/dist/modules/dock/index.js +2372 -2330
  93. package/dist/modules/dock/index.js.map +1 -1
  94. package/dist/modules/dock/layout.d.ts +26 -0
  95. package/dist/modules/dock/layout.d.ts.map +1 -0
  96. package/dist/modules/dock/module.d.ts.map +1 -1
  97. package/dist/modules/dock/overlay.d.ts.map +1 -1
  98. package/dist/modules/dock/paths.d.ts +10 -0
  99. package/dist/modules/dock/paths.d.ts.map +1 -0
  100. package/dist/modules/dock/provider.d.ts.map +1 -1
  101. package/dist/modules/dock/runtime/display.d.ts.map +1 -1
  102. package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
  103. package/dist/modules/dock/runtime/pipeline.d.ts +32 -0
  104. package/dist/modules/dock/runtime/pipeline.d.ts.map +1 -0
  105. package/dist/modules/dock/status/events.d.ts.map +1 -1
  106. package/dist/modules/dock/styles.d.ts +900 -0
  107. package/dist/modules/dock/styles.d.ts.map +1 -0
  108. package/dist/modules/dock/surface/context.d.ts +1 -2
  109. package/dist/modules/dock/surface/context.d.ts.map +1 -1
  110. package/dist/modules/dock/surface/definition.d.ts.map +1 -1
  111. package/dist/modules/dock/surface/helpers.d.ts +7 -0
  112. package/dist/modules/dock/surface/helpers.d.ts.map +1 -0
  113. package/dist/modules/dock/surface/hooks.d.ts.map +1 -1
  114. package/dist/modules/dock/surface/machine.d.ts.map +1 -1
  115. package/dist/modules/dock/surface/stack.d.ts.map +1 -1
  116. package/dist/modules/dock/surface/view-model.d.ts.map +1 -1
  117. package/dist/modules/dock/types.d.ts.map +1 -1
  118. package/dist/modules/index.js +5010 -4916
  119. package/dist/modules/index.js.map +1 -1
  120. package/dist/modules/loading/context.d.ts +2 -13
  121. package/dist/modules/loading/context.d.ts.map +1 -1
  122. package/dist/modules/loading/hooks.d.ts +15 -2
  123. package/dist/modules/loading/hooks.d.ts.map +1 -1
  124. package/dist/modules/loading/index.d.ts +3 -2
  125. package/dist/modules/loading/index.d.ts.map +1 -1
  126. package/dist/modules/loading/index.js +38 -34
  127. package/dist/modules/loading/index.js.map +1 -1
  128. package/dist/modules/loading/module.d.ts.map +1 -1
  129. package/dist/modules/loading/state.d.ts +6 -0
  130. package/dist/modules/loading/state.d.ts.map +1 -0
  131. package/dist/modules/media/constants.d.ts +0 -1
  132. package/dist/modules/media/constants.d.ts.map +1 -1
  133. package/dist/modules/media/context.d.ts +2 -6
  134. package/dist/modules/media/context.d.ts.map +1 -1
  135. package/dist/modules/media/hooks.d.ts +6 -1
  136. package/dist/modules/media/hooks.d.ts.map +1 -1
  137. package/dist/modules/media/index.d.ts +4 -3
  138. package/dist/modules/media/index.d.ts.map +1 -1
  139. package/dist/modules/media/index.js +100 -98
  140. package/dist/modules/media/index.js.map +1 -1
  141. package/dist/modules/media/module.d.ts.map +1 -1
  142. package/dist/modules/media/overlay.d.ts +0 -5
  143. package/dist/modules/media/overlay.d.ts.map +1 -1
  144. package/dist/modules/media/playback.d.ts +5 -0
  145. package/dist/modules/media/playback.d.ts.map +1 -0
  146. package/dist/modules/media/session.d.ts +8 -0
  147. package/dist/modules/media/session.d.ts.map +1 -0
  148. package/dist/modules/media/types.d.ts +0 -1
  149. package/dist/modules/media/types.d.ts.map +1 -1
  150. package/dist/modules/modal/context.d.ts +1 -12
  151. package/dist/modules/modal/context.d.ts.map +1 -1
  152. package/dist/modules/modal/dom.d.ts +4 -0
  153. package/dist/modules/modal/dom.d.ts.map +1 -0
  154. package/dist/modules/modal/header.d.ts +7 -0
  155. package/dist/modules/modal/header.d.ts.map +1 -0
  156. package/dist/modules/modal/hooks.d.ts +12 -1
  157. package/dist/modules/modal/hooks.d.ts.map +1 -1
  158. package/dist/modules/modal/identity.d.ts +7 -0
  159. package/dist/modules/modal/identity.d.ts.map +1 -0
  160. package/dist/modules/modal/index.d.ts +9 -4
  161. package/dist/modules/modal/index.d.ts.map +1 -1
  162. package/dist/modules/modal/index.js +118 -86
  163. package/dist/modules/modal/index.js.map +1 -1
  164. package/dist/modules/modal/layout.d.ts +13 -0
  165. package/dist/modules/modal/layout.d.ts.map +1 -0
  166. package/dist/modules/modal/module.d.ts.map +1 -1
  167. package/dist/modules/modal/overlay.d.ts.map +1 -1
  168. package/dist/modules/modal/types.d.ts.map +1 -1
  169. package/dist/modules/notification/context.d.ts +2 -8
  170. package/dist/modules/notification/context.d.ts.map +1 -1
  171. package/dist/modules/notification/hooks.d.ts +8 -1
  172. package/dist/modules/notification/hooks.d.ts.map +1 -1
  173. package/dist/modules/notification/index.d.ts +4 -3
  174. package/dist/modules/notification/index.d.ts.map +1 -1
  175. package/dist/modules/notification/index.js +56 -46
  176. package/dist/modules/notification/index.js.map +1 -1
  177. package/dist/modules/notification/state.d.ts +17 -0
  178. package/dist/modules/notification/state.d.ts.map +1 -0
  179. package/dist/provider.js.map +1 -1
  180. package/docs/modules/ambient.md +12 -8
  181. package/docs/modules/background.md +15 -13
  182. package/docs/modules/context-menu.md +13 -13
  183. package/docs/modules/controls.md +4 -3
  184. package/docs/modules/dock.md +8 -2
  185. package/docs/modules/loading.md +11 -11
  186. package/docs/modules/media.md +8 -7
  187. package/docs/modules/modal.md +4 -1
  188. package/docs/modules/notification.md +4 -4
  189. package/package.json +4 -4
  190. package/dist/modules/ambient/utils.d.ts +0 -18
  191. package/dist/modules/ambient/utils.d.ts.map +0 -1
  192. package/dist/modules/background/utils.d.ts.map +0 -1
  193. package/dist/modules/context-menu/utils.d.ts.map +0 -1
  194. package/dist/modules/controls/utils.d.ts.map +0 -1
  195. package/dist/modules/dock/utils.d.ts +0 -93
  196. package/dist/modules/dock/utils.d.ts.map +0 -1
  197. package/dist/modules/loading/utils.d.ts +0 -4
  198. package/dist/modules/loading/utils.d.ts.map +0 -1
  199. package/dist/modules/media/utils.d.ts +0 -17
  200. package/dist/modules/media/utils.d.ts.map +0 -1
  201. package/dist/modules/modal/utils.d.ts +0 -31
  202. package/dist/modules/modal/utils.d.ts.map +0 -1
  203. package/dist/modules/notification/utils.d.ts +0 -9
  204. package/dist/modules/notification/utils.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import React, { createContext, memo, createElement, isValidElement, cloneElement, Suspense, useEffect, useState, useCallback, useMemo, use, useSyncExternalStore, useRef, useContext, useLayoutEffect, lazy, useId, useReducer, useInsertionEffect, Children } from 'react';
2
+ import React, { createContext, memo, createElement, isValidElement, cloneElement, Suspense, useEffect, useState, useCallback, useMemo, use, useSyncExternalStore, useRef, useContext, useLayoutEffect, lazy, useId, useReducer, Children } from 'react';
3
3
  import { motion, AnimatePresence, useMotionValue, useSpring, useTransform, useDragControls, MotionConfig } from 'motion/react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
@@ -423,1585 +423,1392 @@ function Button({
423
423
  );
424
424
  }
425
425
  Button.displayName = "Button";
426
-
427
- // src/kernel/constants.ts
428
- var DEFAULT_SOURCE = "dynamic";
429
- var REGISTRY_SOURCES = Object.freeze({
430
- STATIC: "static",
431
- DYNAMIC: "dynamic",
432
- USER: "user"
433
- });
434
- var REGISTRY_LIFECYCLES = Object.freeze({
435
- IMMEDIATE: "immediate",
436
- GRACEFUL: "graceful",
437
- PERSISTENT: "persistent",
438
- ROUTE: "route"
439
- });
440
- var REGISTRY_VALIDATION_MODES = Object.freeze({
441
- WARN: "warn",
442
- STRICT: "strict"
443
- });
444
- var REGISTRY_METADATA_KEYS = Object.freeze([
445
- "cleanup",
446
- "cleanupDelayMs",
447
- "instanceId",
448
- "lifecycle",
449
- "priority",
450
- "source",
451
- "scope",
452
- "validation"
453
- ]);
454
- Object.freeze({
455
- [REGISTRY_SOURCES.STATIC]: 100,
456
- [REGISTRY_SOURCES.DYNAMIC]: 200,
457
- [REGISTRY_SOURCES.USER]: 300
458
- });
459
- Object.freeze({
460
- [REGISTRY_SOURCES.STATIC]: 10,
461
- [REGISTRY_SOURCES.DYNAMIC]: 20,
462
- [REGISTRY_SOURCES.USER]: 30
463
- });
464
- var REGISTRY_LIFECYCLE_VALUES = new Set(
465
- Object.values(REGISTRY_LIFECYCLES)
466
- );
467
- var REGISTRY_METADATA_KEY_SET = new Set(
468
- REGISTRY_METADATA_KEYS
469
- );
470
- var REGISTRY_SCOPE_KINDS = Object.freeze({
471
- APP: "app",
472
- SESSION: "session",
473
- ROUTE: "route",
474
- INSTANCE: "instance",
475
- WORKSPACE: "workspace"
476
- });
477
- REGISTRY_SCOPE_KINDS.APP;
478
- function getOrCreateGlobalContext(name, defaultValue) {
479
- const sym = /* @__PURE__ */ Symbol.for(`@omerdlw/base-framework/context/${name}`);
480
- const store = globalThis;
481
- if (!store[sym]) {
482
- store[sym] = createContext(defaultValue);
483
- }
484
- return store[sym];
485
- }
486
-
487
- // src/kernel/utils.ts
488
- function parseFiniteNumber(value) {
489
- const parsed = Number(value);
490
- return Number.isFinite(parsed) ? parsed : null;
426
+ function isValidComponentType(type) {
427
+ if (typeof type === "function") return true;
428
+ return Boolean(
429
+ type != null && typeof type === "object" && !isValidElement(type) && "$$typeof" in type
430
+ );
491
431
  }
492
- function getValidString(value, fallback = null) {
493
- return trimToNull(value) ?? fallback;
432
+ function resolveComponentType(...candidates) {
433
+ return candidates.find(isValidComponentType) ?? null;
494
434
  }
495
-
496
- // src/kernel/schema.ts
497
- function resolveLifecycle(metadata, fallback) {
498
- const lifecycle = metadata?.lifecycle ?? metadata?.cleanup;
499
- return lifecycle && REGISTRY_LIFECYCLE_VALUES.has(lifecycle) ? lifecycle : fallback;
435
+ function resolveRenderableContent(...candidates) {
436
+ return candidates.find(
437
+ (candidate) => candidate !== null && candidate !== void 0
438
+ ) ?? null;
500
439
  }
501
- function validateRegistryMetadata(metadata) {
502
- if (metadata === void 0 || metadata === null)
503
- return { issues: [], valid: true };
504
- if (!isObject(metadata))
505
- return { issues: ["registry metadata must be an object"], valid: false };
506
- const issues = [];
507
- Object.keys(metadata).forEach((key) => {
508
- if (!REGISTRY_METADATA_KEY_SET.has(key))
509
- issues.push(`unknown registry metadata field: ${key}`);
510
- });
511
- if (metadata.source !== void 0 && !getValidString(metadata.source))
512
- issues.push("source must be a non-empty string");
513
- if (metadata.instanceId !== void 0 && !getValidString(metadata.instanceId))
514
- issues.push("instanceId must be a non-empty string");
515
- if (metadata.scope !== void 0 && !getValidString(metadata.scope))
516
- issues.push("scope must be a non-empty string");
517
- if (metadata.priority !== void 0 && parseFiniteNumber(metadata.priority) === null)
518
- issues.push("priority must be a finite number");
519
- if (metadata.cleanupDelayMs !== void 0) {
520
- const delay = parseFiniteNumber(metadata.cleanupDelayMs);
521
- if (delay === null || delay < 0)
522
- issues.push("cleanupDelayMs must be a non-negative finite number");
440
+ function collectSearchableText(value, visitedObjects) {
441
+ if (typeof value === "string") return value;
442
+ if (typeof value === "number" || typeof value === "boolean") {
443
+ return String(value);
523
444
  }
524
- const lifecycle = metadata.lifecycle ?? metadata.cleanup;
525
- if (lifecycle !== void 0 && !REGISTRY_LIFECYCLE_VALUES.has(String(lifecycle))) {
526
- issues.push(
527
- `lifecycle must be one of: ${[...REGISTRY_LIFECYCLE_VALUES].join(", ")}`
528
- );
445
+ if (Array.isArray(value)) {
446
+ if (visitedObjects.has(value)) return "";
447
+ visitedObjects.add(value);
448
+ const text = value.map((entry) => collectSearchableText(entry, visitedObjects)).join(" ");
449
+ visitedObjects.delete(value);
450
+ return text;
529
451
  }
530
- if (metadata.validation !== void 0 && !Object.values(REGISTRY_VALIDATION_MODES).includes(
531
- metadata.validation
532
- )) {
533
- issues.push(
534
- `validation must be one of: ${Object.values(REGISTRY_VALIDATION_MODES).join(", ")}`
452
+ if (isValidElement(value)) {
453
+ if (visitedObjects.has(value)) return "";
454
+ visitedObjects.add(value);
455
+ const text = collectSearchableText(
456
+ value.props?.children,
457
+ visitedObjects
535
458
  );
459
+ visitedObjects.delete(value);
460
+ return text;
536
461
  }
537
- return { issues, valid: issues.length === 0 };
538
- }
539
- function normalizeRegistryMetadata(metadata, {
540
- defaultCleanupDelayMs = null,
541
- defaultLifecycle = null,
542
- defaultSource = DEFAULT_SOURCE
543
- } = {}) {
544
- const registryMeta = isObject(metadata) ? metadata : {};
545
- const normalizedDefaultDelay = parseFiniteNumber(defaultCleanupDelayMs);
546
- const fallbackCleanupDelayMs = normalizedDefaultDelay !== null && normalizedDefaultDelay >= 0 ? normalizedDefaultDelay : null;
547
- const fallbackLifecycle = defaultLifecycle && REGISTRY_LIFECYCLE_VALUES.has(defaultLifecycle) ? defaultLifecycle : null;
548
- const source = getValidString(registryMeta.source) || getValidString(defaultSource, DEFAULT_SOURCE) || DEFAULT_SOURCE;
549
- const scope = getValidString(registryMeta.scope);
550
- const priority = parseFiniteNumber(registryMeta.priority);
551
- const lifecycle = resolveLifecycle(registryMeta, fallbackLifecycle);
552
- const validation = isValidationMode(registryMeta.validation) ? registryMeta.validation : null;
553
- const cleanupDelayCandidate = parseFiniteNumber(registryMeta.cleanupDelayMs);
554
- let cleanupDelayMs = cleanupDelayCandidate !== null && cleanupDelayCandidate >= 0 ? cleanupDelayCandidate : fallbackCleanupDelayMs;
555
- if (lifecycle === REGISTRY_LIFECYCLES.IMMEDIATE) {
556
- cleanupDelayMs = 0;
557
- } else if ((lifecycle === REGISTRY_LIFECYCLES.GRACEFUL || lifecycle === REGISTRY_LIFECYCLES.ROUTE) && cleanupDelayMs === null) {
558
- cleanupDelayMs = fallbackCleanupDelayMs;
462
+ if (value && typeof value === "object") {
463
+ if (visitedObjects.has(value)) return "";
464
+ visitedObjects.add(value);
465
+ return Object.values(value).map((entry) => collectSearchableText(entry, visitedObjects)).join(" ");
559
466
  }
560
- return {
561
- cleanupDelayMs,
562
- lifecycle,
563
- priority,
564
- registerOptions: {
565
- ...priority !== null && { priority },
566
- ...validation === REGISTRY_VALIDATION_MODES.STRICT && { validation },
567
- ...scope && { scope }
568
- },
569
- source,
570
- ...scope && { scope },
571
- ...validation && { validation }
572
- };
467
+ return "";
573
468
  }
574
- function isValidationMode(value) {
575
- return Object.values(REGISTRY_VALIDATION_MODES).includes(value);
469
+ function toSearchableText(value) {
470
+ return collectSearchableText(value, /* @__PURE__ */ new WeakSet());
576
471
  }
577
- function pickRegistryMetadata(options) {
578
- if (!isObject(options)) return {};
579
- return Object.fromEntries(
580
- REGISTRY_METADATA_KEYS.filter((key) => options[key] !== void 0).map(
581
- (key) => [key, options[key]]
582
- )
583
- );
472
+ function normalizeUpper(value) {
473
+ return String(value || "").trim().toUpperCase();
584
474
  }
585
- function useRequiredContext(context, hookName, providerName) {
586
- const value = use(context);
587
- if (value === null) {
588
- throw new Error(`${hookName} must be used within ${providerName}`);
589
- }
590
- return value;
475
+ function formatMediaTime(seconds = 0) {
476
+ const sec = Math.max(0, Math.floor(Number(seconds) || 0));
477
+ const m = Math.floor(sec / 60);
478
+ const s = sec % 60;
479
+ return `${m}:${s < 10 ? "0" : ""}${s}`;
591
480
  }
592
- function useStore(store, selector = (state) => state, isEqual = Object.is) {
593
- const cacheRef = useRef({
594
- isEqual: null,
595
- selected: void 0,
596
- selector: null,
597
- snapshot: null
481
+ function isHudDescriptor(value) {
482
+ return isObject(value) && !isValidElement(value) && ("component" in value || "content" in value || "node" in value || "element" in value || "isActive" in value || "id" in value);
483
+ }
484
+ function createDockScheduler(options = {}) {
485
+ return createScheduler({
486
+ defaultLabel: "dock-task",
487
+ ...options
598
488
  });
599
- const getSelectedSnapshot = useCallback(() => {
600
- const snapshot = store.getSnapshot();
601
- const cache = cacheRef.current;
602
- if (cache.snapshot === snapshot && cache.selector === selector && cache.isEqual === isEqual) {
603
- return cache.selected;
604
- }
605
- const selected = selector(snapshot);
606
- if (cache.snapshot !== null && cache.isEqual === isEqual && isEqual(cache.selected, selected)) {
607
- cache.snapshot = snapshot;
608
- cache.selector = selector;
609
- return cache.selected;
610
- }
611
- cacheRef.current = {
612
- isEqual,
613
- selected,
614
- selector,
615
- snapshot
616
- };
617
- return selected;
618
- }, [isEqual, selector, store]);
619
- return useSyncExternalStore(
620
- store.subscribe,
621
- getSelectedSnapshot,
622
- getSelectedSnapshot
623
- );
624
489
  }
625
- function useClickOutside(ref, callback) {
626
- const handlePointer = useCallback(
627
- (event) => {
628
- if (ref?.current && !ref.current.contains(event.target)) {
629
- callback?.(event);
630
- }
631
- },
632
- [callback, ref]
490
+
491
+ // src/modules/dock/paths.ts
492
+ function isSamePath(left, right) {
493
+ const normalizedLeft = normalizePath(left);
494
+ const normalizedRight = normalizePath(right);
495
+ return Boolean(
496
+ normalizedLeft && normalizedRight && normalizedLeft === normalizedRight
633
497
  );
634
- useEffect(() => {
635
- document.addEventListener("pointerdown", handlePointer);
636
- return () => document.removeEventListener("pointerdown", handlePointer);
637
- }, [handlePointer]);
638
498
  }
639
- var useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
640
-
641
- // src/events.ts
642
- var EVENT_TYPES = Object.freeze({
643
- API_UNAUTHORIZED: "API_UNAUTHORIZED",
644
- STATE_CHANGE: "STATE_CHANGE",
645
- API_ERROR: "API_ERROR",
646
- APP_ERROR: "APP_ERROR"
647
- });
648
- var EventEmitter = class {
649
- events = /* @__PURE__ */ new Map();
650
- debounceTimers = /* @__PURE__ */ new Map();
651
- subscribe(event, callback) {
652
- if (typeof event !== "string" || !event || typeof callback !== "function") {
653
- return () => {
654
- };
655
- }
656
- const listeners2 = this.events.get(event) || /* @__PURE__ */ new Set();
657
- listeners2.add(callback);
658
- this.events.set(event, listeners2);
659
- return () => {
660
- listeners2.delete(callback);
661
- if (listeners2.size === 0) this.events.delete(event);
662
- };
663
- }
664
- emit(event, payload) {
665
- const listeners2 = this.events.get(event);
666
- if (!listeners2 || listeners2.size === 0) return;
667
- const snapshot = Array.from(listeners2);
668
- for (const callback of snapshot) {
669
- try {
670
- callback(payload);
671
- } catch (error) {
672
- report(`Event listener (${String(event)})`, error);
673
- }
674
- }
675
- }
676
- emitDebounced(event, payload, waitMs = 100) {
677
- const existing = this.debounceTimers.get(event);
678
- if (existing) clearTimeout(existing);
679
- const timer = setTimeout(() => {
680
- this.debounceTimers.delete(event);
681
- this.emit(event, payload);
682
- }, waitMs);
683
- this.debounceTimers.set(event, timer);
684
- }
685
- cancelDebounced(event) {
686
- if (event) {
687
- const timer = this.debounceTimers.get(event);
688
- if (timer) {
689
- clearTimeout(timer);
690
- this.debounceTimers.delete(event);
691
- }
692
- } else {
693
- for (const timer of this.debounceTimers.values()) {
694
- clearTimeout(timer);
695
- }
696
- this.debounceTimers.clear();
697
- }
698
- }
699
- unsubscribeAll(event) {
700
- this.cancelDebounced(event);
701
- if (event) this.events.delete(event);
702
- else this.events.clear();
703
- }
704
- hasListeners(event) {
705
- return Boolean(this.events.get(event)?.size);
706
- }
707
- getListenerCount(event) {
708
- return this.events.get(event)?.size || 0;
709
- }
710
- getAllEvents() {
711
- return [...this.events.keys()];
712
- }
713
- };
714
- var GLOBAL_EVENTS_KEY = /* @__PURE__ */ Symbol.for("__base_framework_global_events__");
715
- var _global = globalThis;
716
- var globalEvents = _global[GLOBAL_EVENTS_KEY] ?? (_global[GLOBAL_EVENTS_KEY] = new EventEmitter());
717
-
718
- // src/hooks/use-global-event.ts
719
- function useGlobalEvent(event, callback, options) {
720
- const callbackRef = useRef(callback);
721
- const debounceTimerRef = useRef(null);
722
- useRef(0);
723
- const throttleTimerRef = useRef(null);
724
- useIsomorphicLayoutEffect(() => {
725
- callbackRef.current = callback;
726
- });
727
- const debounceMs = options?.debounceMs;
728
- const throttleMs = options?.throttleMs;
729
- useEffect(() => {
730
- if (!event) return;
731
- const events = Array.isArray(event) ? event.filter(Boolean) : [event];
732
- if (events.length === 0) return;
733
- const handler = (payload) => {
734
- callbackRef.current?.(payload);
735
- };
736
- const unsubs = events.map((ev) => globalEvents.subscribe(ev, handler));
737
- return () => {
738
- unsubs.forEach((unsub) => unsub());
739
- if (debounceTimerRef.current !== null) {
740
- clearTimeout(debounceTimerRef.current);
741
- debounceTimerRef.current = null;
742
- }
743
- if (throttleTimerRef.current !== null) {
744
- clearTimeout(throttleTimerRef.current);
745
- throttleTimerRef.current = null;
746
- }
747
- };
748
- }, [event, debounceMs, throttleMs]);
499
+ function isPathPrefix(candidatePath, pathname) {
500
+ const normalizedCandidate = normalizePath(candidatePath);
501
+ const normalizedPathname = normalizePath(pathname);
502
+ if (!normalizedCandidate || !normalizedPathname) return false;
503
+ if (normalizedCandidate === normalizedPathname) return true;
504
+ if (normalizedCandidate === "/") return normalizedPathname.startsWith("/");
505
+ return normalizedPathname.startsWith(`${normalizedCandidate}/`);
749
506
  }
750
- var listeners = /* @__PURE__ */ new Set();
751
- var activeFullscreenStateIds = /* @__PURE__ */ new Set();
752
- var ACTIVE_FULLSCREEN_ROOT_SELECTOR = '[data-fullscreen-state-root="true"][data-affect-global-state="true"]';
753
- var domObserver = null;
754
- var lastSnapshot = false;
755
- function getDomSnapshot() {
756
- if (typeof document === "undefined") {
757
- return activeFullscreenStateIds.size > 0;
758
- }
759
- return document.querySelector(ACTIVE_FULLSCREEN_ROOT_SELECTOR) !== null;
507
+ function isInlineActionPathMatch(path, pathname) {
508
+ return isSamePath(path, pathname) || path !== "/" && isPathPrefix(path, pathname);
760
509
  }
761
- function emitChange() {
762
- listeners.forEach((listener) => listener());
510
+ function isSafeInternalHref(value) {
511
+ const href = typeof value === "string" ? value.trim() : "";
512
+ return href.startsWith("/") && !href.startsWith("//");
763
513
  }
764
- function emitIfSnapshotChanged() {
765
- const nextSnapshot = getDomSnapshot();
766
- if (nextSnapshot === lastSnapshot) {
767
- return;
768
- }
769
- lastSnapshot = nextSnapshot;
770
- emitChange();
514
+ function getDockLocationKey({
515
+ hash = "",
516
+ pathname = "/",
517
+ search = ""
518
+ } = {}) {
519
+ const normalizedPathname = String(pathname || "/").trim() || "/";
520
+ const normalizedSearch = String(search || "").trim();
521
+ const normalizedHash = String(hash || "").trim();
522
+ const query = normalizedSearch ? normalizedSearch.startsWith("?") ? normalizedSearch : `?${normalizedSearch}` : "";
523
+ const fragment = normalizedHash ? normalizedHash.startsWith("#") ? normalizedHash : `#${normalizedHash}` : "";
524
+ return `${normalizedPathname}${query}${fragment}`;
771
525
  }
772
- function ensureDomObserver() {
773
- if (typeof document === "undefined" || domObserver || listeners.size === 0) {
774
- return;
775
- }
776
- domObserver = new MutationObserver(() => {
777
- emitIfSnapshotChanged();
778
- });
779
- domObserver.observe(document.documentElement, {
780
- attributes: true,
781
- childList: true,
782
- subtree: true,
783
- attributeFilter: [
784
- "data-fullscreen-state",
785
- "data-fullscreen-state-root",
786
- "data-affect-global-state"
787
- ]
788
- });
526
+
527
+ // src/modules/dock/surface/helpers.ts
528
+ function isSurfaceDescriptor(value) {
529
+ return value != null && typeof value === "object" && !Array.isArray(value) && !isValidElement(value);
789
530
  }
790
- function releaseDomObserver() {
791
- if (listeners.size > 0 || !domObserver) {
792
- return;
531
+ var generatedExtensionId = 0;
532
+ function normalizeSurfaceExtension(input) {
533
+ if (!input) return null;
534
+ if (isValidElement(input)) {
535
+ return {
536
+ align: "left",
537
+ className: "",
538
+ component: null,
539
+ content: input,
540
+ id: `ext-${++generatedExtensionId}`,
541
+ order: 0,
542
+ props: {},
543
+ unstyled: false
544
+ };
793
545
  }
794
- domObserver.disconnect();
795
- domObserver = null;
796
- }
797
- function subscribe(listener) {
798
- listeners.add(listener);
799
- ensureDomObserver();
800
- emitIfSnapshotChanged();
801
- return () => {
802
- listeners.delete(listener);
803
- releaseDomObserver();
546
+ if (typeof input !== "object") return null;
547
+ const extension = input;
548
+ const component = isValidComponentType(extension.component) ? extension.component : null;
549
+ const content = isValidElement(extension.content) || typeof extension.content === "string" || typeof extension.content === "number" ? extension.content : null;
550
+ if (!component && content == null) return null;
551
+ const align = extension.align === "right" || extension.align === "end" ? "right" : extension.align === "center" ? "center" : "left";
552
+ return {
553
+ align,
554
+ className: typeof extension.className === "string" ? extension.className : "",
555
+ component,
556
+ content,
557
+ id: String(
558
+ extension.id || extension.key || `ext-${++generatedExtensionId}`
559
+ ),
560
+ order: Number.isFinite(Number(extension.order)) ? Number(extension.order) : 0,
561
+ props: isPlainObject(extension.props) ? extension.props : {},
562
+ unstyled: Boolean(extension.unstyled)
804
563
  };
805
564
  }
806
- function getSnapshot() {
807
- return getDomSnapshot();
565
+ function normalizeSurfaceFlowSnapshot(value) {
566
+ if (!isPlainObject(value)) return null;
567
+ return { ...value };
808
568
  }
809
- function useIsFullscreenStateActive() {
810
- return useSyncExternalStore(subscribe, getSnapshot, () => false);
569
+ function createSurfaceReturnHandshake(input) {
570
+ const source = typeof input === "string" ? { pathname: input } : input;
571
+ const pathname = typeof source?.pathname === "string" ? source.pathname.trim() : "";
572
+ if (!isSafeInternalHref(pathname)) return null;
573
+ return {
574
+ focusKey: typeof source?.focusKey === "string" && source.focusKey.trim() ? source.focusKey.trim() : null,
575
+ pathname,
576
+ restoreScroll: source?.restoreScroll !== false,
577
+ returnOnCancel: source?.returnOnCancel === true
578
+ };
811
579
  }
812
- var RegistryContext = getOrCreateGlobalContext(
813
- "RegistryContext",
814
- null
815
- );
816
- function useRegistryActions() {
817
- return useRequiredContext(
818
- RegistryContext,
819
- "useRegistryActions",
820
- "RegistryProvider"
821
- ).actions;
580
+ function resolveSurfaceFlowReturnHandshake(definition, input) {
581
+ const flowInput = input && typeof input === "object" ? input : null;
582
+ const inputHandshake = flowInput?.returnHandshake ?? (flowInput?.returnTo ? {
583
+ focusKey: flowInput.returnFocusKey,
584
+ pathname: flowInput.returnTo,
585
+ restoreScroll: flowInput.restoreReturnScroll,
586
+ returnOnCancel: flowInput.returnOnCancel
587
+ } : null);
588
+ const baseHandshake = definition?.returnHandshake;
589
+ if (!baseHandshake && !inputHandshake) return null;
590
+ return createSurfaceReturnHandshake({ ...baseHandshake, ...inputHandshake });
822
591
  }
823
- function useRegistrySubscription() {
824
- return useRequiredContext(
825
- RegistryContext,
826
- "useRegistrySubscription",
827
- "RegistryProvider"
828
- ).subscription;
592
+
593
+ // src/modules/dock/surface/context.tsx
594
+ var SurfaceExtensionsContext = createContext(null);
595
+ var SurfaceItemContext = createContext({
596
+ id: null,
597
+ width: null
598
+ });
599
+ function useSurfaceDimensions() {
600
+ const { width } = use(SurfaceItemContext);
601
+ return useMemo(() => ({ width }), [width]);
829
602
  }
830
- function useRegistryEntries(type) {
831
- const { getEntriesSnapshot, subscribe: subscribe2 } = useRegistrySubscription();
832
- const subscribeToType = useCallback(
833
- (listener) => subscribe2(type, null, listener),
834
- [subscribe2, type]
835
- );
836
- const getEntries = useCallback(
837
- () => getEntriesSnapshot(type),
838
- [getEntriesSnapshot, type]
603
+ function useSurfaceId() {
604
+ return use(SurfaceItemContext).id;
605
+ }
606
+ function useSurfaceHeader() {
607
+ const store = use(SurfaceExtensionsContext)?.headerActions ?? null;
608
+ const surfaceId = useSurfaceId();
609
+ return useCallback(
610
+ (patch) => {
611
+ if (!store) return;
612
+ const data = typeof patch === "function" ? patch({}) : patch;
613
+ if (data?.headerAction !== void 0)
614
+ store.setAction(surfaceId, data.headerAction);
615
+ },
616
+ [store, surfaceId]
839
617
  );
840
- return useSyncExternalStore(subscribeToType, getEntries, getEntries);
841
618
  }
842
- var EMPTY_MODULES = Object.freeze([]);
843
- var ModuleHostContext = getOrCreateGlobalContext("ModuleHostContext", null);
844
- function selectModuleSlice(coreModule, config) {
845
- const slice = coreModule.page?.select ? coreModule.page.select(config) : config[coreModule.id];
846
- return slice ?? null;
619
+ function useSurfaceAction(action) {
620
+ const store = use(SurfaceExtensionsContext)?.headerActions ?? null;
621
+ const surfaceId = useSurfaceId();
622
+ useEffect(() => {
623
+ if (!store || action === void 0) return;
624
+ store.setAction(surfaceId, action);
625
+ return () => store.removeAction(surfaceId);
626
+ }, [store, surfaceId, action]);
847
627
  }
848
- function createModulePayload(modules, config, metadata = {}) {
849
- const payload = {};
850
- let hasSlices = false;
851
- for (const coreModule of modules) {
852
- if (!coreModule.page?.entries) continue;
853
- const slice = selectModuleSlice(coreModule, config);
854
- if (slice === null) continue;
855
- payload[coreModule.id] = slice;
856
- hasSlices = true;
628
+ var SurfaceHeaderActionStore = class {
629
+ store = createStore({
630
+ actionsBySurface: {},
631
+ version: 0
632
+ });
633
+ get version() {
634
+ return this.store.getSnapshot().version;
857
635
  }
858
- if (!hasSlices) return null;
859
- return {
860
- ...payload,
861
- registry: {
862
- ...metadata,
863
- ...isObject(config.registry) ? config.registry : {}
864
- }
636
+ subscribe = (listener) => this.store.subscribe(listener);
637
+ getAction = (surfaceId) => {
638
+ const sId = surfaceId != null ? String(surfaceId) : "active";
639
+ const { actionsBySurface } = this.store.getSnapshot();
640
+ return actionsBySurface[sId] ?? actionsBySurface.active ?? actionsBySurface.global ?? null;
865
641
  };
866
- }
867
- function useInstalledModules() {
868
- return use(ModuleHostContext)?.modules ?? EMPTY_MODULES;
869
- }
870
-
871
- // src/kernel/handlers.ts
872
- function isPersistentLifecycle(lifecycle) {
873
- return lifecycle === REGISTRY_LIFECYCLES.PERSISTENT;
874
- }
875
- function runRegistrationBatch(context, executor) {
876
- return typeof context.batch === "function" ? context.batch(executor) : executor(context);
877
- }
878
- function createEntryCleanupKey(type, key, source, instanceId, scope) {
879
- return JSON.stringify([
880
- "entry",
881
- type,
882
- key,
883
- scope ?? null,
884
- source,
885
- instanceId
886
- ]);
887
- }
888
- function getCleanupScope(context) {
889
- return context?.cleanupScope || /* @__PURE__ */ new Map();
890
- }
891
- function clearCleanupTimer(scope, cleanupKey) {
892
- const lifecycle = scope.get(cleanupKey);
893
- if (!lifecycle) return;
894
- lifecycle.cancelled = true;
895
- if (lifecycle.timerId !== null) clearTimeout(lifecycle.timerId);
896
- scope.delete(cleanupKey);
897
- }
898
- function scheduleCleanup(scope, cleanupKey, callback, delayMs) {
899
- clearCleanupTimer(scope, cleanupKey);
900
- const lifecycle = {
901
- cancelled: false,
902
- timerId: null
642
+ setAction = (surfaceId, action) => {
643
+ const sId = surfaceId != null ? String(surfaceId) : "active";
644
+ const current = this.store.getSnapshot();
645
+ if (current.actionsBySurface[sId] === action) return;
646
+ this.store.setState({
647
+ actionsBySurface: {
648
+ ...current.actionsBySurface,
649
+ [sId]: action
650
+ },
651
+ version: current.version + 1
652
+ });
903
653
  };
904
- lifecycle.timerId = setTimeout(() => {
905
- if (lifecycle.cancelled || scope.get(cleanupKey) !== lifecycle) return;
906
- scope.delete(cleanupKey);
907
- callback();
908
- }, delayMs);
909
- scope.set(cleanupKey, lifecycle);
910
- }
911
- function collectModuleRegistrations(coreModule, slice, pageMetadata, context) {
912
- const definition = coreModule.registry;
913
- const toEntries = coreModule.page?.entries;
914
- if (!definition || !toEntries || slice == null || slice === false) return [];
915
- const type = coreModule.id;
916
- const items = Array.isArray(slice) ? slice : [slice];
917
- const registrations = [];
918
- for (const item of items) {
919
- if (item == null || item === false) continue;
920
- const itemMetadata = isObject(item) && isObject(item.registry) ? item.registry : {};
921
- const metadata = { ...pageMetadata, ...itemMetadata };
922
- if (metadata.validation === "strict" && !validateRegistryMetadata(metadata).valid) {
923
- continue;
924
- }
925
- const payload = isObject(item) ? Object.fromEntries(
926
- Object.entries(item).filter(([key]) => key !== "registry")
927
- ) : item;
928
- const { cleanupDelayMs, lifecycle, registerOptions, scope, source } = normalizeRegistryMetadata(metadata, {
929
- defaultCleanupDelayMs: definition.cleanupDelayMs ?? null,
930
- defaultLifecycle: definition.lifecycle,
931
- defaultSource: DEFAULT_SOURCE
654
+ removeAction = (surfaceId) => {
655
+ const sId = surfaceId != null ? String(surfaceId) : "active";
656
+ const current = this.store.getSnapshot();
657
+ if (!(sId in current.actionsBySurface)) return;
658
+ const { [sId]: _, ...nextActions } = current.actionsBySurface;
659
+ this.store.setState({
660
+ actionsBySurface: nextActions,
661
+ version: current.version + 1
932
662
  });
933
- for (const { key, value } of toEntries(payload, context) ?? []) {
934
- const validation = definition.validate?.(value);
935
- if (validation && !validation.valid) {
936
- report(`Registry ${type} config is invalid`, validation.issues, "warn");
937
- continue;
663
+ };
664
+ clearSurface = (surfaceId) => this.removeAction(surfaceId);
665
+ };
666
+ var SurfaceExtensionsStore = class {
667
+ extensionsBySurface = /* @__PURE__ */ new Map();
668
+ cachedListBySurface = /* @__PURE__ */ new Map();
669
+ store = createStore({ version: 0 });
670
+ get version() {
671
+ return this.store.getSnapshot().version;
672
+ }
673
+ subscribe = (listener) => this.store.subscribe(listener);
674
+ notify = () => {
675
+ this.cachedListBySurface.clear();
676
+ this.store.setState((prev) => ({ version: prev.version + 1 }));
677
+ };
678
+ getExtensionsForSurface = (surfaceId) => {
679
+ const sId = surfaceId != null ? String(surfaceId) : "active";
680
+ if (this.cachedListBySurface.has(sId))
681
+ return this.cachedListBySurface.get(sId);
682
+ const globalExts = this.extensionsBySurface.get("global");
683
+ const activeExts = this.extensionsBySurface.get("active");
684
+ const surfaceExts = sId !== "global" && sId !== "active" ? this.extensionsBySurface.get(sId) : void 0;
685
+ let fallbackExts;
686
+ if (!globalExts && !activeExts && !surfaceExts && this.extensionsBySurface.size > 0) {
687
+ for (const map of this.extensionsBySurface.values()) {
688
+ if (map.size > 0) {
689
+ fallbackExts = map;
690
+ break;
691
+ }
938
692
  }
939
- registrations.push({
940
- cleanupDelayMs,
941
- cleanupKey: createEntryCleanupKey(
942
- type,
943
- key,
944
- source,
945
- context.instanceId,
946
- scope
947
- ),
948
- key,
949
- lifecycle,
950
- registerOptions,
951
- scope,
952
- source,
953
- type,
954
- value
955
- });
956
693
  }
957
- }
958
- return registrations;
959
- }
960
- var NO_CLEANUP = () => {
961
- };
962
- function applyRegistryConfig(config, context) {
963
- const modules = context.modules ?? [];
964
- if (!config || modules.length === 0) return NO_CLEANUP;
965
- const pageMetadata = isObject(config.registry) ? config.registry : {};
966
- const slices = config;
967
- const registrations = modules.flatMap((coreModule) => {
968
- try {
969
- return collectModuleRegistrations(
970
- coreModule,
971
- slices[coreModule.id],
972
- pageMetadata,
973
- context
974
- );
975
- } catch (error) {
976
- report(`Registry apply (${coreModule.id})`, error);
977
- return [];
694
+ if (!globalExts && !activeExts && !surfaceExts && !fallbackExts) {
695
+ const empty = [];
696
+ this.cachedListBySurface.set(sId, empty);
697
+ return empty;
978
698
  }
979
- });
980
- if (registrations.length === 0) return NO_CLEANUP;
981
- const cleanupScope = getCleanupScope(context);
982
- const owner = (registration) => ({
983
- instanceId: context.instanceId,
984
- scope: registration.scope,
985
- source: registration.source
986
- });
987
- runRegistrationBatch(context, (queue) => {
988
- registrations.forEach(
989
- ({ cleanupKey, key, registerOptions, source, type, value }) => {
990
- clearCleanupTimer(cleanupScope, cleanupKey);
991
- queue.register(type, key, value, source, registerOptions);
992
- }
699
+ const merged = new Map([
700
+ ...globalExts || [],
701
+ ...activeExts || [],
702
+ ...surfaceExts || [],
703
+ ...fallbackExts || []
704
+ ]);
705
+ const result = Array.from(merged.values()).sort(
706
+ (a, b) => a.order - b.order
993
707
  );
994
- });
995
- return () => {
996
- const immediate = [];
997
- registrations.forEach((registration) => {
998
- if (isPersistentLifecycle(registration.lifecycle)) return;
999
- const { cleanupDelayMs, cleanupKey, key, type } = registration;
1000
- if (typeof cleanupDelayMs === "number" && cleanupDelayMs > 0) {
1001
- scheduleCleanup(
1002
- cleanupScope,
1003
- cleanupKey,
1004
- () => context.unregister(type, key, owner(registration)),
1005
- cleanupDelayMs
1006
- );
1007
- } else {
1008
- immediate.push(registration);
1009
- }
1010
- });
1011
- if (immediate.length === 0) return;
1012
- runRegistrationBatch(context, (queue) => {
1013
- immediate.forEach(
1014
- (registration) => queue.unregister(
1015
- registration.type,
1016
- registration.key,
1017
- owner(registration)
1018
- )
1019
- );
1020
- });
708
+ this.cachedListBySurface.set(sId, result);
709
+ return result;
1021
710
  };
1022
- }
1023
-
1024
- // src/kernel/hooks.ts
1025
- function withInstanceId(instanceId, sourceOrOptions, optionsArg) {
1026
- if (typeof sourceOrOptions === "string") {
1027
- return {
1028
- optionsArg: { ...isObject(optionsArg) ? optionsArg : {}, instanceId },
1029
- sourceOrOptions
1030
- };
1031
- }
1032
- if (isObject(sourceOrOptions)) {
1033
- return {
1034
- optionsArg,
1035
- sourceOrOptions: { ...sourceOrOptions, instanceId }
1036
- };
1037
- }
1038
- return {
1039
- optionsArg: void 0,
1040
- sourceOrOptions: {
1041
- ...isObject(optionsArg) ? optionsArg : {},
1042
- instanceId
711
+ setExtension = (surfaceId, extension) => {
712
+ if (!extension) return;
713
+ const normalized = normalizeSurfaceExtension(extension);
714
+ if (!normalized) return;
715
+ const sId = surfaceId != null ? String(surfaceId) : "active";
716
+ let surfaceMap = this.extensionsBySurface.get(sId);
717
+ if (!surfaceMap) {
718
+ surfaceMap = /* @__PURE__ */ new Map();
719
+ this.extensionsBySurface.set(sId, surfaceMap);
1043
720
  }
721
+ const prev = surfaceMap.get(normalized.id);
722
+ if (prev && prev.content === normalized.content && prev.align === normalized.align && prev.order === normalized.order && prev.className === normalized.className && prev.unstyled === normalized.unstyled && prev.component === normalized.component && prev.props === normalized.props)
723
+ return;
724
+ surfaceMap.set(normalized.id, normalized);
725
+ this.notify();
1044
726
  };
1045
- }
1046
- function withInstanceIdForUnregister(instanceId, sourceOrOptions) {
1047
- if (typeof sourceOrOptions === "string") {
1048
- return { instanceId, source: sourceOrOptions };
1049
- }
1050
- if (isObject(sourceOrOptions)) {
1051
- return { ...sourceOrOptions, instanceId };
1052
- }
1053
- return { instanceId };
1054
- }
1055
- function resolveRegisterArgsWithInstance(instanceId, sourceOrOptions, optionsArg) {
1056
- const input = withInstanceId(instanceId, sourceOrOptions, optionsArg);
1057
- return [input.sourceOrOptions, input.optionsArg];
1058
- }
1059
- var MAX_STABILIZATION_DEPTH = 64;
1060
- function createStableFunctionEntry(fn) {
1061
- const entry = {
1062
- current: fn,
1063
- stable(...args) {
1064
- return entry.current?.apply(this, args);
727
+ removeExtension = (surfaceId, extensionId) => {
728
+ if (!extensionId) return;
729
+ const sId = surfaceId != null ? String(surfaceId) : "active";
730
+ let removed = false;
731
+ for (const [key, map] of this.extensionsBySurface.entries()) {
732
+ if (key === sId || key === "active" || key === "global") {
733
+ if (map.has(extensionId)) {
734
+ map.delete(extensionId);
735
+ if (map.size === 0) this.extensionsBySurface.delete(key);
736
+ removed = true;
737
+ }
738
+ }
1065
739
  }
740
+ if (removed) this.notify();
1066
741
  };
1067
- return entry;
1068
- }
1069
- function isDirectRegistryComponentPath(path, componentSlices) {
1070
- const match = /^config\.([^.[]+)\.[^.[]+$/.exec(path);
1071
- return Boolean(match && componentSlices.has(match[1]));
742
+ clearSurface = (surfaceId) => {
743
+ const sId = surfaceId != null ? String(surfaceId) : "active";
744
+ this.extensionsBySurface.delete(sId);
745
+ this.extensionsBySurface.delete("active");
746
+ this.notify();
747
+ };
748
+ };
749
+
750
+ // src/kernel/constants.ts
751
+ var DEFAULT_SOURCE = "dynamic";
752
+ var REGISTRY_SOURCES = Object.freeze({
753
+ STATIC: "static",
754
+ DYNAMIC: "dynamic",
755
+ USER: "user"
756
+ });
757
+ var REGISTRY_LIFECYCLES = Object.freeze({
758
+ IMMEDIATE: "immediate",
759
+ GRACEFUL: "graceful",
760
+ PERSISTENT: "persistent",
761
+ ROUTE: "route"
762
+ });
763
+ var REGISTRY_VALIDATION_MODES = Object.freeze({
764
+ WARN: "warn",
765
+ STRICT: "strict"
766
+ });
767
+ var REGISTRY_METADATA_KEYS = Object.freeze([
768
+ "cleanup",
769
+ "cleanupDelayMs",
770
+ "instanceId",
771
+ "lifecycle",
772
+ "priority",
773
+ "source",
774
+ "scope",
775
+ "validation"
776
+ ]);
777
+ Object.freeze({
778
+ [REGISTRY_SOURCES.STATIC]: 100,
779
+ [REGISTRY_SOURCES.DYNAMIC]: 200,
780
+ [REGISTRY_SOURCES.USER]: 300
781
+ });
782
+ Object.freeze({
783
+ [REGISTRY_SOURCES.STATIC]: 10,
784
+ [REGISTRY_SOURCES.DYNAMIC]: 20,
785
+ [REGISTRY_SOURCES.USER]: 30
786
+ });
787
+ var REGISTRY_LIFECYCLE_VALUES = new Set(
788
+ Object.values(REGISTRY_LIFECYCLES)
789
+ );
790
+ var REGISTRY_METADATA_KEY_SET = new Set(
791
+ REGISTRY_METADATA_KEYS
792
+ );
793
+ var REGISTRY_SCOPE_KINDS = Object.freeze({
794
+ APP: "app",
795
+ SESSION: "session",
796
+ ROUTE: "route",
797
+ INSTANCE: "instance",
798
+ WORKSPACE: "workspace"
799
+ });
800
+ REGISTRY_SCOPE_KINDS.APP;
801
+ function getOrCreateGlobalContext(name, defaultValue) {
802
+ const sym = /* @__PURE__ */ Symbol.for(`@omerdlw/base-framework/context/${name}`);
803
+ const store = globalThis;
804
+ if (!store[sym]) {
805
+ store[sym] = createContext(defaultValue);
806
+ }
807
+ return store[sym];
1072
808
  }
1073
- function isReactNodeLike(value) {
1074
- return value === null || value === void 0 || typeof value === "boolean" || typeof value === "string" || typeof value === "number" || isValidElement(value);
809
+
810
+ // src/kernel/utils.ts
811
+ function parseFiniteNumber(value) {
812
+ const parsed = Number(value);
813
+ return Number.isFinite(parsed) ? parsed : null;
1075
814
  }
1076
- function isStabilizableObject(value) {
1077
- return typeof value === "object" && value !== null && !Array.isArray(value) && !isValidElement(value);
815
+ function getValidString(value, fallback = null) {
816
+ return trimToNull(value) ?? fallback;
1078
817
  }
1079
- function hasSameObjectKeys(previousValue, nextValue) {
1080
- if (!isStabilizableObject(previousValue) || !isStabilizableObject(nextValue)) {
1081
- return false;
1082
- }
1083
- const previousKeys = Object.keys(previousValue);
1084
- const nextKeys = Object.keys(nextValue);
1085
- if (previousKeys.length !== nextKeys.length) return false;
1086
- return nextKeys.every(
1087
- (key) => Object.prototype.hasOwnProperty.call(previousValue, key)
1088
- );
818
+
819
+ // src/kernel/schema.ts
820
+ function resolveLifecycle(metadata, fallback) {
821
+ const lifecycle = metadata?.lifecycle ?? metadata?.cleanup;
822
+ return lifecycle && REGISTRY_LIFECYCLE_VALUES.has(lifecycle) ? lifecycle : fallback;
1089
823
  }
1090
- function stabilizeRegistryValue(value, path, scope, previousValue, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1091
- if (depth > MAX_STABILIZATION_DEPTH) return value;
1092
- if (typeof value === "function") {
1093
- const isComponent = value.name && /^[A-Z]/.test(value.name);
1094
- if (isComponent || isDirectRegistryComponentPath(path, scope.componentSlices)) {
1095
- return value;
1096
- }
1097
- scope.usedPaths.add(path);
1098
- let entry = scope.functionEntries.get(path);
1099
- if (!entry) {
1100
- entry = createStableFunctionEntry(value);
1101
- scope.functionEntries.set(path, entry);
1102
- } else {
1103
- entry.current = value;
1104
- }
1105
- return entry.stable;
1106
- }
1107
- if (value && typeof value === "object") {
1108
- if (seen.has(value)) return value;
1109
- seen.add(value);
824
+ function validateRegistryMetadata(metadata) {
825
+ if (metadata === void 0 || metadata === null)
826
+ return { issues: [], valid: true };
827
+ if (!isObject(metadata))
828
+ return { issues: ["registry metadata must be an object"], valid: false };
829
+ const issues = [];
830
+ Object.keys(metadata).forEach((key) => {
831
+ if (!REGISTRY_METADATA_KEY_SET.has(key))
832
+ issues.push(`unknown registry metadata field: ${key}`);
833
+ });
834
+ if (metadata.source !== void 0 && !getValidString(metadata.source))
835
+ issues.push("source must be a non-empty string");
836
+ if (metadata.instanceId !== void 0 && !getValidString(metadata.instanceId))
837
+ issues.push("instanceId must be a non-empty string");
838
+ if (metadata.scope !== void 0 && !getValidString(metadata.scope))
839
+ issues.push("scope must be a non-empty string");
840
+ if (metadata.priority !== void 0 && parseFiniteNumber(metadata.priority) === null)
841
+ issues.push("priority must be a finite number");
842
+ if (metadata.cleanupDelayMs !== void 0) {
843
+ const delay = parseFiniteNumber(metadata.cleanupDelayMs);
844
+ if (delay === null || delay < 0)
845
+ issues.push("cleanupDelayMs must be a non-negative finite number");
1110
846
  }
1111
- if (isValidElement(value)) {
1112
- const nextProps = stabilizeRegistryValue(
1113
- value.props,
1114
- `${path}.props`,
1115
- scope,
1116
- isValidElement(previousValue) ? previousValue.props : void 0,
1117
- seen,
1118
- depth + 1
847
+ const lifecycle = metadata.lifecycle ?? metadata.cleanup;
848
+ if (lifecycle !== void 0 && !REGISTRY_LIFECYCLE_VALUES.has(String(lifecycle))) {
849
+ issues.push(
850
+ `lifecycle must be one of: ${[...REGISTRY_LIFECYCLE_VALUES].join(", ")}`
1119
851
  );
1120
- if (isValidElement(previousValue) && previousValue.type === value.type && previousValue.key === value.key && previousValue.props === nextProps) {
1121
- return previousValue;
1122
- }
1123
- return cloneElement(value, nextProps);
1124
- }
1125
- if (Array.isArray(value)) {
1126
- const nextValue = value.every(isReactNodeLike) ? Children.toArray(value) : value;
1127
- const previousArray = Array.isArray(previousValue) ? previousValue : null;
1128
- let hasChanged2 = !previousArray || previousArray.length !== nextValue.length;
1129
- const stabilizedValue2 = nextValue.map((item, index) => {
1130
- const nextItem = stabilizeRegistryValue(
1131
- item,
1132
- `${path}[${index}]`,
1133
- scope,
1134
- previousArray?.[index],
1135
- seen,
1136
- depth + 1
1137
- );
1138
- if (!previousArray || nextItem !== previousArray[index]) {
1139
- hasChanged2 = true;
1140
- }
1141
- return nextItem;
1142
- });
1143
- return !hasChanged2 ? previousArray : stabilizedValue2;
1144
852
  }
1145
- if (typeof value !== "object" || value === null) {
1146
- return value;
853
+ if (metadata.validation !== void 0 && !Object.values(REGISTRY_VALIDATION_MODES).includes(
854
+ metadata.validation
855
+ )) {
856
+ issues.push(
857
+ `validation must be one of: ${Object.values(REGISTRY_VALIDATION_MODES).join(", ")}`
858
+ );
1147
859
  }
1148
- const source = value;
1149
- const previousRecord = previousValue;
1150
- const stabilizedValue = {};
1151
- const canReusePrevious = hasSameObjectKeys(previousValue, value);
1152
- let hasChanged = !canReusePrevious;
1153
- Object.keys(source).forEach((key) => {
1154
- const nextValue = stabilizeRegistryValue(
1155
- source[key],
1156
- `${path}.${key}`,
1157
- scope,
1158
- previousRecord?.[key],
1159
- seen,
1160
- depth + 1
1161
- );
1162
- stabilizedValue[key] = nextValue;
1163
- if (!canReusePrevious || nextValue !== previousRecord?.[key]) {
1164
- hasChanged = true;
1165
- }
1166
- });
1167
- return !hasChanged ? previousValue : stabilizedValue;
1168
- }
1169
- function useAppliedRegistryConfig(payload, context, componentSlices) {
1170
- const functionEntriesRef = useRef(/* @__PURE__ */ new Map());
1171
- const appliedRef = useRef(null);
1172
- useIsomorphicLayoutEffect(() => {
1173
- const previous = appliedRef.current;
1174
- if (previous && previous.payload === payload && previous.context === context) {
1175
- return;
1176
- }
1177
- const functionEntries = functionEntriesRef.current;
1178
- const usedPaths = /* @__PURE__ */ new Set();
1179
- const config = stabilizeRegistryValue(
1180
- payload,
1181
- "config",
1182
- { componentSlices, functionEntries, usedPaths },
1183
- previous?.config
1184
- );
1185
- functionEntries.forEach((_entry, path) => {
1186
- if (!usedPaths.has(path)) functionEntries.delete(path);
1187
- });
1188
- if (previous && previous.config === config && previous.context === context) {
1189
- previous.payload = payload;
1190
- return;
1191
- }
1192
- previous?.cleanup();
1193
- appliedRef.current = {
1194
- cleanup: applyRegistryConfig(config, context),
1195
- config,
1196
- context,
1197
- payload
1198
- };
1199
- });
1200
- useIsomorphicLayoutEffect(
1201
- () => () => {
1202
- appliedRef.current?.cleanup();
1203
- appliedRef.current = null;
1204
- },
1205
- []
1206
- );
860
+ return { issues, valid: issues.length === 0 };
1207
861
  }
1208
- function createRegistryApplyContext(actions, {
1209
- cleanupScope = /* @__PURE__ */ new Map(),
1210
- instanceId,
1211
- modules = [],
1212
- pathname
1213
- }) {
1214
- const stamp = (queue) => ({
1215
- register: (type, key, item, sourceOrOptions, optionsArg) => queue.register(
1216
- type,
1217
- key,
1218
- item,
1219
- ...resolveRegisterArgsWithInstance(
1220
- instanceId,
1221
- sourceOrOptions,
1222
- optionsArg
1223
- )
1224
- ),
1225
- unregister: (type, key, sourceOrOptions) => queue.unregister(
1226
- type,
1227
- key,
1228
- withInstanceIdForUnregister(instanceId, sourceOrOptions)
1229
- )
1230
- });
862
+ function normalizeRegistryMetadata(metadata, {
863
+ defaultCleanupDelayMs = null,
864
+ defaultLifecycle = null,
865
+ defaultSource = DEFAULT_SOURCE
866
+ } = {}) {
867
+ const registryMeta = isObject(metadata) ? metadata : {};
868
+ const normalizedDefaultDelay = parseFiniteNumber(defaultCleanupDelayMs);
869
+ const fallbackCleanupDelayMs = normalizedDefaultDelay !== null && normalizedDefaultDelay >= 0 ? normalizedDefaultDelay : null;
870
+ const fallbackLifecycle = defaultLifecycle && REGISTRY_LIFECYCLE_VALUES.has(defaultLifecycle) ? defaultLifecycle : null;
871
+ const source = getValidString(registryMeta.source) || getValidString(defaultSource, DEFAULT_SOURCE) || DEFAULT_SOURCE;
872
+ const scope = getValidString(registryMeta.scope);
873
+ const priority = parseFiniteNumber(registryMeta.priority);
874
+ const lifecycle = resolveLifecycle(registryMeta, fallbackLifecycle);
875
+ const validation = isValidationMode(registryMeta.validation) ? registryMeta.validation : null;
876
+ const cleanupDelayCandidate = parseFiniteNumber(registryMeta.cleanupDelayMs);
877
+ let cleanupDelayMs = cleanupDelayCandidate !== null && cleanupDelayCandidate >= 0 ? cleanupDelayCandidate : fallbackCleanupDelayMs;
878
+ if (lifecycle === REGISTRY_LIFECYCLES.IMMEDIATE) {
879
+ cleanupDelayMs = 0;
880
+ } else if ((lifecycle === REGISTRY_LIFECYCLES.GRACEFUL || lifecycle === REGISTRY_LIFECYCLES.ROUTE) && cleanupDelayMs === null) {
881
+ cleanupDelayMs = fallbackCleanupDelayMs;
882
+ }
1231
883
  return {
1232
- ...stamp(actions),
1233
- batch: (executor) => typeof executor === "function" ? actions.batch((queue) => executor(stamp(queue))) : 0,
1234
- cleanupScope,
1235
- instanceId,
1236
- modules,
1237
- pathname
884
+ cleanupDelayMs,
885
+ lifecycle,
886
+ priority,
887
+ registerOptions: {
888
+ ...priority !== null && { priority },
889
+ ...validation === REGISTRY_VALIDATION_MODES.STRICT && { validation },
890
+ ...scope && { scope }
891
+ },
892
+ source,
893
+ ...scope && { scope },
894
+ ...validation && { validation }
1238
895
  };
1239
896
  }
1240
- function useRegistry(config, metadata) {
1241
- const { batch, register, unregister } = useRegistryActions();
1242
- const pathname = usePathname();
1243
- const modules = useInstalledModules();
1244
- const instanceId = `registry-instance-${useId()}`;
1245
- const [cleanupScope] = useState(
1246
- () => /* @__PURE__ */ new Map()
1247
- );
1248
- const context = useMemo(
1249
- () => createRegistryApplyContext(
1250
- { batch, register, unregister },
1251
- {
1252
- cleanupScope,
1253
- instanceId,
1254
- modules,
1255
- pathname
1256
- }
1257
- ),
1258
- [batch, cleanupScope, instanceId, modules, pathname, register, unregister]
1259
- );
1260
- const payload = useMemo(
1261
- () => config ? createModulePayload(modules, config, metadata) : null,
1262
- [config, metadata, modules]
1263
- );
1264
- const componentSlices = useMemo(
1265
- () => new Set(
1266
- modules.filter(
1267
- (coreModule) => coreModule.registry?.valueKind === "component"
1268
- ).map((coreModule) => coreModule.id)
1269
- ),
1270
- [modules]
897
+ function isValidationMode(value) {
898
+ return Object.values(REGISTRY_VALIDATION_MODES).includes(value);
899
+ }
900
+ function pickRegistryMetadata(options) {
901
+ if (!isObject(options)) return {};
902
+ return Object.fromEntries(
903
+ REGISTRY_METADATA_KEYS.filter((key) => options[key] !== void 0).map(
904
+ (key) => [key, options[key]]
905
+ )
1271
906
  );
1272
- useAppliedRegistryConfig(payload, context, componentSlices);
1273
907
  }
1274
- function defineModule(coreModule) {
1275
- return Object.freeze({ ...coreModule });
908
+ function useRequiredContext(context, hookName, providerName) {
909
+ const value = use(context);
910
+ if (value === null) {
911
+ throw new Error(`${hookName} must be used within ${providerName}`);
912
+ }
913
+ return value;
1276
914
  }
1277
- createStore(void 0);
1278
-
1279
- // src/kernel/adapters.ts
1280
- function useModuleRegistration(id, value, options) {
1281
- const coreModule = useInstalledModules().find(
1282
- (candidate) => candidate.id === id
915
+ function useStore(store, selector = (state) => state, isEqual = Object.is) {
916
+ const cacheRef = useRef({
917
+ isEqual: null,
918
+ selected: void 0,
919
+ selector: null,
920
+ snapshot: null
921
+ });
922
+ const getSelectedSnapshot = useCallback(() => {
923
+ const snapshot = store.getSnapshot();
924
+ const cache = cacheRef.current;
925
+ if (cache.snapshot === snapshot && cache.selector === selector && cache.isEqual === isEqual) {
926
+ return cache.selected;
927
+ }
928
+ const selected = selector(snapshot);
929
+ if (cache.snapshot !== null && cache.isEqual === isEqual && isEqual(cache.selected, selected)) {
930
+ cache.snapshot = snapshot;
931
+ cache.selector = selector;
932
+ return cache.selected;
933
+ }
934
+ cacheRef.current = {
935
+ isEqual,
936
+ selected,
937
+ selector,
938
+ snapshot
939
+ };
940
+ return selected;
941
+ }, [isEqual, selector, store]);
942
+ return useSyncExternalStore(
943
+ store.subscribe,
944
+ getSelectedSnapshot,
945
+ getSelectedSnapshot
1283
946
  );
1284
- const config = coreModule && options?.enabled !== false && value != null ? { [id]: value } : null;
1285
- useRegistry(config, pickRegistryMetadata(options));
1286
- }
1287
-
1288
- // src/theme.tsx
1289
- function defineThemeSpec(id) {
1290
- return Object.freeze({ id });
1291
947
  }
1292
- var EMPTY = /* @__PURE__ */ new Map();
1293
- var ThemeContext = getOrCreateGlobalContext("ThemeContext", EMPTY);
1294
- function useTheme(spec) {
1295
- const config = useContext(ThemeContext).get(spec.id);
1296
- if (!config) {
1297
- throw new Error(
1298
- `Theme "${spec.id}" is missing. Ensure its theme configuration is provided to <ThemeProvider themes={[...]}> (e.g. in src/config/ or your theme setup).`
1299
- );
1300
- }
1301
- return {
1302
- slots: config.slots,
1303
- styles: config.styles ?? {}
1304
- };
948
+ function useClickOutside(ref, callback) {
949
+ const handlePointer = useCallback(
950
+ (event) => {
951
+ if (ref?.current && !ref.current.contains(event.target)) {
952
+ callback?.(event);
953
+ }
954
+ },
955
+ [callback, ref]
956
+ );
957
+ useEffect(() => {
958
+ document.addEventListener("pointerdown", handlePointer);
959
+ return () => document.removeEventListener("pointerdown", handlePointer);
960
+ }, [handlePointer]);
1305
961
  }
962
+ var useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
1306
963
 
1307
- // src/modules/dock/constants.ts
1308
- var DOCK_STYLE_SECTIONS = Object.freeze([
1309
- "card",
1310
- "icon",
1311
- "title",
1312
- "description"
1313
- ]);
1314
- var DOCK_EVENTS = Object.freeze({
1315
- NAVIGATE_START: "DOCK_NAVIGATE_START",
1316
- NAVIGATE_END: "DOCK_NAVIGATE_END",
1317
- UPDATE_BADGE: "DOCK_UPDATE_BADGE",
1318
- NAVIGATE: "DOCK_NAVIGATE",
1319
- GUARD: "DOCK_GUARD",
1320
- NOT_FOUND: "DOCK_NOT_FOUND",
1321
- STATUS_SET: "DOCK_STATUS_SET",
1322
- STATUS_CLEAR: "DOCK_STATUS_CLEAR"
1323
- });
1324
- var DOCK_HUD_RENDER_MODE = Object.freeze({
1325
- COMPONENT: "component",
1326
- NODE: "node"
1327
- });
1328
- var DOCK_HUD_VARIANT = Object.freeze({
1329
- EXPANDED: "expanded",
1330
- PROGRESS: "progress",
1331
- CUSTOM: "custom"
1332
- });
1333
- var DOCK_HUD_PRIORITY = Object.freeze({
1334
- TASK_PROGRESS: 30,
1335
- CONTEXTUAL: 10,
1336
- SELECTION: 20,
1337
- CRITICAL: 50,
1338
- DEFAULT: 0,
1339
- MEDIA: 15
1340
- });
1341
- var DOCK_ATTENTION_KIND = Object.freeze({
1342
- SURFACE: "surface",
1343
- OPERATION: "operation",
1344
- LOADING: "loading",
1345
- STATUS: "status",
1346
- ROUTE: "route",
1347
- HUD: "hud"
1348
- });
1349
- var DOCK_ATTENTION_PRIORITY = Object.freeze({
1350
- STATUS_OVERLAY: 300,
1351
- SURFACE: 400,
1352
- OPERATION: 250,
1353
- LOADING: 100,
1354
- STATUS: 75,
1355
- ROUTE: 0,
1356
- HUD: 200
1357
- });
1358
- var DOCK_ATTENTION_PRIORITY_OFFSET_MAX = 99;
1359
- var DOCK_SURFACE_RENDER_MODE = Object.freeze({
1360
- COMPONENT: "component",
1361
- NODE: "node"
1362
- });
1363
- var DOCK_SURFACE_FLOW_STATUS = Object.freeze({
1364
- OPEN: "open",
1365
- COMPLETED: "completed",
1366
- CANCELLED: "cancelled"
1367
- });
1368
- var DOCK_SURFACE_PHASE = Object.freeze({
1369
- IDLE: "idle",
1370
- DISMISSING_ACTION: "dismissing_action",
1371
- EXPANDING_BODY: "expanding_body",
1372
- OPEN: "open",
1373
- CLOSING_ANTICIPATION: "closing_anticipation",
1374
- COLLAPSING_BODY: "collapsing_body",
1375
- RESTORING_HEADER: "restoring_header"
1376
- });
1377
- var VIEWPORT_MARGIN = 24;
1378
- var DOCK_CARD_DIMENSIONS = Object.freeze({
1379
- expandedY: -72,
1380
- chromeHeight: 20,
1381
- collapsedY: -10,
1382
- extensionShelfY: -42,
1383
- extensionShelfScale: 0.94,
1384
- extensionShelfHeight: 45,
1385
- hudHeight: 52,
1386
- actionGap: 10,
1387
- height: 68
1388
- });
1389
- var DOCK_CARD_LAYOUT = Object.freeze({
1390
- collapsed: Object.freeze({
1391
- offsetY: DOCK_CARD_DIMENSIONS.collapsedY,
1392
- scale: 0.88
1393
- }),
1394
- expanded: Object.freeze({
1395
- offsetY: DOCK_CARD_DIMENSIONS.expandedY,
1396
- scale: 1
1397
- }),
1398
- extensionShelfY: DOCK_CARD_DIMENSIONS.extensionShelfY,
1399
- extensionShelfScale: DOCK_CARD_DIMENSIONS.extensionShelfScale,
1400
- extensionShelfHeight: DOCK_CARD_DIMENSIONS.extensionShelfHeight,
1401
- chromeHeight: DOCK_CARD_DIMENSIONS.chromeHeight,
1402
- hudHeight: DOCK_CARD_DIMENSIONS.hudHeight,
1403
- actionGap: DOCK_CARD_DIMENSIONS.actionGap,
1404
- baseHeight: DOCK_CARD_DIMENSIONS.height
1405
- });
1406
- var DOCK_TRANSACTION_EVENTS = Object.freeze({
1407
- START: "START",
1408
- COMPLETE: "COMPLETE",
1409
- CANCEL: "CANCEL",
1410
- FAIL: "FAIL",
1411
- TIME_OUT: "TIME_OUT"
1412
- });
1413
- var DOCK_TRANSACTION_STATUS = Object.freeze({
1414
- PENDING: "pending",
1415
- COMPLETED: "completed",
1416
- CANCELLED: "cancelled",
1417
- FAILED: "failed",
1418
- TIMED_OUT: "timed-out"
1419
- });
1420
- var DOCK_TRANSACTION_REASON = Object.freeze({
1421
- GUARD: "guard",
1422
- SUPERSEDED: "superseded",
1423
- TIME_OUT: "time-out"
1424
- });
1425
- var DOCK_TRANSACTION_TIMEOUT_MS = 15e3;
1426
- var DOCK_PREFETCH_INTENT_DELAY_MS = 90;
1427
- var DOCK_CONTINUITY_EVENTS = Object.freeze({
1428
- CLEAR: "CLEAR",
1429
- CONSUME_RETURN: "CONSUME_RETURN",
1430
- DELIVER_RETURN: "DELIVER_RETURN",
1431
- RECORD: "RECORD",
1432
- REMOVE: "REMOVE"
1433
- });
1434
- var DOCK_CONTINUITY_MAX_ENTRIES = 32;
1435
- var DOCK_SURFACE_RETURN_MAX_ENTRIES = 16;
1436
- var DOCK_OPERATION_EVENTS = Object.freeze({
1437
- CANCEL: "CANCEL",
1438
- CLEAR: "CLEAR",
1439
- COMPLETE: "COMPLETE",
1440
- START: "START",
1441
- UPDATE: "UPDATE"
1442
- });
1443
- var DOCK_OPERATION_STATUS = Object.freeze({
1444
- CANCELLED: "cancelled",
1445
- COMPLETED: "completed",
1446
- PENDING: "pending"
1447
- });
1448
- var DOCK_OPERATION_MAX_ENTRIES = 24;
1449
- var DOCK_FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
1450
- var DOCK_FOCUS_RESTORE_BLOCKED_REASONS = Object.freeze([
1451
- "browser-back",
1452
- "dock",
1453
- "unmount"
1454
- ]);
1455
- var DOCK_LIFECYCLE = Object.freeze({
1456
- CLOSING: "closing",
1457
- OPENING: "opening",
1458
- IDLE: "idle",
1459
- OPEN: "open"
1460
- });
1461
- var PLAYBACK_RATES = Object.freeze([1, 1.25, 1.5, 2]);
1462
- var STATUS_PRIORITY = Object.freeze({
1463
- GUARD: 120,
1464
- ACCOUNT_DELETE: 115,
1465
- APP_ERROR: 100,
1466
- API_ERROR: 95,
1467
- NOT_FOUND: 97,
1468
- OFFLINE: 90,
1469
- LOGOUT: 110,
1470
- SIGNUP: 110,
1471
- LOGIN: 110,
1472
- ONLINE: 10
964
+ // src/events.ts
965
+ var EVENT_TYPES = Object.freeze({
966
+ API_UNAUTHORIZED: "API_UNAUTHORIZED",
967
+ STATE_CHANGE: "STATE_CHANGE",
968
+ API_ERROR: "API_ERROR",
969
+ APP_ERROR: "APP_ERROR"
1473
970
  });
1474
- var ERROR_STATUS_TYPES = /* @__PURE__ */ new Set([
1475
- "GUARD",
1476
- "ACCOUNT_DELETE",
1477
- "APP_ERROR",
1478
- "API_ERROR",
1479
- "NOT_FOUND"
1480
- ]);
1481
- var OVERLAY_STATUS_STORAGE_KEY = "dock_overlay_status";
1482
- var OVERLAY_STATUS_CLEAR_DURATION = 3e3;
1483
- var STATUS_CLEAR_DURATION = 4500;
1484
- var API_ERROR_BATCH_DELAY = 300;
1485
- var DOCK_HEIGHT_BUFFER = 16;
1486
- var HEIGHT_EPSILON = 0.5;
1487
- var MAX_VISIBLE_STACKED_CARDS = 3;
1488
- var dockTheme = defineThemeSpec("dock");
1489
-
1490
- // src/modules/dock/utils.ts
1491
- function isSamePath(left, right) {
1492
- const normalizedLeft = normalizePath(left);
1493
- const normalizedRight = normalizePath(right);
1494
- return Boolean(
1495
- normalizedLeft && normalizedRight && normalizedLeft === normalizedRight
1496
- );
1497
- }
1498
- function isPathPrefix(candidatePath, pathname) {
1499
- const normalizedCandidate = normalizePath(candidatePath);
1500
- const normalizedPathname = normalizePath(pathname);
1501
- if (!normalizedCandidate || !normalizedPathname) return false;
1502
- if (normalizedCandidate === normalizedPathname) return true;
1503
- if (normalizedCandidate === "/") return normalizedPathname.startsWith("/");
1504
- return normalizedPathname.startsWith(`${normalizedCandidate}/`);
1505
- }
1506
- function isInlineActionPathMatch(path, pathname) {
1507
- return isSamePath(path, pathname) || path !== "/" && isPathPrefix(path, pathname);
1508
- }
1509
- function isSafeInternalHref(value) {
1510
- const href = typeof value === "string" ? value.trim() : "";
1511
- return href.startsWith("/") && !href.startsWith("//");
1512
- }
1513
- function getDockLocationKey({
1514
- hash = "",
1515
- pathname = "/",
1516
- search = ""
1517
- } = {}) {
1518
- const normalizedPathname = String(pathname || "/").trim() || "/";
1519
- const normalizedSearch = String(search || "").trim();
1520
- const normalizedHash = String(hash || "").trim();
1521
- const query = normalizedSearch ? normalizedSearch.startsWith("?") ? normalizedSearch : `?${normalizedSearch}` : "";
1522
- const fragment = normalizedHash ? normalizedHash.startsWith("#") ? normalizedHash : `#${normalizedHash}` : "";
1523
- return `${normalizedPathname}${query}${fragment}`;
1524
- }
1525
- function formatMediaTime(seconds = 0) {
1526
- const sec = Math.max(0, Math.floor(Number(seconds) || 0));
1527
- const m = Math.floor(sec / 60);
1528
- const s = sec % 60;
1529
- return `${m}:${s < 10 ? "0" : ""}${s}`;
1530
- }
1531
- function isValidBannerUrl(banner) {
1532
- const value = trimToNull(banner);
1533
- if (!value) return false;
1534
- return /^(https?:\/\/|\/|data:image\/)/.test(value);
1535
- }
1536
- function toObject(value) {
1537
- return isObject(value) ? value : {};
1538
- }
1539
- function isValidComponentType(type) {
1540
- if (typeof type === "function") return true;
1541
- return Boolean(
1542
- type != null && typeof type === "object" && !isValidElement(type) && "$$typeof" in type
1543
- );
1544
- }
1545
- function resolveComponentType(...candidates) {
1546
- return candidates.find(isValidComponentType) ?? null;
1547
- }
1548
- function resolveRenderableContent(...candidates) {
1549
- return candidates.find(
1550
- (candidate) => candidate !== null && candidate !== void 0
1551
- ) ?? null;
1552
- }
1553
- function collectSearchableText(value, visitedObjects) {
1554
- if (typeof value === "string") return value;
1555
- if (typeof value === "number" || typeof value === "boolean")
1556
- return String(value);
1557
- if (Array.isArray(value)) {
1558
- if (visitedObjects.has(value)) return "";
1559
- visitedObjects.add(value);
1560
- const text = value.map((entry) => collectSearchableText(entry, visitedObjects)).join(" ");
1561
- visitedObjects.delete(value);
1562
- return text;
971
+ var EventEmitter = class {
972
+ events = /* @__PURE__ */ new Map();
973
+ debounceTimers = /* @__PURE__ */ new Map();
974
+ subscribe(event, callback) {
975
+ if (typeof event !== "string" || !event || typeof callback !== "function") {
976
+ return () => {
977
+ };
978
+ }
979
+ const listeners2 = this.events.get(event) || /* @__PURE__ */ new Set();
980
+ listeners2.add(callback);
981
+ this.events.set(event, listeners2);
982
+ return () => {
983
+ listeners2.delete(callback);
984
+ if (listeners2.size === 0) this.events.delete(event);
985
+ };
1563
986
  }
1564
- if (isValidElement(value)) {
1565
- if (visitedObjects.has(value)) return "";
1566
- visitedObjects.add(value);
1567
- const text = collectSearchableText(
1568
- value.props?.children,
1569
- visitedObjects
1570
- );
1571
- visitedObjects.delete(value);
1572
- return text;
987
+ emit(event, payload) {
988
+ const listeners2 = this.events.get(event);
989
+ if (!listeners2 || listeners2.size === 0) return;
990
+ const snapshot = Array.from(listeners2);
991
+ for (const callback of snapshot) {
992
+ try {
993
+ callback(payload);
994
+ } catch (error) {
995
+ report(`Event listener (${String(event)})`, error);
996
+ }
997
+ }
1573
998
  }
1574
- if (value && typeof value === "object") {
1575
- if (visitedObjects.has(value)) return "";
1576
- visitedObjects.add(value);
1577
- return Object.values(value).map((entry) => collectSearchableText(entry, visitedObjects)).join(" ");
999
+ emitDebounced(event, payload, waitMs = 100) {
1000
+ const existing = this.debounceTimers.get(event);
1001
+ if (existing) clearTimeout(existing);
1002
+ const timer = setTimeout(() => {
1003
+ this.debounceTimers.delete(event);
1004
+ this.emit(event, payload);
1005
+ }, waitMs);
1006
+ this.debounceTimers.set(event, timer);
1578
1007
  }
1579
- return "";
1008
+ cancelDebounced(event) {
1009
+ if (event) {
1010
+ const timer = this.debounceTimers.get(event);
1011
+ if (timer) {
1012
+ clearTimeout(timer);
1013
+ this.debounceTimers.delete(event);
1014
+ }
1015
+ } else {
1016
+ for (const timer of this.debounceTimers.values()) {
1017
+ clearTimeout(timer);
1018
+ }
1019
+ this.debounceTimers.clear();
1020
+ }
1021
+ }
1022
+ unsubscribeAll(event) {
1023
+ this.cancelDebounced(event);
1024
+ if (event) this.events.delete(event);
1025
+ else this.events.clear();
1026
+ }
1027
+ hasListeners(event) {
1028
+ return Boolean(this.events.get(event)?.size);
1029
+ }
1030
+ getListenerCount(event) {
1031
+ return this.events.get(event)?.size || 0;
1032
+ }
1033
+ getAllEvents() {
1034
+ return [...this.events.keys()];
1035
+ }
1036
+ };
1037
+ var GLOBAL_EVENTS_KEY = /* @__PURE__ */ Symbol.for("__base_framework_global_events__");
1038
+ var _global = globalThis;
1039
+ var globalEvents = _global[GLOBAL_EVENTS_KEY] ?? (_global[GLOBAL_EVENTS_KEY] = new EventEmitter());
1040
+
1041
+ // src/hooks/use-global-event.ts
1042
+ function useGlobalEvent(event, callback, options) {
1043
+ const callbackRef = useRef(callback);
1044
+ const debounceTimerRef = useRef(null);
1045
+ useRef(0);
1046
+ const throttleTimerRef = useRef(null);
1047
+ useIsomorphicLayoutEffect(() => {
1048
+ callbackRef.current = callback;
1049
+ });
1050
+ const debounceMs = options?.debounceMs;
1051
+ const throttleMs = options?.throttleMs;
1052
+ useEffect(() => {
1053
+ if (!event) return;
1054
+ const events = Array.isArray(event) ? event.filter(Boolean) : [event];
1055
+ if (events.length === 0) return;
1056
+ const handler = (payload) => {
1057
+ callbackRef.current?.(payload);
1058
+ };
1059
+ const unsubs = events.map((ev) => globalEvents.subscribe(ev, handler));
1060
+ return () => {
1061
+ unsubs.forEach((unsub) => unsub());
1062
+ if (debounceTimerRef.current !== null) {
1063
+ clearTimeout(debounceTimerRef.current);
1064
+ debounceTimerRef.current = null;
1065
+ }
1066
+ if (throttleTimerRef.current !== null) {
1067
+ clearTimeout(throttleTimerRef.current);
1068
+ throttleTimerRef.current = null;
1069
+ }
1070
+ };
1071
+ }, [event, debounceMs, throttleMs]);
1580
1072
  }
1581
- function toSearchableText(value) {
1582
- return collectSearchableText(value, /* @__PURE__ */ new WeakSet());
1073
+ var listeners = /* @__PURE__ */ new Set();
1074
+ var activeFullscreenStateIds = /* @__PURE__ */ new Set();
1075
+ var ACTIVE_FULLSCREEN_ROOT_SELECTOR = '[data-fullscreen-state-root="true"][data-affect-global-state="true"]';
1076
+ var domObserver = null;
1077
+ var lastSnapshot = false;
1078
+ function getDomSnapshot() {
1079
+ if (typeof document === "undefined") {
1080
+ return activeFullscreenStateIds.size > 0;
1081
+ }
1082
+ return document.querySelector(ACTIVE_FULLSCREEN_ROOT_SELECTOR) !== null;
1583
1083
  }
1584
- function normalizeUpper(value) {
1585
- return String(value || "").trim().toUpperCase();
1084
+ function emitChange() {
1085
+ listeners.forEach((listener) => listener());
1586
1086
  }
1587
- function splitStyle(style = {}) {
1588
- const { className, ...inlineStyle } = style;
1589
- return {
1590
- className,
1591
- inlineStyle
1592
- };
1087
+ function emitIfSnapshotChanged() {
1088
+ const nextSnapshot = getDomSnapshot();
1089
+ if (nextSnapshot === lastSnapshot) {
1090
+ return;
1091
+ }
1092
+ lastSnapshot = nextSnapshot;
1093
+ emitChange();
1593
1094
  }
1594
- function getLineClampStyle(maxLines, style) {
1595
- if (Number(maxLines) <= 1) return style;
1596
- return {
1597
- WebkitBoxOrient: "vertical",
1598
- WebkitLineClamp: maxLines,
1599
- display: "-webkit-box",
1600
- overflow: "hidden",
1601
- ...style
1602
- };
1095
+ function ensureDomObserver() {
1096
+ if (typeof document === "undefined" || domObserver || listeners.size === 0) {
1097
+ return;
1098
+ }
1099
+ domObserver = new MutationObserver(() => {
1100
+ emitIfSnapshotChanged();
1101
+ });
1102
+ domObserver.observe(document.documentElement, {
1103
+ attributes: true,
1104
+ childList: true,
1105
+ subtree: true,
1106
+ attributeFilter: [
1107
+ "data-fullscreen-state",
1108
+ "data-fullscreen-state-root",
1109
+ "data-affect-global-state"
1110
+ ]
1111
+ });
1603
1112
  }
1604
- function getImageIconStyle(style, icon) {
1605
- const nextStyle = {
1606
- ...style
1607
- };
1608
- delete nextStyle.background;
1609
- delete nextStyle.backgroundImage;
1610
- return {
1611
- ...nextStyle,
1612
- backgroundImage: `url(${icon})`
1613
- };
1113
+ function releaseDomObserver() {
1114
+ if (listeners.size > 0 || !domObserver) {
1115
+ return;
1116
+ }
1117
+ domObserver.disconnect();
1118
+ domObserver = null;
1614
1119
  }
1615
- function resolveDockActionClass(slots, {
1616
- className = "",
1617
- button = "",
1618
- isActive = false,
1619
- variant = "",
1620
- tone = "",
1621
- base,
1622
- cn: classNamesFn
1623
- } = {}) {
1624
- const resolve = classNamesFn || cn;
1625
- const toneClasses = {
1626
- muted: slots.actionMuted,
1627
- active: slots.actionActive
1120
+ function subscribe(listener) {
1121
+ listeners.add(listener);
1122
+ ensureDomObserver();
1123
+ emitIfSnapshotChanged();
1124
+ return () => {
1125
+ listeners.delete(listener);
1126
+ releaseDomObserver();
1628
1127
  };
1629
- if (button && !className && base === void 0) {
1630
- if (tone) {
1631
- const toneClass = toneClasses[tone] || slots.actionSurface || slots.actionMuted;
1632
- return resolve(button, toneClass);
1633
- }
1634
- const stateToken = isActive ? slots.actionActive : slots.actionMuted;
1635
- return resolve(button, stateToken);
1636
- }
1637
- const elementClass = className || button;
1638
- const resolvedBase = base !== void 0 ? base : slots.action;
1639
- const stateClass = variant || tone && (toneClasses[tone] || slots.actionSurface) || (isActive ? slots.actionActive : slots.actionMuted);
1640
- return resolve(stateClass, resolvedBase, elementClass);
1641
1128
  }
1642
- function blurActiveElement() {
1643
- if (typeof document === "undefined") return;
1644
- const activeElement = document.activeElement;
1645
- if (activeElement && typeof activeElement.blur === "function") {
1646
- activeElement.blur();
1647
- }
1129
+ function getSnapshot() {
1130
+ return getDomSnapshot();
1648
1131
  }
1649
- function closestMatches(target, selector) {
1650
- const element = target;
1651
- return Boolean(
1652
- element && typeof element.closest === "function" && element.closest(selector)
1653
- );
1132
+ function useIsFullscreenStateActive() {
1133
+ return useSyncExternalStore(subscribe, getSnapshot, () => false);
1654
1134
  }
1655
- function isInteractiveTarget(target) {
1656
- return closestMatches(
1657
- target,
1658
- 'button, a, input, textarea, select, [role="button"], [tabindex]:not([tabindex="-1"])'
1659
- );
1135
+ var RegistryContext = getOrCreateGlobalContext(
1136
+ "RegistryContext",
1137
+ null
1138
+ );
1139
+ function useRegistryActions() {
1140
+ return useRequiredContext(
1141
+ RegistryContext,
1142
+ "useRegistryActions",
1143
+ "RegistryProvider"
1144
+ ).actions;
1660
1145
  }
1661
- function isEditableDockTarget(target) {
1662
- return closestMatches(
1663
- target,
1664
- 'input, textarea, select, [contenteditable="true"]'
1665
- );
1146
+ function useRegistrySubscription() {
1147
+ return useRequiredContext(
1148
+ RegistryContext,
1149
+ "useRegistrySubscription",
1150
+ "RegistryProvider"
1151
+ ).subscription;
1666
1152
  }
1667
- function getDockFocusableElements(container) {
1668
- if (!container || typeof container.querySelectorAll !== "function") return [];
1669
- return Array.from(
1670
- container.querySelectorAll(DOCK_FOCUSABLE_SELECTOR)
1671
- ).filter((element) => {
1672
- if (!element || element.getAttribute("aria-hidden") === "true")
1673
- return false;
1674
- const style = typeof window !== "undefined" ? window.getComputedStyle(element) : null;
1675
- return style?.display !== "none" && style?.visibility !== "hidden" && style?.pointerEvents !== "none";
1676
- });
1153
+ function useRegistryEntries(type) {
1154
+ const { getEntriesSnapshot, subscribe: subscribe2 } = useRegistrySubscription();
1155
+ const subscribeToType = useCallback(
1156
+ (listener) => subscribe2(type, null, listener),
1157
+ [subscribe2, type]
1158
+ );
1159
+ const getEntries = useCallback(
1160
+ () => getEntriesSnapshot(type),
1161
+ [getEntriesSnapshot, type]
1162
+ );
1163
+ return useSyncExternalStore(subscribeToType, getEntries, getEntries);
1677
1164
  }
1678
- function focusDockElement(element) {
1679
- if (!element || typeof element.focus !== "function") return false;
1680
- try {
1681
- element.focus({ preventScroll: true });
1682
- return document.activeElement === element;
1683
- } catch {
1684
- return false;
1165
+ var EMPTY_MODULES = Object.freeze([]);
1166
+ var ModuleHostContext = getOrCreateGlobalContext("ModuleHostContext", null);
1167
+ function selectModuleSlice(coreModule, config) {
1168
+ const slice = coreModule.page?.select ? coreModule.page.select(config) : config[coreModule.id];
1169
+ return slice ?? null;
1170
+ }
1171
+ function createModulePayload(modules, config, metadata = {}) {
1172
+ const payload = {};
1173
+ let hasSlices = false;
1174
+ for (const coreModule of modules) {
1175
+ if (!coreModule.page?.entries) continue;
1176
+ const slice = selectModuleSlice(coreModule, config);
1177
+ if (slice === null) continue;
1178
+ payload[coreModule.id] = slice;
1179
+ hasSlices = true;
1685
1180
  }
1181
+ if (!hasSlices) return null;
1182
+ return {
1183
+ ...payload,
1184
+ registry: {
1185
+ ...metadata,
1186
+ ...isObject(config.registry) ? config.registry : {}
1187
+ }
1188
+ };
1686
1189
  }
1687
- function shouldRestoreDockFocus(result) {
1688
- const blockedReason = result?.blockedReason;
1689
- return !result || !DOCK_FOCUS_RESTORE_BLOCKED_REASONS.includes(
1690
- blockedReason
1691
- );
1190
+ function useInstalledModules() {
1191
+ return use(ModuleHostContext)?.modules ?? EMPTY_MODULES;
1692
1192
  }
1693
- function isSameDockItem(item, candidate) {
1694
- return Boolean(
1695
- item?.path && item.path === candidate?.path || item?.name && item.name === candidate?.name
1696
- );
1193
+
1194
+ // src/kernel/handlers.ts
1195
+ function isPersistentLifecycle(lifecycle) {
1196
+ return lifecycle === REGISTRY_LIFECYCLES.PERSISTENT;
1697
1197
  }
1698
- var isSameItem = isSameDockItem;
1699
- function getItemKey(link, index = 0) {
1700
- const identity = link?.id ?? link?.path ?? link?.name ?? link?.type;
1701
- return `dock-card:${identity == null ? `slot-${index}` : String(identity)}`;
1198
+ function runRegistrationBatch(context, executor) {
1199
+ return typeof context.batch === "function" ? context.batch(executor) : executor(context);
1702
1200
  }
1703
- function getItemMeasurementKey({
1704
- link,
1705
- expanded,
1706
- isHud = false
1707
- }) {
1708
- const state = isHud ? "hud" : link.isLoading ? "loading" : link.isSurface ? `surface-${link.surfacePhase ?? "open"}` : "standard";
1709
- return `${link.path || link.name || "item"}:${state}:${expanded ? "expanded" : "collapsed"}`;
1201
+ function createEntryCleanupKey(type, key, source, instanceId, scope) {
1202
+ return JSON.stringify([
1203
+ "entry",
1204
+ type,
1205
+ key,
1206
+ scope ?? null,
1207
+ source,
1208
+ instanceId
1209
+ ]);
1710
1210
  }
1711
- function getRouteMeasurementKey(pathname, key) {
1712
- return `${pathname || ""}:${key}`;
1211
+ function getCleanupScope(context) {
1212
+ return context?.cleanupScope || /* @__PURE__ */ new Map();
1713
1213
  }
1714
- function resolveDockHeaderKey({
1715
- link,
1716
- description = "",
1717
- showVideoIcon = false
1718
- }) {
1719
- const statusPart = link?.isStatus ? `status:${link.statusType || link.type || "status"}` : "standard";
1720
- const identityPart = link?.path || link?.name || link?.id || "item";
1721
- const titlePart = link?.title || link?.name || "";
1722
- const descPart = description || "";
1723
- const iconPart = showVideoIcon ? "video" : link?.icon || "no-icon";
1724
- return `${statusPart}:${identityPart}:${iconPart}:${titlePart}:${descPart}`;
1214
+ function clearCleanupTimer(scope, cleanupKey) {
1215
+ const lifecycle = scope.get(cleanupKey);
1216
+ if (!lifecycle) return;
1217
+ lifecycle.cancelled = true;
1218
+ if (lifecycle.timerId !== null) clearTimeout(lifecycle.timerId);
1219
+ scope.delete(cleanupKey);
1725
1220
  }
1726
- function findDockItemIndex(dockItems, activeItem, pathname) {
1727
- const normalizedPathname = normalizePath(pathname);
1728
- const selectedDataSourceIndex = dockItems.findIndex(
1729
- (item) => item.isDataSource && item.isSelected
1730
- );
1731
- if (selectedDataSourceIndex !== -1) return selectedDataSourceIndex;
1732
- if (activeItem) {
1733
- const matchedActiveIndex = dockItems.findIndex(
1734
- (item) => item.path && isSamePath(item.path, activeItem.path) || item.name && item.name === activeItem.name
1735
- );
1736
- if (matchedActiveIndex !== -1) return matchedActiveIndex;
1737
- }
1738
- return dockItems.findIndex(
1739
- (item) => isSamePath(item.path, normalizedPathname) || item.targetPath && isSamePath(item.targetPath, normalizedPathname)
1740
- );
1221
+ function scheduleCleanup(scope, cleanupKey, callback, delayMs) {
1222
+ clearCleanupTimer(scope, cleanupKey);
1223
+ const lifecycle = {
1224
+ cancelled: false,
1225
+ timerId: null
1226
+ };
1227
+ lifecycle.timerId = setTimeout(() => {
1228
+ if (lifecycle.cancelled || scope.get(cleanupKey) !== lifecycle) return;
1229
+ scope.delete(cleanupKey);
1230
+ callback();
1231
+ }, delayMs);
1232
+ scope.set(cleanupKey, lifecycle);
1741
1233
  }
1742
- function resolveActiveIndex({
1743
- dockItems,
1744
- activeItem,
1745
- pathname
1746
- }) {
1747
- return Math.max(0, findDockItemIndex(dockItems, activeItem, pathname));
1234
+ function collectModuleRegistrations(coreModule, slice, pageMetadata, context) {
1235
+ const definition = coreModule.registry;
1236
+ const toEntries = coreModule.page?.entries;
1237
+ if (!definition || !toEntries || slice == null || slice === false) return [];
1238
+ const type = coreModule.id;
1239
+ const items = Array.isArray(slice) ? slice : [slice];
1240
+ const registrations = [];
1241
+ for (const item of items) {
1242
+ if (item == null || item === false) continue;
1243
+ const itemMetadata = isObject(item) && isObject(item.registry) ? item.registry : {};
1244
+ const metadata = { ...pageMetadata, ...itemMetadata };
1245
+ if (metadata.validation === "strict" && !validateRegistryMetadata(metadata).valid) {
1246
+ continue;
1247
+ }
1248
+ const payload = isObject(item) ? Object.fromEntries(
1249
+ Object.entries(item).filter(([key]) => key !== "registry")
1250
+ ) : item;
1251
+ const { cleanupDelayMs, lifecycle, registerOptions, scope, source } = normalizeRegistryMetadata(metadata, {
1252
+ defaultCleanupDelayMs: definition.cleanupDelayMs ?? null,
1253
+ defaultLifecycle: definition.lifecycle,
1254
+ defaultSource: DEFAULT_SOURCE
1255
+ });
1256
+ for (const { key, value } of toEntries(payload, context) ?? []) {
1257
+ const validation = definition.validate?.(value);
1258
+ if (validation && !validation.valid) {
1259
+ report(`Registry ${type} config is invalid`, validation.issues, "warn");
1260
+ continue;
1261
+ }
1262
+ registrations.push({
1263
+ cleanupDelayMs,
1264
+ cleanupKey: createEntryCleanupKey(
1265
+ type,
1266
+ key,
1267
+ source,
1268
+ context.instanceId,
1269
+ scope
1270
+ ),
1271
+ key,
1272
+ lifecycle,
1273
+ registerOptions,
1274
+ scope,
1275
+ source,
1276
+ type,
1277
+ value
1278
+ });
1279
+ }
1280
+ }
1281
+ return registrations;
1748
1282
  }
1749
- function shouldKeepAncestorItem(item, activePath) {
1750
- const policy = item?.keepWhenDescendant;
1751
- if (typeof policy === "function") {
1283
+ var NO_CLEANUP = () => {
1284
+ };
1285
+ function applyRegistryConfig(config, context) {
1286
+ const modules = context.modules ?? [];
1287
+ if (!config || modules.length === 0) return NO_CLEANUP;
1288
+ const pageMetadata = isObject(config.registry) ? config.registry : {};
1289
+ const slices = config;
1290
+ const registrations = modules.flatMap((coreModule) => {
1752
1291
  try {
1753
- return Boolean(policy(activePath, item));
1754
- } catch {
1755
- return false;
1292
+ return collectModuleRegistrations(
1293
+ coreModule,
1294
+ slices[coreModule.id],
1295
+ pageMetadata,
1296
+ context
1297
+ );
1298
+ } catch (error) {
1299
+ report(`Registry apply (${coreModule.id})`, error);
1300
+ return [];
1756
1301
  }
1302
+ });
1303
+ if (registrations.length === 0) return NO_CLEANUP;
1304
+ const cleanupScope = getCleanupScope(context);
1305
+ const owner = (registration) => ({
1306
+ instanceId: context.instanceId,
1307
+ scope: registration.scope,
1308
+ source: registration.source
1309
+ });
1310
+ runRegistrationBatch(context, (queue) => {
1311
+ registrations.forEach(
1312
+ ({ cleanupKey, key, registerOptions, source, type, value }) => {
1313
+ clearCleanupTimer(cleanupScope, cleanupKey);
1314
+ queue.register(type, key, value, source, registerOptions);
1315
+ }
1316
+ );
1317
+ });
1318
+ return () => {
1319
+ const immediate = [];
1320
+ registrations.forEach((registration) => {
1321
+ if (isPersistentLifecycle(registration.lifecycle)) return;
1322
+ const { cleanupDelayMs, cleanupKey, key, type } = registration;
1323
+ if (typeof cleanupDelayMs === "number" && cleanupDelayMs > 0) {
1324
+ scheduleCleanup(
1325
+ cleanupScope,
1326
+ cleanupKey,
1327
+ () => context.unregister(type, key, owner(registration)),
1328
+ cleanupDelayMs
1329
+ );
1330
+ } else {
1331
+ immediate.push(registration);
1332
+ }
1333
+ });
1334
+ if (immediate.length === 0) return;
1335
+ runRegistrationBatch(context, (queue) => {
1336
+ immediate.forEach(
1337
+ (registration) => queue.unregister(
1338
+ registration.type,
1339
+ registration.key,
1340
+ owner(registration)
1341
+ )
1342
+ );
1343
+ });
1344
+ };
1345
+ }
1346
+
1347
+ // src/kernel/hooks.ts
1348
+ function withInstanceId(instanceId, sourceOrOptions, optionsArg) {
1349
+ if (typeof sourceOrOptions === "string") {
1350
+ return {
1351
+ optionsArg: { ...isObject(optionsArg) ? optionsArg : {}, instanceId },
1352
+ sourceOrOptions
1353
+ };
1354
+ }
1355
+ if (isObject(sourceOrOptions)) {
1356
+ return {
1357
+ optionsArg,
1358
+ sourceOrOptions: { ...sourceOrOptions, instanceId }
1359
+ };
1757
1360
  }
1758
- return policy === true;
1361
+ return {
1362
+ optionsArg: void 0,
1363
+ sourceOrOptions: {
1364
+ ...isObject(optionsArg) ? optionsArg : {},
1365
+ instanceId
1366
+ }
1367
+ };
1759
1368
  }
1760
- function isAncestorPath(item, activePath) {
1761
- const candidatePath = item?.path;
1762
- if (!candidatePath || candidatePath === "/" || candidatePath === activePath)
1763
- return false;
1764
- return isPathPrefix(candidatePath, activePath) && !shouldKeepAncestorItem(item, activePath);
1369
+ function withInstanceIdForUnregister(instanceId, sourceOrOptions) {
1370
+ if (typeof sourceOrOptions === "string") {
1371
+ return { instanceId, source: sourceOrOptions };
1372
+ }
1373
+ if (isObject(sourceOrOptions)) {
1374
+ return { ...sourceOrOptions, instanceId };
1375
+ }
1376
+ return { instanceId };
1765
1377
  }
1766
- function removeAncestorDuplicates(items = []) {
1767
- if (!Array.isArray(items) || items.length <= 1) return items;
1768
- const activePath = items[0]?.path;
1769
- if (!activePath) return items;
1770
- return items.filter(
1771
- (item, index) => index === 0 || !isAncestorPath(item, activePath)
1772
- );
1378
+ function resolveRegisterArgsWithInstance(instanceId, sourceOrOptions, optionsArg) {
1379
+ const input = withInstanceId(instanceId, sourceOrOptions, optionsArg);
1380
+ return [input.sourceOrOptions, input.optionsArg];
1773
1381
  }
1774
- function replaceActiveItem(items, activeIndex, activeItem) {
1775
- if (activeIndex === -1 || !activeItem) return items;
1776
- const nextItems = [...items];
1777
- nextItems[activeIndex] = activeItem;
1778
- return nextItems;
1382
+ var MAX_STABILIZATION_DEPTH = 64;
1383
+ function createStableFunctionEntry(fn) {
1384
+ const entry = {
1385
+ current: fn,
1386
+ stable(...args) {
1387
+ return entry.current?.apply(this, args);
1388
+ }
1389
+ };
1390
+ return entry;
1779
1391
  }
1780
- function removeInactiveLoadingItems(items = [], activeItem = null) {
1781
- if (!Array.isArray(items) || items.length === 0) return items;
1782
- return items.filter(
1783
- (item) => !item?.isLoading || isSameItem(item, activeItem)
1784
- );
1392
+ function isDirectRegistryComponentPath(path, componentSlices) {
1393
+ const match = /^config\.([^.[]+)\.[^.[]+$/.exec(path);
1394
+ return Boolean(match && componentSlices.has(match[1]));
1785
1395
  }
1786
- function reorderItemsWithActiveFirst(items, activeIndex) {
1787
- if (activeIndex === -1) return items;
1788
- const active = items[activeIndex];
1789
- const rest = [
1790
- ...items.slice(0, activeIndex),
1791
- ...items.slice(activeIndex + 1)
1792
- ];
1793
- rest.sort((a, b) => a.path === "/" ? 1 : b.path === "/" ? -1 : 0);
1794
- return [active, ...rest];
1396
+ function isReactNodeLike(value) {
1397
+ return value === null || value === void 0 || typeof value === "boolean" || typeof value === "string" || typeof value === "number" || isValidElement(value);
1795
1398
  }
1796
- function isHudDescriptor(value) {
1797
- return isObject(value) && !isValidElement(value) && ("component" in value || "content" in value || "node" in value || "element" in value || "isActive" in value || "id" in value);
1399
+ function isStabilizableObject(value) {
1400
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !isValidElement(value);
1798
1401
  }
1799
- function isSurfaceDescriptor(value) {
1800
- return value != null && typeof value === "object" && !Array.isArray(value) && !isValidElement(value);
1402
+ function hasSameObjectKeys(previousValue, nextValue) {
1403
+ if (!isStabilizableObject(previousValue) || !isStabilizableObject(nextValue)) {
1404
+ return false;
1405
+ }
1406
+ const previousKeys = Object.keys(previousValue);
1407
+ const nextKeys = Object.keys(nextValue);
1408
+ if (previousKeys.length !== nextKeys.length) return false;
1409
+ return nextKeys.every(
1410
+ (key) => Object.prototype.hasOwnProperty.call(previousValue, key)
1411
+ );
1801
1412
  }
1802
- var generatedExtensionId = 0;
1803
- function normalizeSurfaceExtension(input) {
1804
- if (!input) return null;
1805
- if (isValidElement(input)) {
1806
- return {
1807
- align: "left",
1808
- className: "",
1809
- component: null,
1810
- content: input,
1811
- id: `ext-${++generatedExtensionId}`,
1812
- order: 0,
1813
- props: {},
1814
- unstyled: false
1815
- };
1413
+ function stabilizeRegistryValue(value, path, scope, previousValue, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
1414
+ if (depth > MAX_STABILIZATION_DEPTH) return value;
1415
+ if (typeof value === "function") {
1416
+ const isComponent = value.name && /^[A-Z]/.test(value.name);
1417
+ if (isComponent || isDirectRegistryComponentPath(path, scope.componentSlices)) {
1418
+ return value;
1419
+ }
1420
+ scope.usedPaths.add(path);
1421
+ let entry = scope.functionEntries.get(path);
1422
+ if (!entry) {
1423
+ entry = createStableFunctionEntry(value);
1424
+ scope.functionEntries.set(path, entry);
1425
+ } else {
1426
+ entry.current = value;
1427
+ }
1428
+ return entry.stable;
1816
1429
  }
1817
- if (typeof input !== "object") return null;
1818
- const extension = input;
1819
- const component = isValidComponentType(extension.component) ? extension.component : null;
1820
- const content = isValidElement(extension.content) || typeof extension.content === "string" || typeof extension.content === "number" ? extension.content : null;
1821
- if (!component && content == null) return null;
1822
- const align = extension.align === "right" || extension.align === "end" ? "right" : extension.align === "center" ? "center" : "left";
1823
- return {
1824
- align,
1825
- className: typeof extension.className === "string" ? extension.className : "",
1826
- component,
1827
- content,
1828
- id: String(
1829
- extension.id || extension.key || `ext-${++generatedExtensionId}`
1830
- ),
1831
- order: Number.isFinite(Number(extension.order)) ? Number(extension.order) : 0,
1832
- props: isPlainObject(extension.props) ? extension.props : {},
1833
- unstyled: Boolean(extension.unstyled)
1834
- };
1430
+ if (value && typeof value === "object") {
1431
+ if (seen.has(value)) return value;
1432
+ seen.add(value);
1433
+ }
1434
+ if (isValidElement(value)) {
1435
+ const nextProps = stabilizeRegistryValue(
1436
+ value.props,
1437
+ `${path}.props`,
1438
+ scope,
1439
+ isValidElement(previousValue) ? previousValue.props : void 0,
1440
+ seen,
1441
+ depth + 1
1442
+ );
1443
+ if (isValidElement(previousValue) && previousValue.type === value.type && previousValue.key === value.key && previousValue.props === nextProps) {
1444
+ return previousValue;
1445
+ }
1446
+ return cloneElement(value, nextProps);
1447
+ }
1448
+ if (Array.isArray(value)) {
1449
+ const nextValue = value.every(isReactNodeLike) ? Children.toArray(value) : value;
1450
+ const previousArray = Array.isArray(previousValue) ? previousValue : null;
1451
+ let hasChanged2 = !previousArray || previousArray.length !== nextValue.length;
1452
+ const stabilizedValue2 = nextValue.map((item, index) => {
1453
+ const nextItem = stabilizeRegistryValue(
1454
+ item,
1455
+ `${path}[${index}]`,
1456
+ scope,
1457
+ previousArray?.[index],
1458
+ seen,
1459
+ depth + 1
1460
+ );
1461
+ if (!previousArray || nextItem !== previousArray[index]) {
1462
+ hasChanged2 = true;
1463
+ }
1464
+ return nextItem;
1465
+ });
1466
+ return !hasChanged2 ? previousArray : stabilizedValue2;
1467
+ }
1468
+ if (typeof value !== "object" || value === null) {
1469
+ return value;
1470
+ }
1471
+ const source = value;
1472
+ const previousRecord = previousValue;
1473
+ const stabilizedValue = {};
1474
+ const canReusePrevious = hasSameObjectKeys(previousValue, value);
1475
+ let hasChanged = !canReusePrevious;
1476
+ Object.keys(source).forEach((key) => {
1477
+ const nextValue = stabilizeRegistryValue(
1478
+ source[key],
1479
+ `${path}.${key}`,
1480
+ scope,
1481
+ previousRecord?.[key],
1482
+ seen,
1483
+ depth + 1
1484
+ );
1485
+ stabilizedValue[key] = nextValue;
1486
+ if (!canReusePrevious || nextValue !== previousRecord?.[key]) {
1487
+ hasChanged = true;
1488
+ }
1489
+ });
1490
+ return !hasChanged ? previousValue : stabilizedValue;
1835
1491
  }
1836
- function normalizeSurfaceFlowSnapshot(value) {
1837
- if (!isPlainObject(value)) return null;
1838
- return { ...value };
1492
+ function useAppliedRegistryConfig(payload, context, componentSlices) {
1493
+ const functionEntriesRef = useRef(/* @__PURE__ */ new Map());
1494
+ const appliedRef = useRef(null);
1495
+ useIsomorphicLayoutEffect(() => {
1496
+ const previous = appliedRef.current;
1497
+ if (previous && previous.payload === payload && previous.context === context) {
1498
+ return;
1499
+ }
1500
+ const functionEntries = functionEntriesRef.current;
1501
+ const usedPaths = /* @__PURE__ */ new Set();
1502
+ const config = stabilizeRegistryValue(
1503
+ payload,
1504
+ "config",
1505
+ { componentSlices, functionEntries, usedPaths },
1506
+ previous?.config
1507
+ );
1508
+ functionEntries.forEach((_entry, path) => {
1509
+ if (!usedPaths.has(path)) functionEntries.delete(path);
1510
+ });
1511
+ if (previous && previous.config === config && previous.context === context) {
1512
+ previous.payload = payload;
1513
+ return;
1514
+ }
1515
+ previous?.cleanup();
1516
+ appliedRef.current = {
1517
+ cleanup: applyRegistryConfig(config, context),
1518
+ config,
1519
+ context,
1520
+ payload
1521
+ };
1522
+ });
1523
+ useIsomorphicLayoutEffect(
1524
+ () => () => {
1525
+ appliedRef.current?.cleanup();
1526
+ appliedRef.current = null;
1527
+ },
1528
+ []
1529
+ );
1839
1530
  }
1840
- function createSurfaceReturnHandshake(input) {
1841
- const source = typeof input === "string" ? { pathname: input } : input;
1842
- const pathname = typeof source?.pathname === "string" ? source.pathname.trim() : "";
1843
- if (!isSafeInternalHref(pathname)) return null;
1531
+ function createRegistryApplyContext(actions, {
1532
+ cleanupScope = /* @__PURE__ */ new Map(),
1533
+ instanceId,
1534
+ modules = [],
1535
+ pathname
1536
+ }) {
1537
+ const stamp = (queue) => ({
1538
+ register: (type, key, item, sourceOrOptions, optionsArg) => queue.register(
1539
+ type,
1540
+ key,
1541
+ item,
1542
+ ...resolveRegisterArgsWithInstance(
1543
+ instanceId,
1544
+ sourceOrOptions,
1545
+ optionsArg
1546
+ )
1547
+ ),
1548
+ unregister: (type, key, sourceOrOptions) => queue.unregister(
1549
+ type,
1550
+ key,
1551
+ withInstanceIdForUnregister(instanceId, sourceOrOptions)
1552
+ )
1553
+ });
1844
1554
  return {
1845
- focusKey: typeof source?.focusKey === "string" && source.focusKey.trim() ? source.focusKey.trim() : null,
1846
- pathname,
1847
- restoreScroll: source?.restoreScroll !== false,
1848
- returnOnCancel: source?.returnOnCancel === true
1555
+ ...stamp(actions),
1556
+ batch: (executor) => typeof executor === "function" ? actions.batch((queue) => executor(stamp(queue))) : 0,
1557
+ cleanupScope,
1558
+ instanceId,
1559
+ modules,
1560
+ pathname
1849
1561
  };
1850
1562
  }
1851
- function resolveSurfaceFlowReturnHandshake(definition, input) {
1852
- const flowInput = input && typeof input === "object" ? input : null;
1853
- const inputHandshake = flowInput?.returnHandshake ?? (flowInput?.returnTo ? {
1854
- focusKey: flowInput.returnFocusKey,
1855
- pathname: flowInput.returnTo,
1856
- restoreScroll: flowInput.restoreReturnScroll,
1857
- returnOnCancel: flowInput.returnOnCancel
1858
- } : null);
1859
- const baseHandshake = definition?.returnHandshake;
1860
- if (!baseHandshake && !inputHandshake) return null;
1861
- return createSurfaceReturnHandshake({ ...baseHandshake, ...inputHandshake });
1563
+ function useRegistry(config, metadata) {
1564
+ const { batch, register, unregister } = useRegistryActions();
1565
+ const pathname = usePathname();
1566
+ const modules = useInstalledModules();
1567
+ const instanceId = `registry-instance-${useId()}`;
1568
+ const [cleanupScope] = useState(
1569
+ () => /* @__PURE__ */ new Map()
1570
+ );
1571
+ const context = useMemo(
1572
+ () => createRegistryApplyContext(
1573
+ { batch, register, unregister },
1574
+ {
1575
+ cleanupScope,
1576
+ instanceId,
1577
+ modules,
1578
+ pathname
1579
+ }
1580
+ ),
1581
+ [batch, cleanupScope, instanceId, modules, pathname, register, unregister]
1582
+ );
1583
+ const payload = useMemo(
1584
+ () => config ? createModulePayload(modules, config, metadata) : null,
1585
+ [config, metadata, modules]
1586
+ );
1587
+ const componentSlices = useMemo(
1588
+ () => new Set(
1589
+ modules.filter(
1590
+ (coreModule) => coreModule.registry?.valueKind === "component"
1591
+ ).map((coreModule) => coreModule.id)
1592
+ ),
1593
+ [modules]
1594
+ );
1595
+ useAppliedRegistryConfig(payload, context, componentSlices);
1862
1596
  }
1863
- function createDockScheduler(options = {}) {
1864
- return createScheduler({
1865
- defaultLabel: "dock-task",
1866
- ...options
1867
- });
1597
+ function defineModule(coreModule) {
1598
+ return Object.freeze({ ...coreModule });
1868
1599
  }
1600
+ createStore(void 0);
1869
1601
 
1870
- // src/modules/dock/surface/context.tsx
1871
- var SurfaceExtensionsContext = createContext(null);
1872
- var SurfaceItemContext = createContext({
1873
- id: null,
1874
- width: null
1875
- });
1876
- function useSurfaceDimensions() {
1877
- const { width } = use(SurfaceItemContext);
1878
- return useMemo(() => ({ width }), [width]);
1879
- }
1880
- function useSurfaceId() {
1881
- return use(SurfaceItemContext).id;
1882
- }
1883
- function useSurfaceHeader() {
1884
- const store = use(SurfaceExtensionsContext)?.headerActions ?? null;
1885
- const surfaceId = useSurfaceId();
1886
- return useCallback(
1887
- (patch) => {
1888
- if (!store) return;
1889
- const data = typeof patch === "function" ? patch({}) : patch;
1890
- if (data?.headerAction !== void 0)
1891
- store.setAction(surfaceId, data.headerAction);
1892
- },
1893
- [store, surfaceId]
1602
+ // src/kernel/adapters.ts
1603
+ function useModuleRegistration(id, value, options) {
1604
+ const coreModule = useInstalledModules().find(
1605
+ (candidate) => candidate.id === id
1894
1606
  );
1607
+ const config = coreModule && options?.enabled !== false && value != null ? { [id]: value } : null;
1608
+ useRegistry(config, pickRegistryMetadata(options));
1895
1609
  }
1896
- function useSurfaceAction(action) {
1897
- const store = use(SurfaceExtensionsContext)?.headerActions ?? null;
1898
- const surfaceId = useSurfaceId();
1899
- useEffect(() => {
1900
- if (!store || action === void 0) return;
1901
- store.setAction(surfaceId, action);
1902
- return () => store.removeAction(surfaceId);
1903
- }, [store, surfaceId, action]);
1610
+
1611
+ // src/theme.tsx
1612
+ function defineThemeSpec(id) {
1613
+ return Object.freeze({ id });
1904
1614
  }
1905
- var SurfaceHeaderActionStore = class {
1906
- store = createStore({
1907
- actionsBySurface: {},
1908
- version: 0
1909
- });
1910
- get version() {
1911
- return this.store.getSnapshot().version;
1912
- }
1913
- subscribe = (listener) => this.store.subscribe(listener);
1914
- getAction = (surfaceId) => {
1915
- const sId = surfaceId != null ? String(surfaceId) : "global";
1916
- const { actionsBySurface } = this.store.getSnapshot();
1917
- return actionsBySurface[sId] ?? actionsBySurface.global ?? null;
1918
- };
1919
- setAction = (surfaceId, action) => {
1920
- const sId = surfaceId != null ? String(surfaceId) : "global";
1921
- const current = this.store.getSnapshot();
1922
- if (current.actionsBySurface[sId] === action) return;
1923
- this.store.setState({
1924
- actionsBySurface: {
1925
- ...current.actionsBySurface,
1926
- [sId]: action
1927
- },
1928
- version: current.version + 1
1929
- });
1930
- };
1931
- removeAction = (surfaceId) => {
1932
- const sId = surfaceId != null ? String(surfaceId) : "global";
1933
- const current = this.store.getSnapshot();
1934
- if (!(sId in current.actionsBySurface)) return;
1935
- const { [sId]: _, ...nextActions } = current.actionsBySurface;
1936
- this.store.setState({
1937
- actionsBySurface: nextActions,
1938
- version: current.version + 1
1939
- });
1940
- };
1941
- clearSurface = (surfaceId) => this.removeAction(surfaceId);
1942
- };
1943
- var SurfaceExtensionsStore = class {
1944
- extensionsBySurface = /* @__PURE__ */ new Map();
1945
- cachedListBySurface = /* @__PURE__ */ new Map();
1946
- store = createStore({ version: 0 });
1947
- get version() {
1948
- return this.store.getSnapshot().version;
1949
- }
1950
- subscribe = (listener) => this.store.subscribe(listener);
1951
- notify = () => {
1952
- this.cachedListBySurface.clear();
1953
- this.store.setState((prev) => ({ version: prev.version + 1 }));
1954
- };
1955
- getExtensionsForSurface = (surfaceId) => {
1956
- const sId = surfaceId != null ? String(surfaceId) : "global";
1957
- if (this.cachedListBySurface.has(sId))
1958
- return this.cachedListBySurface.get(sId);
1959
- const globalExts = this.extensionsBySurface.get("global");
1960
- const surfaceExts = this.extensionsBySurface.get(sId);
1961
- if (!globalExts && !surfaceExts) {
1962
- const empty = [];
1963
- this.cachedListBySurface.set(sId, empty);
1964
- return empty;
1965
- }
1966
- const merged = new Map([...globalExts || [], ...surfaceExts || []]);
1967
- const result = Array.from(merged.values()).sort(
1968
- (a, b) => a.order - b.order
1615
+ var EMPTY = /* @__PURE__ */ new Map();
1616
+ var ThemeContext = getOrCreateGlobalContext("ThemeContext", EMPTY);
1617
+ function useTheme(spec) {
1618
+ const config = useContext(ThemeContext).get(spec.id);
1619
+ if (!config) {
1620
+ throw new Error(
1621
+ `Theme "${spec.id}" is missing. Ensure its theme configuration is provided to <ThemeProvider themes={[...]}> (e.g. in src/config/ or your theme setup).`
1969
1622
  );
1970
- this.cachedListBySurface.set(sId, result);
1971
- return result;
1972
- };
1973
- setExtension = (surfaceId, extension) => {
1974
- if (!extension) return;
1975
- const normalized = normalizeSurfaceExtension(extension);
1976
- if (!normalized) return;
1977
- const sId = surfaceId != null ? String(surfaceId) : "global";
1978
- let surfaceMap = this.extensionsBySurface.get(sId);
1979
- if (!surfaceMap) {
1980
- surfaceMap = /* @__PURE__ */ new Map();
1981
- this.extensionsBySurface.set(sId, surfaceMap);
1982
- }
1983
- const prev = surfaceMap.get(normalized.id);
1984
- if (prev && prev.content === normalized.content && prev.align === normalized.align && prev.order === normalized.order && prev.className === normalized.className && prev.unstyled === normalized.unstyled && prev.component === normalized.component && prev.props === normalized.props)
1985
- return;
1986
- surfaceMap.set(normalized.id, normalized);
1987
- this.notify();
1988
- };
1989
- removeExtension = (surfaceId, extensionId) => {
1990
- if (!extensionId) return;
1991
- const sId = surfaceId != null ? String(surfaceId) : "global";
1992
- const surfaceMap = this.extensionsBySurface.get(sId);
1993
- if (!surfaceMap || !surfaceMap.has(extensionId)) return;
1994
- surfaceMap.delete(extensionId);
1995
- if (surfaceMap.size === 0) this.extensionsBySurface.delete(sId);
1996
- this.notify();
1997
- };
1998
- clearSurface = (surfaceId) => {
1999
- const sId = surfaceId != null ? String(surfaceId) : "global";
2000
- if (!this.extensionsBySurface.has(sId)) return;
2001
- this.extensionsBySurface.delete(sId);
2002
- this.notify();
1623
+ }
1624
+ return {
1625
+ slots: config.slots,
1626
+ styles: config.styles ?? {}
2003
1627
  };
2004
- };
1628
+ }
1629
+
1630
+ // src/modules/dock/constants.ts
1631
+ var DOCK_STYLE_SECTIONS = Object.freeze([
1632
+ "card",
1633
+ "icon",
1634
+ "title",
1635
+ "description"
1636
+ ]);
1637
+ var DOCK_EVENTS = Object.freeze({
1638
+ NAVIGATE_START: "DOCK_NAVIGATE_START",
1639
+ NAVIGATE_END: "DOCK_NAVIGATE_END",
1640
+ UPDATE_BADGE: "DOCK_UPDATE_BADGE",
1641
+ NAVIGATE: "DOCK_NAVIGATE",
1642
+ GUARD: "DOCK_GUARD",
1643
+ NOT_FOUND: "DOCK_NOT_FOUND",
1644
+ STATUS_SET: "DOCK_STATUS_SET",
1645
+ STATUS_CLEAR: "DOCK_STATUS_CLEAR"
1646
+ });
1647
+ var DOCK_HUD_RENDER_MODE = Object.freeze({
1648
+ COMPONENT: "component",
1649
+ NODE: "node"
1650
+ });
1651
+ var DOCK_HUD_VARIANT = Object.freeze({
1652
+ EXPANDED: "expanded",
1653
+ PROGRESS: "progress",
1654
+ CUSTOM: "custom"
1655
+ });
1656
+ var DOCK_HUD_PRIORITY = Object.freeze({
1657
+ TASK_PROGRESS: 30,
1658
+ CONTEXTUAL: 10,
1659
+ SELECTION: 20,
1660
+ CRITICAL: 50,
1661
+ DEFAULT: 0,
1662
+ MEDIA: 15
1663
+ });
1664
+ var DOCK_ATTENTION_KIND = Object.freeze({
1665
+ SURFACE: "surface",
1666
+ OPERATION: "operation",
1667
+ LOADING: "loading",
1668
+ STATUS: "status",
1669
+ ROUTE: "route",
1670
+ HUD: "hud"
1671
+ });
1672
+ var DOCK_ATTENTION_PRIORITY = Object.freeze({
1673
+ STATUS_OVERLAY: 300,
1674
+ SURFACE: 400,
1675
+ OPERATION: 250,
1676
+ LOADING: 100,
1677
+ STATUS: 75,
1678
+ ROUTE: 0,
1679
+ HUD: 200
1680
+ });
1681
+ var DOCK_ATTENTION_PRIORITY_OFFSET_MAX = 99;
1682
+ var DOCK_SURFACE_RENDER_MODE = Object.freeze({
1683
+ COMPONENT: "component",
1684
+ NODE: "node"
1685
+ });
1686
+ var DOCK_SURFACE_FLOW_STATUS = Object.freeze({
1687
+ OPEN: "open",
1688
+ COMPLETED: "completed",
1689
+ CANCELLED: "cancelled"
1690
+ });
1691
+ var DOCK_SURFACE_PHASE = Object.freeze({
1692
+ IDLE: "idle",
1693
+ DISMISSING_ACTION: "dismissing_action",
1694
+ EXPANDING_BODY: "expanding_body",
1695
+ OPEN: "open",
1696
+ CLOSING_ANTICIPATION: "closing_anticipation",
1697
+ COLLAPSING_BODY: "collapsing_body",
1698
+ RESTORING_HEADER: "restoring_header"
1699
+ });
1700
+ var VIEWPORT_MARGIN = 24;
1701
+ var DOCK_CARD_DIMENSIONS = Object.freeze({
1702
+ expandedY: -72,
1703
+ chromeHeight: 20,
1704
+ collapsedY: -10,
1705
+ extensionShelfY: -42,
1706
+ extensionShelfScale: 0.94,
1707
+ extensionShelfHeight: 45,
1708
+ hudHeight: 52,
1709
+ actionGap: 10,
1710
+ height: 68
1711
+ });
1712
+ var DOCK_CARD_LAYOUT = Object.freeze({
1713
+ collapsed: Object.freeze({
1714
+ offsetY: DOCK_CARD_DIMENSIONS.collapsedY,
1715
+ scale: 0.88
1716
+ }),
1717
+ expanded: Object.freeze({
1718
+ offsetY: DOCK_CARD_DIMENSIONS.expandedY,
1719
+ scale: 1
1720
+ }),
1721
+ extensionShelfY: DOCK_CARD_DIMENSIONS.extensionShelfY,
1722
+ extensionShelfScale: DOCK_CARD_DIMENSIONS.extensionShelfScale,
1723
+ extensionShelfHeight: DOCK_CARD_DIMENSIONS.extensionShelfHeight,
1724
+ chromeHeight: DOCK_CARD_DIMENSIONS.chromeHeight,
1725
+ hudHeight: DOCK_CARD_DIMENSIONS.hudHeight,
1726
+ actionGap: DOCK_CARD_DIMENSIONS.actionGap,
1727
+ baseHeight: DOCK_CARD_DIMENSIONS.height
1728
+ });
1729
+ var DOCK_TRANSACTION_EVENTS = Object.freeze({
1730
+ START: "START",
1731
+ COMPLETE: "COMPLETE",
1732
+ CANCEL: "CANCEL",
1733
+ FAIL: "FAIL",
1734
+ TIME_OUT: "TIME_OUT"
1735
+ });
1736
+ var DOCK_TRANSACTION_STATUS = Object.freeze({
1737
+ PENDING: "pending",
1738
+ COMPLETED: "completed",
1739
+ CANCELLED: "cancelled",
1740
+ FAILED: "failed",
1741
+ TIMED_OUT: "timed-out"
1742
+ });
1743
+ var DOCK_TRANSACTION_REASON = Object.freeze({
1744
+ GUARD: "guard",
1745
+ SUPERSEDED: "superseded",
1746
+ TIME_OUT: "time-out"
1747
+ });
1748
+ var DOCK_TRANSACTION_TIMEOUT_MS = 15e3;
1749
+ var DOCK_PREFETCH_INTENT_DELAY_MS = 90;
1750
+ var DOCK_CONTINUITY_EVENTS = Object.freeze({
1751
+ CLEAR: "CLEAR",
1752
+ CONSUME_RETURN: "CONSUME_RETURN",
1753
+ DELIVER_RETURN: "DELIVER_RETURN",
1754
+ RECORD: "RECORD",
1755
+ REMOVE: "REMOVE"
1756
+ });
1757
+ var DOCK_CONTINUITY_MAX_ENTRIES = 32;
1758
+ var DOCK_SURFACE_RETURN_MAX_ENTRIES = 16;
1759
+ var DOCK_OPERATION_EVENTS = Object.freeze({
1760
+ CANCEL: "CANCEL",
1761
+ CLEAR: "CLEAR",
1762
+ COMPLETE: "COMPLETE",
1763
+ START: "START",
1764
+ UPDATE: "UPDATE"
1765
+ });
1766
+ var DOCK_OPERATION_STATUS = Object.freeze({
1767
+ CANCELLED: "cancelled",
1768
+ COMPLETED: "completed",
1769
+ PENDING: "pending"
1770
+ });
1771
+ var DOCK_OPERATION_MAX_ENTRIES = 24;
1772
+ var DOCK_FOCUSABLE_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
1773
+ var DOCK_FOCUS_RESTORE_BLOCKED_REASONS = Object.freeze([
1774
+ "browser-back",
1775
+ "dock",
1776
+ "unmount"
1777
+ ]);
1778
+ var DOCK_LIFECYCLE = Object.freeze({
1779
+ CLOSING: "closing",
1780
+ OPENING: "opening",
1781
+ IDLE: "idle",
1782
+ OPEN: "open"
1783
+ });
1784
+ var PLAYBACK_RATES = Object.freeze([1, 1.25, 1.5, 2]);
1785
+ var STATUS_PRIORITY = Object.freeze({
1786
+ GUARD: 120,
1787
+ ACCOUNT_DELETE: 115,
1788
+ APP_ERROR: 100,
1789
+ API_ERROR: 95,
1790
+ NOT_FOUND: 97,
1791
+ OFFLINE: 90,
1792
+ LOGOUT: 110,
1793
+ SIGNUP: 110,
1794
+ LOGIN: 110,
1795
+ ONLINE: 10
1796
+ });
1797
+ var ERROR_STATUS_TYPES = /* @__PURE__ */ new Set([
1798
+ "GUARD",
1799
+ "ACCOUNT_DELETE",
1800
+ "APP_ERROR",
1801
+ "API_ERROR",
1802
+ "NOT_FOUND"
1803
+ ]);
1804
+ var OVERLAY_STATUS_STORAGE_KEY = "dock_overlay_status";
1805
+ var OVERLAY_STATUS_CLEAR_DURATION = 3e3;
1806
+ var STATUS_CLEAR_DURATION = 4500;
1807
+ var API_ERROR_BATCH_DELAY = 300;
1808
+ var DOCK_HEIGHT_BUFFER = 16;
1809
+ var HEIGHT_EPSILON = 0.5;
1810
+ var MAX_VISIBLE_STACKED_CARDS = 3;
1811
+ var dockTheme = defineThemeSpec("dock");
2005
1812
 
2006
1813
  // src/modules/media/constants.ts
2007
1814
  var DEFAULT_MEDIA_STATE = Object.freeze({
@@ -2070,6 +1877,8 @@ var LoadingContext = getOrCreateGlobalContext("LoadingContext", null);
2070
1877
  var NOOP_LOADING_STORE = createStore(
2071
1878
  DEFAULT_LOADING_STATE_WITH_PAGE
2072
1879
  );
1880
+
1881
+ // src/modules/loading/hooks.ts
2073
1882
  function useOptionalLoadingState(selector, isEqual) {
2074
1883
  const ctx = useContext(LoadingContext);
2075
1884
  return useStore(
@@ -4073,126 +3882,55 @@ function runSurfaceTransition({
4073
3882
  }
4074
3883
  });
4075
3884
  }
4076
- function useDockTheme() {
4077
- return useTheme(dockTheme);
3885
+ function blurActiveElement() {
3886
+ if (typeof document === "undefined") return;
3887
+ const activeElement = document.activeElement;
3888
+ if (activeElement && typeof activeElement.blur === "function") {
3889
+ activeElement.blur();
3890
+ }
4078
3891
  }
4079
- function useDockActionClass() {
4080
- const { slots } = useDockTheme();
4081
- return useCallback(
4082
- (options) => resolveDockActionClass(slots, options),
4083
- [slots]
3892
+ function closestMatches(target, selector) {
3893
+ const element = target;
3894
+ return Boolean(
3895
+ element && typeof element.closest === "function" && element.closest(selector)
4084
3896
  );
4085
3897
  }
4086
- function useDockRegistration(config, options) {
4087
- useModuleRegistration("dock", config, options);
4088
- }
4089
- function useDockBanner(banner, options) {
4090
- const position = options?.position;
4091
- const size = options?.size;
4092
- const opacity = options?.opacity;
4093
- const repeat = options?.repeat;
4094
- const targetPath = options?.path;
4095
- const config = useMemo(() => {
4096
- if (!banner) return null;
4097
- if (typeof banner === "string") {
4098
- return {
4099
- bannerUrl: banner,
4100
- ...position ? { bannerPosition: position } : {},
4101
- ...size ? { bannerSize: size } : {},
4102
- ...opacity !== void 0 ? { bannerOpacity: opacity } : {},
4103
- ...repeat ? { bannerRepeat: repeat } : {},
4104
- ...targetPath ? { path: targetPath } : {}
4105
- };
4106
- }
4107
- if (typeof banner === "object") {
4108
- return {
4109
- bannerUrl: banner.url || banner.bannerUrl,
4110
- bannerPosition: banner.position || banner.bannerPosition || position,
4111
- bannerSize: banner.size || banner.bannerSize || size,
4112
- bannerOpacity: banner.opacity ?? banner.bannerOpacity ?? opacity,
4113
- bannerRepeat: banner.repeat || banner.bannerRepeat || repeat,
4114
- ...targetPath ? { path: targetPath } : {}
4115
- };
4116
- }
4117
- return null;
4118
- }, [banner, position, size, opacity, repeat, targetPath]);
4119
- useDockRegistration(config, options);
4120
- }
4121
- var EMPTY_DOCK_STATE_STORE = createStore(EMPTY_DOCK_STATE);
4122
- function selectDockConfigState(state) {
4123
- return {
4124
- activeItem: state.activeItem,
4125
- dockHeight: state.dockHeight,
4126
- expanded: state.expanded,
4127
- pathname: state.pathname
4128
- };
4129
- }
4130
- function useDockConfig(configOrTitle, options) {
4131
- const normalizedConfig = useMemo(() => {
4132
- if (!configOrTitle) return null;
4133
- if (typeof configOrTitle === "string") return { title: configOrTitle };
4134
- return configOrTitle;
4135
- }, [configOrTitle]);
4136
- useDockRegistration(normalizedConfig, options);
4137
- const dockContext = use(DockContext);
4138
- const actions = dockContext?.actions;
4139
- const state = useStore(
4140
- dockContext?.store ?? EMPTY_DOCK_STATE_STORE,
4141
- selectDockConfigState,
4142
- shallowEqual
4143
- );
4144
- return useMemo(
4145
- () => ({
4146
- activeItem: state.activeItem,
4147
- clearHud: actions?.clearHud ?? (() => {
4148
- }),
4149
- closeAllSurfaces: actions?.closeAllSurfaces ?? (() => {
4150
- }),
4151
- closeSurface: actions?.closeSurface ?? (() => {
4152
- }),
4153
- expanded: state.expanded,
4154
- dockHeight: state.dockHeight,
4155
- navigate: actions?.navigate ?? (async () => false),
4156
- openSurface: actions?.openSurface ?? (() => void 0),
4157
- pathname: state.pathname,
4158
- setExpanded: actions?.setExpanded ?? (() => {
4159
- }),
4160
- setHud: actions?.setHud ?? (() => {
4161
- }),
4162
- setSearchQuery: actions?.setSearchQuery ?? (() => {
4163
- })
4164
- }),
4165
- [actions, state]
3898
+ function isInteractiveTarget(target) {
3899
+ return closestMatches(
3900
+ target,
3901
+ 'button, a, input, textarea, select, [role="button"], [tabindex]:not([tabindex="-1"])'
4166
3902
  );
4167
3903
  }
4168
- function useDockDimensions() {
4169
- const { dockHeight } = useDockHeight();
4170
- const activeItem = useDockSelector((state) => state.activeItem);
4171
- return {
4172
- height: dockHeight,
4173
- width: activeItem?.width ?? null,
4174
- isSurface: Boolean(activeItem?.isSurface)
4175
- };
4176
- }
4177
- function useSurfaceReturn() {
4178
- const pathname = usePathname();
4179
- const { continuity } = useDockActions();
4180
- const dockReturnHandoffs = useDockSelector(
4181
- (state) => state.dockReturnHandoffs
4182
- );
4183
- const entries = useMemo(
4184
- () => (dockReturnHandoffs || []).filter(
4185
- (handoff) => isSamePath(handoff.path, pathname)
4186
- ),
4187
- [dockReturnHandoffs, pathname]
4188
- );
4189
- const consume = useCallback(
4190
- (handoffId = null) => continuity.consumeReturn(pathname, handoffId),
4191
- [continuity, pathname]
3904
+ function isEditableDockTarget(target) {
3905
+ return closestMatches(
3906
+ target,
3907
+ 'input, textarea, select, [contenteditable="true"]'
4192
3908
  );
4193
- return useMemo(
4194
- () => ({ consume, entries, peek: () => entries[0] || null }),
4195
- [consume, entries]
3909
+ }
3910
+ function getDockFocusableElements(container) {
3911
+ if (!container || typeof container.querySelectorAll !== "function") return [];
3912
+ return Array.from(
3913
+ container.querySelectorAll(DOCK_FOCUSABLE_SELECTOR)
3914
+ ).filter((element) => {
3915
+ if (!element || element.getAttribute("aria-hidden") === "true")
3916
+ return false;
3917
+ const style = typeof window !== "undefined" ? window.getComputedStyle(element) : null;
3918
+ return style?.display !== "none" && style?.visibility !== "hidden" && style?.pointerEvents !== "none";
3919
+ });
3920
+ }
3921
+ function focusDockElement(element) {
3922
+ if (!element || typeof element.focus !== "function") return false;
3923
+ try {
3924
+ element.focus({ preventScroll: true });
3925
+ return document.activeElement === element;
3926
+ } catch {
3927
+ return false;
3928
+ }
3929
+ }
3930
+ function shouldRestoreDockFocus(result) {
3931
+ const blockedReason = result?.blockedReason;
3932
+ return !result || !DOCK_FOCUS_RESTORE_BLOCKED_REASONS.includes(
3933
+ blockedReason
4196
3934
  );
4197
3935
  }
4198
3936
  function useDockFocusTrap({
@@ -4269,8 +4007,9 @@ function useDockKeyboard({
4269
4007
  }) {
4270
4008
  const handleKeyDown = useCallback(
4271
4009
  (event) => {
4272
- if (isEditableDockTarget(event.target) || isInteractiveTarget(event.target))
4010
+ if (isEditableDockTarget(event.target) || isInteractiveTarget(event.target)) {
4273
4011
  return;
4012
+ }
4274
4013
  if (isOverlayActive || !expanded) return;
4275
4014
  const { key } = event;
4276
4015
  if (key === "Escape") {
@@ -4281,8 +4020,9 @@ function useDockKeyboard({
4281
4020
  if (key === "Enter" && focusedIndex !== -1) {
4282
4021
  event.preventDefault();
4283
4022
  const focusedItem = dockItems[focusedIndex];
4284
- if (focusedItem?.path)
4023
+ if (focusedItem?.path) {
4285
4024
  navigate(focusedItem.path, { item: focusedItem });
4025
+ }
4286
4026
  return;
4287
4027
  }
4288
4028
  if (dockItems.length === 0) return;
@@ -4310,814 +4050,1119 @@ function useDockKeyboard({
4310
4050
  setFocusedIndex
4311
4051
  ]
4312
4052
  );
4313
- useEffect(() => {
4314
- if (!expanded) return;
4315
- window.addEventListener("keydown", handleKeyDown);
4316
- return () => window.removeEventListener("keydown", handleKeyDown);
4317
- }, [expanded, handleKeyDown]);
4053
+ useEffect(() => {
4054
+ if (!expanded) return;
4055
+ window.addEventListener("keydown", handleKeyDown);
4056
+ return () => window.removeEventListener("keydown", handleKeyDown);
4057
+ }, [expanded, handleKeyDown]);
4058
+ }
4059
+ function useDockRouteReset(pathname, onRouteChange) {
4060
+ const previousPathRef = useRef(pathname);
4061
+ useEffect(() => {
4062
+ if (previousPathRef.current === pathname) return;
4063
+ previousPathRef.current = pathname;
4064
+ onRouteChange?.(pathname);
4065
+ }, [onRouteChange, pathname]);
4066
+ }
4067
+ function useSurfaceFlows({
4068
+ closeSurface,
4069
+ openSurface,
4070
+ refs,
4071
+ syncSurfaceStack
4072
+ }) {
4073
+ const updateSurfaceFlow = useCallback(
4074
+ (flowId, snapshot) => {
4075
+ const surfaceId = refs.flowIndex.get(flowId);
4076
+ if (!surfaceId) return false;
4077
+ const record = refs.records.get(surfaceId);
4078
+ if (!record?.flow) return false;
4079
+ const nextSession = updateSurfaceFlowSession(record.flow, snapshot);
4080
+ if (!nextSession) return false;
4081
+ const nextSurfaceEntry = {
4082
+ ...record.entry,
4083
+ flow: toSurfaceFlowState(nextSession)
4084
+ };
4085
+ record.flow = nextSession;
4086
+ record.entry = nextSurfaceEntry;
4087
+ syncSurfaceFlowUrlState(nextSurfaceEntry, record.urlState);
4088
+ syncSurfaceStack(
4089
+ updateSurfaceStackEntry(
4090
+ refs.stackRef.current,
4091
+ surfaceId,
4092
+ () => nextSurfaceEntry
4093
+ )
4094
+ );
4095
+ return true;
4096
+ },
4097
+ [syncSurfaceStack, refs]
4098
+ );
4099
+ const completeSurfaceFlow = useCallback(
4100
+ (flowId, data = null) => {
4101
+ const surfaceId = refs.flowIndex.get(flowId);
4102
+ if (!surfaceId) return false;
4103
+ const record = refs.records.get(surfaceId);
4104
+ if (record?.flow)
4105
+ record.flow = {
4106
+ ...record.flow,
4107
+ status: DOCK_SURFACE_FLOW_STATUS.COMPLETED
4108
+ };
4109
+ closeSurface({ data, success: true }, surfaceId);
4110
+ return true;
4111
+ },
4112
+ [closeSurface, refs]
4113
+ );
4114
+ const cancelSurfaceFlow = useCallback(
4115
+ (flowId, data = null) => {
4116
+ const surfaceId = refs.flowIndex.get(flowId);
4117
+ if (!surfaceId) return false;
4118
+ const record = refs.records.get(surfaceId);
4119
+ if (record?.flow)
4120
+ record.flow = {
4121
+ ...record.flow,
4122
+ status: DOCK_SURFACE_FLOW_STATUS.CANCELLED
4123
+ };
4124
+ closeSurface(
4125
+ { cancelled: true, data, reason: "flow-cancelled", success: false },
4126
+ surfaceId
4127
+ );
4128
+ return true;
4129
+ },
4130
+ [closeSurface, refs]
4131
+ );
4132
+ const getSurfaceFlow = useCallback(
4133
+ (flowId) => {
4134
+ const surfaceId = refs.flowIndex.get(flowId);
4135
+ const session = surfaceId ? refs.records.get(surfaceId)?.flow : null;
4136
+ if (!session) return null;
4137
+ return {
4138
+ cancel: (data = null) => cancelSurfaceFlow(flowId, data),
4139
+ complete: (data = null) => completeSurfaceFlow(flowId, data),
4140
+ flowId,
4141
+ isOpen: session.status === DOCK_SURFACE_FLOW_STATUS.OPEN,
4142
+ snapshot: session.snapshot,
4143
+ status: session.status,
4144
+ update: (snapshot) => updateSurfaceFlow(flowId, snapshot)
4145
+ };
4146
+ },
4147
+ [cancelSurfaceFlow, completeSurfaceFlow, updateSurfaceFlow, refs]
4148
+ );
4149
+ const openSurfaceFlow = useCallback(
4150
+ (input, flowInput = null, {
4151
+ preserveUrl = false,
4152
+ snapshot
4153
+ } = {}) => {
4154
+ const definition = createSurfaceFlowDefinition(input);
4155
+ if (!definition) {
4156
+ const error = createSurfaceError(
4157
+ "DOCK_SURFACE_FLOW_INVALID_DEFINITION",
4158
+ "Dock surface flow definition is invalid"
4159
+ );
4160
+ report("Dock surface flow definition", error);
4161
+ return Promise.resolve({ success: false, error });
4162
+ }
4163
+ const existingSurfaceId = refs.flowIndex.get(definition.id);
4164
+ if (definition.singleton && existingSurfaceId) {
4165
+ return refs.records.get(existingSurfaceId)?.promise ?? Promise.resolve({
4166
+ success: false,
4167
+ error: createSurfaceError(
4168
+ "DOCK_SURFACE_FLOW_ORPHANED",
4169
+ "Dock surface flow is orphaned"
4170
+ )
4171
+ });
4172
+ }
4173
+ const flowSession = createSurfaceFlowSession(definition, {
4174
+ input: flowInput,
4175
+ snapshot
4176
+ });
4177
+ try {
4178
+ const surfaceInput = definition.createSurface({
4179
+ flowId: definition.id,
4180
+ input: flowInput,
4181
+ snapshot: flowSession?.snapshot
4182
+ });
4183
+ return openSurface(surfaceInput, { flowSession, preserveUrl });
4184
+ } catch (error) {
4185
+ report("Dock surface flow factory", error);
4186
+ return Promise.resolve({ success: false, error });
4187
+ }
4188
+ },
4189
+ [openSurface, refs]
4190
+ );
4191
+ const restoreSurfaceFlow = useCallback(
4192
+ (input) => {
4193
+ const definition = createSurfaceFlowDefinition(input);
4194
+ const snapshot = getRestorableSurfaceFlowSnapshot(definition);
4195
+ if (!definition || snapshot === void 0) return Promise.resolve(null);
4196
+ return openSurfaceFlow(definition, null, { preserveUrl: true, snapshot });
4197
+ },
4198
+ [openSurfaceFlow]
4199
+ );
4200
+ return {
4201
+ cancelSurfaceFlow,
4202
+ completeSurfaceFlow,
4203
+ getSurfaceFlow,
4204
+ openSurfaceFlow,
4205
+ restoreSurfaceFlow,
4206
+ updateSurfaceFlow
4207
+ };
4208
+ }
4209
+ function useSurfaceLifecycleEffects({
4210
+ clearChoreographyTimers,
4211
+ closeAllSurfaces,
4212
+ focusRestoreFrameRef,
4213
+ refs,
4214
+ runtimeScheduler
4215
+ }) {
4216
+ useEffect(() => {
4217
+ if (!IS_BROWSER) return;
4218
+ const handlePopState = () => {
4219
+ const activeEntry = refs.stackRef.current[refs.stackRef.current.length - 1];
4220
+ if (!activeEntry?.syncWithUrl && !activeEntry?.urlKey) return;
4221
+ if (new URL(window.location.href).searchParams.get("surface") === getSurfaceUrlValue(activeEntry))
4222
+ return;
4223
+ closeAllSurfaces({
4224
+ success: false,
4225
+ cancelled: true,
4226
+ reason: "browser-back"
4227
+ });
4228
+ };
4229
+ window.addEventListener("popstate", handlePopState);
4230
+ return () => window.removeEventListener("popstate", handlePopState);
4231
+ }, [closeAllSurfaces, refs]);
4232
+ useEffect(() => {
4233
+ return () => {
4234
+ clearChoreographyTimers();
4235
+ if (focusRestoreFrameRef.current !== null) {
4236
+ runtimeScheduler.cancel(focusRestoreFrameRef.current);
4237
+ focusRestoreFrameRef.current = null;
4238
+ }
4239
+ const result = { cancelled: true, reason: "unmount", success: false };
4240
+ refs.stackRef.current.forEach(
4241
+ ({ id }) => releaseSurfaceResources({ refs, result, surfaceId: id })
4242
+ );
4243
+ refs.stackRef.current = [];
4244
+ refs.payloads.clear();
4245
+ refs.records.clear();
4246
+ refs.flowIndex.clear();
4247
+ };
4248
+ }, [clearChoreographyTimers, focusRestoreFrameRef, refs, runtimeScheduler]);
4249
+ }
4250
+ var SurfaceFlowContext = createContext(null);
4251
+ function SurfaceFlowProvider({
4252
+ children,
4253
+ value
4254
+ }) {
4255
+ return /* @__PURE__ */ jsx(SurfaceFlowContext, { value, children });
4256
+ }
4257
+ function useSurfaceFlow(input) {
4258
+ const context = use(SurfaceFlowContext);
4259
+ const definition = useMemo(() => createSurfaceFlowDefinition(input), [input]);
4260
+ const restoredFlowIdsRef = useRef(/* @__PURE__ */ new Set());
4261
+ const flowId = definition?.id ?? null;
4262
+ const activeFlow = useMemo(() => {
4263
+ if (!flowId) return null;
4264
+ return context?.surfaceState?.surfaceStack?.map((s) => s.flow).find((f) => f?.flowId === flowId) ?? null;
4265
+ }, [context?.surfaceState?.surfaceStack, flowId]);
4266
+ useEffect(() => {
4267
+ if (!definition || !context?.restoreSurfaceFlow || restoredFlowIdsRef.current.has(definition.id))
4268
+ return;
4269
+ restoredFlowIdsRef.current.add(definition.id);
4270
+ void context.restoreSurfaceFlow(definition);
4271
+ }, [context, definition]);
4272
+ const open = useCallback(
4273
+ (flowInput = null) => {
4274
+ if (!definition || !context?.openSurfaceFlow) {
4275
+ return Promise.resolve({
4276
+ success: false,
4277
+ error: createSurfaceError(
4278
+ "DOCK_SURFACE_FLOW_UNAVAILABLE",
4279
+ "Dock surface flow is unavailable"
4280
+ )
4281
+ });
4282
+ }
4283
+ return context.openSurfaceFlow(definition, flowInput);
4284
+ },
4285
+ [context, definition]
4286
+ );
4287
+ return useMemo(
4288
+ () => ({
4289
+ activeFlow,
4290
+ cancel: (result = null) => flowId ? context?.cancelSurfaceFlow?.(flowId, result) : void 0,
4291
+ complete: (result = null) => flowId ? context?.completeSurfaceFlow?.(flowId, result) : void 0,
4292
+ flowId,
4293
+ isOpen: activeFlow?.status === DOCK_SURFACE_FLOW_STATUS.OPEN,
4294
+ open,
4295
+ snapshot: activeFlow?.snapshot ?? definition?.initialSnapshot ?? null,
4296
+ update: (snapshot) => flowId ? context?.updateSurfaceFlow?.(flowId, snapshot) : void 0
4297
+ }),
4298
+ [activeFlow, context, definition?.initialSnapshot, flowId, open]
4299
+ );
4318
4300
  }
4319
- function useDockRouteReset(pathname, onRouteChange) {
4320
- const previousPathRef = useRef(pathname);
4321
- useEffect(() => {
4322
- if (previousPathRef.current === pathname) return;
4323
- previousPathRef.current = pathname;
4324
- onRouteChange?.(pathname);
4325
- }, [onRouteChange, pathname]);
4301
+ var DockSurfaceHeaderRadiusContext = createContext(
4302
+ null
4303
+ );
4304
+ function useDockSurfaceHeaderButtonModel({
4305
+ children,
4306
+ className = "",
4307
+ disabled = false,
4308
+ onClick,
4309
+ ariaLabel,
4310
+ icon = null
4311
+ }) {
4312
+ const contextRadius = use(DockSurfaceHeaderRadiusContext);
4313
+ return { contextRadius };
4326
4314
  }
4327
- function getDockItemCardProps({
4328
- cardScale,
4329
- cardStyle,
4330
- expanded,
4315
+ function useDockSurfaceControlsModel({
4316
+ activeItem = null,
4331
4317
  hasExtensions = false,
4332
- isAnchoredToBottom,
4333
- position,
4334
- theme,
4335
- visibleCount = 3
4318
+ onClose = null,
4319
+ onBack = null,
4320
+ closeLabel = null,
4321
+ backLabel = null,
4322
+ className = "",
4323
+ surfacePhase: propSurfacePhase
4336
4324
  }) {
4337
- const { offsetY: collapsedOffsetY, scale: collapsedScale } = DOCK_CARD_LAYOUT.collapsed;
4338
- const { offsetY: expandedOffsetY } = DOCK_CARD_LAYOUT.expanded;
4339
- const safeCardStyle = { ...cardStyle };
4340
- delete safeCardStyle.scale;
4341
- delete safeCardStyle.className;
4342
- const isTop = position === 0;
4343
- const isShelf = !expanded && position === 1 && hasExtensions;
4344
- const isHeavyBlur = position < visibleCount || expanded || isShelf;
4345
- const collapsedScaleValue = collapsedScale ** position;
4346
- const y = expanded ? position * expandedOffsetY : isShelf ? DOCK_CARD_LAYOUT.extensionShelfY : position * collapsedOffsetY;
4347
- const shelfScale = DOCK_CARD_LAYOUT.extensionShelfScale ?? 0.94;
4348
- const scale = expanded ? cardScale || 1 : isShelf ? shelfScale : collapsedScaleValue;
4349
- const collapsedOpacity = Math.max(0.1, +(1 - position * 0.2).toFixed(2));
4350
- const opacity = expanded || isShelf ? 1 : position < visibleCount ? collapsedOpacity : 0;
4351
- return {
4352
- className: cn(theme.slots.card, cardStyle?.className),
4353
- data: {
4354
- "data-anchored": isAnchoredToBottom,
4355
- "data-blur": isHeavyBlur ? "heavy" : "light",
4356
- "data-placement": isTop ? "top" : isAnchoredToBottom ? "bottom" : "float",
4357
- "data-shelf": isShelf
4358
- },
4359
- style: {
4360
- ...theme.styles.card,
4361
- ...safeCardStyle,
4362
- willChange: "transform, opacity",
4363
- transformOrigin: isAnchoredToBottom ? "bottom center" : "top center",
4364
- zIndex: Z_INDEX.DOCK_CARD_STACK_BASE - position,
4365
- WebkitPerspective: 1e3,
4366
- perspective: 1e3,
4367
- WebkitBackfaceVisibility: "hidden",
4368
- backfaceVisibility: "hidden",
4369
- WebkitFontSmoothing: "antialiased",
4370
- transform: "translateZ(0)",
4371
- ...isTop ? { height: "100%" } : {},
4372
- pointerEvents: expanded || position < visibleCount ? void 0 : "none"
4325
+ const phase = propSurfacePhase ?? activeItem?.surfacePhase;
4326
+ const isBodyVisible = phase ? phase === DOCK_SURFACE_PHASE.EXPANDING_BODY || phase === DOCK_SURFACE_PHASE.OPEN : true;
4327
+ const actionStore = use(SurfaceExtensionsContext)?.headerActions ?? null;
4328
+ const surfaceId = activeItem?.surfaceId || "active";
4329
+ const subscribeAction = useCallback(
4330
+ (onStoreChange) => actionStore ? actionStore.subscribe(onStoreChange) : () => {
4373
4331
  },
4374
- motionValues: { y, scale, opacity }
4375
- };
4376
- }
4377
- function getViewportMaxHeight() {
4378
- if (typeof window === "undefined") return Infinity;
4379
- return window.innerHeight - VIEWPORT_MARGIN;
4380
- }
4381
- function getContainerHeight({
4382
- cardContentHeight,
4383
- isHud = false
4384
- }) {
4385
- const chromeHeight = DOCK_CARD_LAYOUT.chromeHeight;
4386
- const minCardHeight = isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight;
4387
- const numericContentHeight = Number(cardContentHeight);
4388
- const nextCardHeight = Math.max(
4389
- minCardHeight,
4390
- (Number.isFinite(numericContentHeight) ? numericContentHeight : 0) + chromeHeight
4332
+ [actionStore]
4391
4333
  );
4392
- return Math.min(nextCardHeight, getViewportMaxHeight());
4393
- }
4394
- function getDockCardWidth({
4395
- width
4396
- } = {}) {
4397
- if (typeof window === "undefined") return width ? Number(width) || 460 : 460;
4398
- const windowWidth = window.innerWidth;
4399
- const isDesktop = windowWidth >= 640;
4400
- const targetWidth = Number(width);
4401
- if (Number.isFinite(targetWidth) && targetWidth > 0) {
4402
- return Math.min(
4403
- targetWidth,
4404
- Math.max(windowWidth - (isDesktop ? 32 : 16), 0)
4405
- );
4406
- }
4407
- return Math.min(460, Math.max(windowWidth - 16, 0));
4408
- }
4409
- function getObservedHeight(entry, element) {
4410
- const rawBorderBoxSize = entry?.borderBoxSize;
4411
- const borderBoxSize = Array.isArray(rawBorderBoxSize) ? rawBorderBoxSize[0] : rawBorderBoxSize;
4412
- if (borderBoxSize?.blockSize != null)
4413
- return Math.round(borderBoxSize.blockSize);
4414
- if (entry?.contentRect?.height != null)
4415
- return Math.round(entry.contentRect.height);
4416
- return Math.round(element?.offsetHeight || 0);
4334
+ const getActionSnapshot = useCallback(
4335
+ () => actionStore ? actionStore.getAction(surfaceId) : null,
4336
+ [actionStore, surfaceId]
4337
+ );
4338
+ const dynamicAction = useSyncExternalStore(
4339
+ subscribeAction,
4340
+ getActionSnapshot,
4341
+ () => null
4342
+ );
4343
+ return { isBodyVisible, dynamicAction };
4417
4344
  }
4418
- function hasMeaningfulHeightChange(previousHeight, nextHeight) {
4419
- return Math.abs(Math.round(nextHeight) - Math.round(previousHeight)) > HEIGHT_EPSILON;
4345
+ function SurfaceExtensionsProvider({
4346
+ children
4347
+ }) {
4348
+ const [value] = useState(() => ({
4349
+ extensions: new SurfaceExtensionsStore(),
4350
+ headerActions: new SurfaceHeaderActionStore()
4351
+ }));
4352
+ return /* @__PURE__ */ jsx(SurfaceExtensionsContext, { value, children });
4420
4353
  }
4421
- function useElementHeight(onHeightChange, elementRef, shouldMeasure, dependencyKey = null) {
4422
- const lastHeightRef = useRef(0);
4423
- const rafRef = useRef(null);
4424
- const callbackRef = useRef(onHeightChange);
4425
- useIsomorphicLayoutEffect(() => {
4426
- callbackRef.current = onHeightChange;
4427
- }, [onHeightChange]);
4354
+ function useDockSurfaceExtension({
4355
+ align = "left",
4356
+ children,
4357
+ className = "",
4358
+ id,
4359
+ order = 0,
4360
+ unstyled = false
4361
+ }) {
4362
+ const store = use(SurfaceExtensionsContext)?.extensions ?? null;
4363
+ const surfaceId = useSurfaceId();
4364
+ const generatedId = useId();
4365
+ const effectiveId = id || `dock-surface-ext-${generatedId}`;
4428
4366
  useIsomorphicLayoutEffect(() => {
4429
- if (rafRef.current !== null) {
4430
- cancelAnimationFrame(rafRef.current);
4431
- rafRef.current = null;
4432
- }
4433
- lastHeightRef.current = -1;
4434
- if (!callbackRef.current) return;
4435
- if (!shouldMeasure) {
4436
- if (hasMeaningfulHeightChange(lastHeightRef.current, 0)) {
4437
- lastHeightRef.current = 0;
4438
- callbackRef.current(0);
4439
- }
4440
- return;
4441
- }
4442
- const element = elementRef?.current;
4443
- if (!element) return;
4444
- function publishHeight(nextHeight) {
4445
- if (!hasMeaningfulHeightChange(lastHeightRef.current, nextHeight)) return;
4446
- lastHeightRef.current = nextHeight;
4447
- callbackRef.current?.(nextHeight);
4448
- }
4449
- let pendingHeight = null;
4450
- function flushPendingHeight() {
4451
- rafRef.current = null;
4452
- if (pendingHeight == null) return;
4453
- const heightToPublish = pendingHeight;
4454
- pendingHeight = null;
4455
- publishHeight(heightToPublish);
4456
- }
4457
- function scheduleMeasurement(nextHeight) {
4458
- pendingHeight = nextHeight;
4459
- if (rafRef.current !== null) return;
4460
- rafRef.current = requestAnimationFrame(flushPendingHeight);
4461
- }
4462
- publishHeight(element.offsetHeight || 0);
4463
- const observer = new ResizeObserver((entries) => {
4464
- for (const entry of entries) {
4465
- scheduleMeasurement(getObservedHeight(entry, element));
4466
- }
4467
- });
4468
- observer.observe(element);
4469
- const handlePageShow = () => scheduleMeasurement(element.offsetHeight || 0);
4470
- const handleVisibilityChange = () => {
4471
- if (document.visibilityState === "visible") handlePageShow();
4367
+ if (!store) return;
4368
+ const extension = {
4369
+ align,
4370
+ className,
4371
+ content: children,
4372
+ id: effectiveId,
4373
+ order,
4374
+ unstyled
4472
4375
  };
4473
- window.addEventListener("pageshow", handlePageShow);
4474
- document.addEventListener("visibilitychange", handleVisibilityChange);
4376
+ store.setExtension(surfaceId, extension);
4377
+ if (surfaceId && surfaceId !== "active") {
4378
+ store.setExtension("active", extension);
4379
+ }
4380
+ });
4381
+ useEffect(() => {
4475
4382
  return () => {
4476
- observer.disconnect();
4477
- window.removeEventListener("pageshow", handlePageShow);
4478
- document.removeEventListener("visibilitychange", handleVisibilityChange);
4479
- if (rafRef.current !== null) {
4480
- cancelAnimationFrame(rafRef.current);
4481
- rafRef.current = null;
4383
+ if (store) {
4384
+ store.removeExtension(surfaceId, effectiveId);
4385
+ if (surfaceId && surfaceId !== "active") {
4386
+ store.removeExtension("active", effectiveId);
4387
+ }
4482
4388
  }
4483
4389
  };
4484
- }, [dependencyKey, elementRef, shouldMeasure]);
4390
+ }, [store, surfaceId, effectiveId]);
4485
4391
  }
4486
- function publishDockHeightCssVar(heightPx) {
4487
- if (typeof document === "undefined") return;
4488
- document.documentElement.style.setProperty(
4489
- "--dock-h",
4490
- `${Math.round(heightPx)}px`
4392
+ function useIsSurfaceExtensionsVisible(activeItem) {
4393
+ const store = use(SurfaceExtensionsContext)?.extensions ?? null;
4394
+ const surfaceId = activeItem?.surfaceId || "active";
4395
+ const isSurface = Boolean(activeItem?.isSurface);
4396
+ const phase = activeItem?.surfacePhase;
4397
+ const isBodyVisible = phase === DOCK_SURFACE_PHASE.EXPANDING_BODY || phase === DOCK_SURFACE_PHASE.OPEN;
4398
+ const subscribe2 = useCallback(
4399
+ (onStoreChange) => store ? store.subscribe(onStoreChange) : () => {
4400
+ },
4401
+ [store]
4402
+ );
4403
+ const getSnapshot2 = useCallback(
4404
+ () => store ? store.getExtensionsForSurface(surfaceId).length : 0,
4405
+ [store, surfaceId]
4406
+ );
4407
+ const dynamicCount = useSyncExternalStore(subscribe2, getSnapshot2, () => 0);
4408
+ const descriptorCount = useMemo(() => {
4409
+ const raw = activeItem?.surfaceExtensions || activeItem?.extensions || [];
4410
+ return Array.isArray(raw) ? raw.length : 0;
4411
+ }, [activeItem?.surfaceExtensions, activeItem?.extensions]);
4412
+ return Boolean(
4413
+ isSurface && isBodyVisible && (dynamicCount > 0 || descriptorCount > 0)
4491
4414
  );
4492
4415
  }
4493
- function useDockHeightController({
4494
- contentKey = null,
4495
- isHud = false,
4496
- setDockHeight,
4497
- surfacePhase = DOCK_SURFACE_PHASE.IDLE
4416
+ function useDockSurfaceExtensionsBarModel({
4417
+ activeItem
4498
4418
  }) {
4499
- const [containerHeight, setContainerHeight] = useState(
4500
- isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight
4419
+ const store = use(SurfaceExtensionsContext)?.extensions ?? null;
4420
+ const surfaceId = activeItem?.surfaceId || "active";
4421
+ const subscribe2 = useCallback(
4422
+ (onStoreChange) => store ? store.subscribe(onStoreChange) : () => {
4423
+ },
4424
+ [store]
4501
4425
  );
4502
- const heightRef = useRef({ content: 0 });
4503
- const rafRef = useRef(null);
4504
- const isHudRef = useRef(isHud);
4505
- const surfacePhaseRef = useRef(surfacePhase);
4506
- const lastAppliedContainerHeightRef = useRef(
4507
- isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight
4426
+ const getSnapshot2 = useCallback(
4427
+ () => store ? store.getExtensionsForSurface(surfaceId) : [],
4428
+ [store, surfaceId]
4508
4429
  );
4509
- const lastAppliedSpacerHeightRef = useRef(
4510
- (isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight) + DOCK_HEIGHT_BUFFER
4430
+ const dynamicExtensions = useSyncExternalStore(
4431
+ subscribe2,
4432
+ getSnapshot2,
4433
+ () => []
4511
4434
  );
4512
- useInsertionEffect(() => {
4513
- isHudRef.current = isHud;
4514
- surfacePhaseRef.current = surfacePhase;
4515
- }, [isHud, surfacePhase]);
4516
- const applyHeight = useCallback(() => {
4517
- if (rafRef.current !== null) {
4518
- cancelAnimationFrame(rafRef.current);
4519
- rafRef.current = null;
4520
- }
4521
- const { content } = heightRef.current;
4522
- const currentPhase = surfacePhaseRef.current;
4523
- const isLockedToBaseHeight = currentPhase === DOCK_SURFACE_PHASE.DISMISSING_ACTION || currentPhase === DOCK_SURFACE_PHASE.COLLAPSING_BODY || currentPhase === DOCK_SURFACE_PHASE.RESTORING_HEADER;
4524
- const computedContentHeight = isLockedToBaseHeight ? 0 : content;
4525
- const height = getContainerHeight({
4526
- cardContentHeight: computedContentHeight,
4527
- isHud: isHudRef.current
4528
- });
4529
- const totalSpacerHeight = height + DOCK_HEIGHT_BUFFER;
4530
- if (Math.abs(height - lastAppliedContainerHeightRef.current) > 0.5) {
4531
- lastAppliedContainerHeightRef.current = height;
4532
- setContainerHeight(height);
4533
- }
4534
- if (Math.abs(totalSpacerHeight - lastAppliedSpacerHeightRef.current) > 0.5) {
4535
- lastAppliedSpacerHeightRef.current = totalSpacerHeight;
4536
- publishDockHeightCssVar(totalSpacerHeight);
4537
- setDockHeight(totalSpacerHeight);
4435
+ const descriptorExtensions = useMemo(() => {
4436
+ const raw = activeItem?.surfaceExtensions || activeItem?.extensions || [];
4437
+ return Array.isArray(raw) ? raw.map(normalizeSurfaceExtension).filter((ext) => ext !== null) : [];
4438
+ }, [activeItem?.surfaceExtensions, activeItem?.extensions]);
4439
+ const allExtensions = useMemo(() => {
4440
+ const map = /* @__PURE__ */ new Map();
4441
+ descriptorExtensions.forEach((ext) => map.set(ext.id, ext));
4442
+ dynamicExtensions.forEach((ext) => map.set(ext.id, ext));
4443
+ return Array.from(map.values()).sort((a, b) => a.order - b.order);
4444
+ }, [descriptorExtensions, dynamicExtensions]);
4445
+ return { allExtensions };
4446
+ }
4447
+ var DRAG_RANGE = [...DOCK_SURFACE_DRAG_INTERPOLATION.DRAG_RANGE];
4448
+ var OPACITY_RANGE = [...DOCK_SURFACE_DRAG_INTERPOLATION.OPACITY_RANGE];
4449
+ var SCALE_RANGE = [...DOCK_SURFACE_DRAG_INTERPOLATION.SCALE_RANGE];
4450
+ function useDockSurfaceShellModel({
4451
+ ref,
4452
+ title = "",
4453
+ onClose = null,
4454
+ onBack = null,
4455
+ allowSwipeDismiss = true,
4456
+ closeLabel = "Close surface",
4457
+ backLabel = "Previous step",
4458
+ showControls = false,
4459
+ className = "",
4460
+ contentClassName = "",
4461
+ children,
4462
+ onAnimationComplete = null,
4463
+ isActive = true,
4464
+ surfaceId = null,
4465
+ surfacePhase = DOCK_SURFACE_PHASE.OPEN,
4466
+ surfaceWidth = null
4467
+ }) {
4468
+ const surfaceElementRef = useRef(null);
4469
+ const setSurfaceElementRef = useCallback(
4470
+ (node) => {
4471
+ surfaceElementRef.current = node;
4472
+ if (typeof ref === "function") ref(node);
4473
+ else if (ref) ref.current = node;
4474
+ },
4475
+ [ref]
4476
+ );
4477
+ const isFullyOpen = surfacePhase === DOCK_SURFACE_PHASE.OPEN;
4478
+ const titleId = surfaceId == null ? void 0 : `dock-surface-title-${surfaceId}`;
4479
+ const dragY = useMotionValue(0);
4480
+ const dragOpacity = useTransform(dragY, DRAG_RANGE, OPACITY_RANGE);
4481
+ const dragScale = useTransform(dragY, DRAG_RANGE, SCALE_RANGE);
4482
+ useDockFocusTrap({
4483
+ containerRef: surfaceElementRef,
4484
+ enabled: isActive && isFullyOpen,
4485
+ onDismiss: typeof onClose === "function" ? onClose : null
4486
+ });
4487
+ const handleDragEnd = (_event, info) => {
4488
+ if (!isActive || !allowSwipeDismiss || typeof onClose !== "function" || !isFullyOpen)
4489
+ return;
4490
+ if (info.offset.y > DOCK_SURFACE_DRAG_THRESHOLDS.DISMISS_OFFSET_Y || info.velocity.y > DOCK_SURFACE_DRAG_THRESHOLDS.DISMISS_VELOCITY_Y) {
4491
+ onClose();
4538
4492
  }
4539
- }, [setDockHeight]);
4540
- const handleContentHeightChange = useCallback(
4541
- (height) => {
4542
- const numericHeight = Number(height);
4543
- heightRef.current.content = Number.isFinite(numericHeight) ? Math.max(0, numericHeight) : 0;
4544
- applyHeight();
4493
+ };
4494
+ const dragControls = useDragControls();
4495
+ const canDragDismiss = Boolean(
4496
+ isActive && allowSwipeDismiss && typeof onClose === "function" && isFullyOpen
4497
+ );
4498
+ const handleSurfacePointerDown = useCallback(
4499
+ (event) => {
4500
+ if (!canDragDismiss) return;
4501
+ const target = event.target;
4502
+ if (target?.closest?.(
4503
+ 'input, textarea, select, [role="slider"], [data-no-surface-drag], [data-lenis-prevent]'
4504
+ )) {
4505
+ return;
4506
+ }
4507
+ dragControls.start(event);
4545
4508
  },
4546
- [applyHeight]
4509
+ [canDragDismiss, dragControls]
4547
4510
  );
4548
- useEffect(() => {
4549
- return () => {
4550
- if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
4551
- };
4552
- }, []);
4553
- useIsomorphicLayoutEffect(() => applyHeight(), [applyHeight, contentKey]);
4554
- useIsomorphicLayoutEffect(() => applyHeight(), [applyHeight, surfacePhase]);
4555
- useIsomorphicLayoutEffect(() => applyHeight(), [applyHeight, isHud]);
4556
- return { containerHeight, handleContentHeightChange };
4557
- }
4558
- function isSurfaceShapePhase(phase) {
4559
- return phase === DOCK_SURFACE_PHASE.EXPANDING_BODY || phase === DOCK_SURFACE_PHASE.OPEN || phase === DOCK_SURFACE_PHASE.CLOSING_ANTICIPATION;
4560
- }
4561
- function getTargetItemWidth(activeItem) {
4562
- if (!activeItem?.isSurface || isSurfaceShapePhase(activeItem.surfacePhase))
4563
- return activeItem?.width;
4564
- return activeItem.restingWidth;
4511
+ const isBodyVisible = surfacePhase === DOCK_SURFACE_PHASE.EXPANDING_BODY || surfacePhase === DOCK_SURFACE_PHASE.OPEN;
4512
+ const resolvedSurfaceId = surfaceId || "active";
4513
+ const bodyVariants = useMemo(
4514
+ () => ({
4515
+ hidden: dockSurfaceBodyRole.hidden,
4516
+ visible: {
4517
+ ...dockSurfaceBodyRole.visible,
4518
+ transition: surfacePhase === DOCK_SURFACE_PHASE.EXPANDING_BODY ? DOCK_SURFACE_BODY_ENTER_TRANSITION : DOCK_SURFACE_BODY_STEP_TRANSITION
4519
+ },
4520
+ exit: {
4521
+ ...dockSurfaceBodyRole.exit,
4522
+ transition: DOCK_SURFACE_BODY_EXIT_TRANSITION
4523
+ }
4524
+ }),
4525
+ [surfacePhase]
4526
+ );
4527
+ const surfaceItemValue = useMemo(
4528
+ () => ({ id: resolvedSurfaceId, width: surfaceWidth }),
4529
+ [resolvedSurfaceId, surfaceWidth]
4530
+ );
4531
+ return {
4532
+ setSurfaceElementRef,
4533
+ titleId,
4534
+ dragY,
4535
+ dragOpacity,
4536
+ dragScale,
4537
+ handleDragEnd,
4538
+ dragControls,
4539
+ canDragDismiss,
4540
+ handleSurfacePointerDown,
4541
+ isBodyVisible,
4542
+ bodyVariants,
4543
+ surfaceItemValue
4544
+ };
4565
4545
  }
4566
- function useDockViewport(activeItem = null) {
4567
- const targetWidth = getTargetItemWidth(activeItem);
4568
- const [, setViewportRevision] = useState(0);
4569
- const [portalTarget, setPortalTarget] = useState(null);
4570
- const stackWidth = getDockCardWidth({ width: targetWidth });
4571
- useIsomorphicLayoutEffect(() => {
4572
- if (typeof document !== "undefined") setPortalTarget(document.body);
4573
- }, []);
4574
- useEffect(() => {
4575
- if (typeof window === "undefined") return void 0;
4576
- let resizeFrameId = null;
4577
- const handleResize = () => {
4578
- if (resizeFrameId !== null) return;
4579
- resizeFrameId = window.requestAnimationFrame(() => {
4580
- resizeFrameId = null;
4581
- setViewportRevision((revision) => revision + 1);
4582
- });
4583
- };
4584
- window.addEventListener("resize", handleResize, { passive: true });
4585
- return () => {
4586
- window.removeEventListener("resize", handleResize);
4587
- if (resizeFrameId !== null) window.cancelAnimationFrame(resizeFrameId);
4588
- };
4589
- }, []);
4590
- return { portalTarget, stackWidth };
4546
+
4547
+ // src/modules/dock/styles.ts
4548
+ function isValidBannerUrl(banner) {
4549
+ const value = trimToNull(banner);
4550
+ if (!value) return false;
4551
+ return /^(https?:\/\/|\/|data:image\/)/.test(value);
4591
4552
  }
4592
- function pickCardSurfaceStyle(style) {
4593
- const cardSurfaceStyle = {};
4594
- if (style?.background != null) cardSurfaceStyle.background = style.background;
4595
- if (style?.borderColor != null)
4596
- cardSurfaceStyle.borderColor = style.borderColor;
4597
- return cardSurfaceStyle;
4553
+ function splitStyle(style = {}) {
4554
+ const { className, ...inlineStyle } = style;
4555
+ return {
4556
+ className,
4557
+ inlineStyle
4558
+ };
4598
4559
  }
4599
- function toStyleSections(value) {
4600
- return toObject(value);
4560
+ function getLineClampStyle(maxLines, style) {
4561
+ if (Number(maxLines) <= 1) return style;
4562
+ return {
4563
+ WebkitBoxOrient: "vertical",
4564
+ WebkitLineClamp: maxLines,
4565
+ display: "-webkit-box",
4566
+ overflow: "hidden",
4567
+ ...style
4568
+ };
4601
4569
  }
4602
- function mergeStyleSection(baseStyle, stateStyle, hoverStyle, section) {
4570
+ function getImageIconStyle(style, icon) {
4571
+ const nextStyle = {
4572
+ ...style
4573
+ };
4574
+ delete nextStyle.background;
4575
+ delete nextStyle.backgroundImage;
4603
4576
  return {
4604
- ...toObject(baseStyle?.[section]),
4605
- ...toObject(stateStyle?.[section]),
4606
- ...toObject(hoverStyle?.[section])
4577
+ ...nextStyle,
4578
+ backgroundImage: `url(${icon})`
4607
4579
  };
4608
4580
  }
4609
- function resolveDockVisualStyle(style, {
4581
+ function resolveDockActionClass(slots, {
4582
+ className = "",
4583
+ button = "",
4610
4584
  isActive = false,
4611
- isHovered = false
4585
+ variant = "",
4586
+ tone = "",
4587
+ base,
4588
+ cn: classNamesFn
4612
4589
  } = {}) {
4613
- const baseStyle = toObject(style);
4614
- const stateStyle = isActive ? toStyleSections(baseStyle.active) : toStyleSections(baseStyle.inactive);
4615
- const hoverStyle = isHovered ? toStyleSections(baseStyle.hover) : {};
4616
- const sections = {
4617
- card: {},
4618
- icon: {},
4619
- title: {},
4620
- description: {}
4590
+ const resolve = classNamesFn || cn;
4591
+ const toneClasses = {
4592
+ muted: slots.actionMuted,
4593
+ active: slots.actionActive
4621
4594
  };
4622
- for (const section of DOCK_STYLE_SECTIONS) {
4623
- sections[section] = mergeStyleSection(
4624
- baseStyle,
4625
- stateStyle,
4626
- hoverStyle,
4627
- section
4628
- );
4595
+ if (button && !className && base === void 0) {
4596
+ if (tone) {
4597
+ const toneClass = toneClasses[tone] || slots.actionSurface || slots.actionMuted;
4598
+ return resolve(button, toneClass);
4599
+ }
4600
+ const stateToken = isActive ? slots.actionActive : slots.actionMuted;
4601
+ return resolve(button, stateToken);
4629
4602
  }
4630
- sections.card = { ...pickCardSurfaceStyle(baseStyle), ...sections.card };
4603
+ const elementClass = className || button;
4604
+ const resolvedBase = base !== void 0 ? base : slots.action;
4605
+ const stateClass = variant || tone && (toneClasses[tone] || slots.actionSurface) || (isActive ? slots.actionActive : slots.actionMuted);
4606
+ return resolve(stateClass, resolvedBase, elementClass);
4607
+ }
4608
+ function getDockItemCardProps({
4609
+ cardScale,
4610
+ cardStyle,
4611
+ expanded,
4612
+ hasExtensions = false,
4613
+ isAnchoredToBottom,
4614
+ position,
4615
+ theme,
4616
+ visibleCount = 3
4617
+ }) {
4618
+ const { offsetY: collapsedOffsetY, scale: collapsedScale } = DOCK_CARD_LAYOUT.collapsed;
4619
+ const { offsetY: expandedOffsetY } = DOCK_CARD_LAYOUT.expanded;
4620
+ const safeCardStyle = { ...cardStyle };
4621
+ delete safeCardStyle.scale;
4622
+ delete safeCardStyle.className;
4623
+ const isTop = position === 0;
4624
+ const isShelf = !expanded && position === 1 && hasExtensions;
4625
+ const isHeavyBlur = position < visibleCount || expanded || isShelf;
4626
+ const collapsedScaleValue = collapsedScale ** position;
4627
+ const y = expanded ? position * expandedOffsetY : isShelf ? DOCK_CARD_LAYOUT.extensionShelfY : position * collapsedOffsetY;
4628
+ const shelfScale = DOCK_CARD_LAYOUT.extensionShelfScale ?? 0.94;
4629
+ const scale = expanded ? cardScale || 1 : isShelf ? shelfScale : collapsedScaleValue;
4630
+ const collapsedOpacity = Math.max(0.1, +(1 - position * 0.2).toFixed(2));
4631
+ const opacity = expanded || isShelf ? 1 : position < visibleCount ? collapsedOpacity : 0;
4631
4632
  return {
4632
- ...sections,
4633
- scale: hoverStyle?.card?.scale ?? stateStyle?.card?.scale ?? baseStyle?.scale
4633
+ className: cn(theme.slots.card, cardStyle?.className),
4634
+ data: {
4635
+ "data-anchored": isAnchoredToBottom,
4636
+ "data-blur": isHeavyBlur ? "heavy" : "light",
4637
+ "data-placement": isTop ? "top" : isAnchoredToBottom ? "bottom" : "float",
4638
+ "data-shelf": isShelf
4639
+ },
4640
+ style: {
4641
+ ...theme.styles.card,
4642
+ ...safeCardStyle,
4643
+ willChange: "transform, opacity",
4644
+ transformOrigin: isAnchoredToBottom ? "bottom center" : "top center",
4645
+ zIndex: Z_INDEX.DOCK_CARD_STACK_BASE - position,
4646
+ WebkitPerspective: 1e3,
4647
+ perspective: 1e3,
4648
+ WebkitBackfaceVisibility: "hidden",
4649
+ backfaceVisibility: "hidden",
4650
+ WebkitFontSmoothing: "antialiased",
4651
+ transform: "translateZ(0)",
4652
+ ...isTop ? { height: "100%" } : {},
4653
+ pointerEvents: expanded || position < visibleCount ? void 0 : "none"
4654
+ },
4655
+ motionValues: { y, scale, opacity }
4634
4656
  };
4635
4657
  }
4636
- function shouldRenderInlineAction({
4637
- action,
4638
- isLoading,
4639
- isOverlay,
4640
- path
4641
- }, pathname) {
4642
- return Boolean(action) && !isLoading && (isOverlay || !path || isInlineActionPathMatch(path, pathname));
4658
+ function toObject(value) {
4659
+ return isObject(value) ? value : {};
4643
4660
  }
4644
- function getIsItemActive(link, activeItem) {
4645
- if (!link || !activeItem) return false;
4646
- if (link.path && activeItem.path && isSamePath(link.path, activeItem.path))
4647
- return true;
4648
- if (link.targetPath && activeItem.targetPath && isSamePath(link.targetPath, activeItem.targetPath))
4649
- return true;
4650
- return Boolean(link.name && activeItem.name && link.name === activeItem.name);
4661
+ function pickCardSurfaceStyle(style) {
4662
+ const cardSurfaceStyle = {};
4663
+ if (style?.background != null) cardSurfaceStyle.background = style.background;
4664
+ if (style?.borderColor != null)
4665
+ cardSurfaceStyle.borderColor = style.borderColor;
4666
+ return cardSurfaceStyle;
4651
4667
  }
4652
- function useSurfaceFlows({
4653
- closeSurface,
4654
- openSurface,
4655
- refs,
4656
- syncSurfaceStack
4657
- }) {
4658
- const updateSurfaceFlow = useCallback(
4659
- (flowId, snapshot) => {
4660
- const surfaceId = refs.flowIndex.get(flowId);
4661
- if (!surfaceId) return false;
4662
- const record = refs.records.get(surfaceId);
4663
- if (!record?.flow) return false;
4664
- const nextSession = updateSurfaceFlowSession(record.flow, snapshot);
4665
- if (!nextSession) return false;
4666
- const nextSurfaceEntry = {
4667
- ...record.entry,
4668
- flow: toSurfaceFlowState(nextSession)
4669
- };
4670
- record.flow = nextSession;
4671
- record.entry = nextSurfaceEntry;
4672
- syncSurfaceFlowUrlState(nextSurfaceEntry, record.urlState);
4673
- syncSurfaceStack(
4674
- updateSurfaceStackEntry(
4675
- refs.stackRef.current,
4676
- surfaceId,
4677
- () => nextSurfaceEntry
4678
- )
4679
- );
4680
- return true;
4681
- },
4682
- [syncSurfaceStack, refs]
4683
- );
4684
- const completeSurfaceFlow = useCallback(
4685
- (flowId, data = null) => {
4686
- const surfaceId = refs.flowIndex.get(flowId);
4687
- if (!surfaceId) return false;
4688
- const record = refs.records.get(surfaceId);
4689
- if (record?.flow)
4690
- record.flow = {
4691
- ...record.flow,
4692
- status: DOCK_SURFACE_FLOW_STATUS.COMPLETED
4693
- };
4694
- closeSurface({ data, success: true }, surfaceId);
4695
- return true;
4696
- },
4697
- [closeSurface, refs]
4698
- );
4699
- const cancelSurfaceFlow = useCallback(
4700
- (flowId, data = null) => {
4701
- const surfaceId = refs.flowIndex.get(flowId);
4702
- if (!surfaceId) return false;
4703
- const record = refs.records.get(surfaceId);
4704
- if (record?.flow)
4705
- record.flow = {
4706
- ...record.flow,
4707
- status: DOCK_SURFACE_FLOW_STATUS.CANCELLED
4708
- };
4709
- closeSurface(
4710
- { cancelled: true, data, reason: "flow-cancelled", success: false },
4711
- surfaceId
4712
- );
4713
- return true;
4714
- },
4715
- [closeSurface, refs]
4716
- );
4717
- const getSurfaceFlow = useCallback(
4718
- (flowId) => {
4719
- const surfaceId = refs.flowIndex.get(flowId);
4720
- const session = surfaceId ? refs.records.get(surfaceId)?.flow : null;
4721
- if (!session) return null;
4722
- return {
4723
- cancel: (data = null) => cancelSurfaceFlow(flowId, data),
4724
- complete: (data = null) => completeSurfaceFlow(flowId, data),
4725
- flowId,
4726
- isOpen: session.status === DOCK_SURFACE_FLOW_STATUS.OPEN,
4727
- snapshot: session.snapshot,
4728
- status: session.status,
4729
- update: (snapshot) => updateSurfaceFlow(flowId, snapshot)
4730
- };
4731
- },
4732
- [cancelSurfaceFlow, completeSurfaceFlow, updateSurfaceFlow, refs]
4733
- );
4734
- const openSurfaceFlow = useCallback(
4735
- (input, flowInput = null, {
4736
- preserveUrl = false,
4737
- snapshot
4738
- } = {}) => {
4739
- const definition = createSurfaceFlowDefinition(input);
4740
- if (!definition) {
4741
- const error = createSurfaceError(
4742
- "DOCK_SURFACE_FLOW_INVALID_DEFINITION",
4743
- "Dock surface flow definition is invalid"
4744
- );
4745
- report("Dock surface flow definition", error);
4746
- return Promise.resolve({ success: false, error });
4747
- }
4748
- const existingSurfaceId = refs.flowIndex.get(definition.id);
4749
- if (definition.singleton && existingSurfaceId) {
4750
- return refs.records.get(existingSurfaceId)?.promise ?? Promise.resolve({
4751
- success: false,
4752
- error: createSurfaceError(
4753
- "DOCK_SURFACE_FLOW_ORPHANED",
4754
- "Dock surface flow is orphaned"
4755
- )
4756
- });
4757
- }
4758
- const flowSession = createSurfaceFlowSession(definition, {
4759
- input: flowInput,
4760
- snapshot
4761
- });
4762
- try {
4763
- const surfaceInput = definition.createSurface({
4764
- flowId: definition.id,
4765
- input: flowInput,
4766
- snapshot: flowSession?.snapshot
4767
- });
4768
- return openSurface(surfaceInput, { flowSession, preserveUrl });
4769
- } catch (error) {
4770
- report("Dock surface flow factory", error);
4771
- return Promise.resolve({ success: false, error });
4772
- }
4773
- },
4774
- [openSurface, refs]
4775
- );
4776
- const restoreSurfaceFlow = useCallback(
4777
- (input) => {
4778
- const definition = createSurfaceFlowDefinition(input);
4779
- const snapshot = getRestorableSurfaceFlowSnapshot(definition);
4780
- if (!definition || snapshot === void 0) return Promise.resolve(null);
4781
- return openSurfaceFlow(definition, null, { preserveUrl: true, snapshot });
4782
- },
4783
- [openSurfaceFlow]
4784
- );
4668
+ function toStyleSections(value) {
4669
+ return toObject(value);
4670
+ }
4671
+ function mergeStyleSection(baseStyle, stateStyle, hoverStyle, section) {
4785
4672
  return {
4786
- cancelSurfaceFlow,
4787
- completeSurfaceFlow,
4788
- getSurfaceFlow,
4789
- openSurfaceFlow,
4790
- restoreSurfaceFlow,
4791
- updateSurfaceFlow
4673
+ ...toObject(baseStyle?.[section]),
4674
+ ...toObject(stateStyle?.[section]),
4675
+ ...toObject(hoverStyle?.[section])
4792
4676
  };
4793
4677
  }
4794
- function useSurfaceLifecycleEffects({
4795
- clearChoreographyTimers,
4796
- closeAllSurfaces,
4797
- focusRestoreFrameRef,
4798
- refs,
4799
- runtimeScheduler
4800
- }) {
4801
- useEffect(() => {
4802
- if (!IS_BROWSER) return;
4803
- const handlePopState = () => {
4804
- const activeEntry = refs.stackRef.current[refs.stackRef.current.length - 1];
4805
- if (!activeEntry?.syncWithUrl && !activeEntry?.urlKey) return;
4806
- if (new URL(window.location.href).searchParams.get("surface") === getSurfaceUrlValue(activeEntry))
4807
- return;
4808
- closeAllSurfaces({
4809
- success: false,
4810
- cancelled: true,
4811
- reason: "browser-back"
4812
- });
4813
- };
4814
- window.addEventListener("popstate", handlePopState);
4815
- return () => window.removeEventListener("popstate", handlePopState);
4816
- }, [closeAllSurfaces, refs]);
4817
- useEffect(() => {
4818
- return () => {
4819
- clearChoreographyTimers();
4820
- if (focusRestoreFrameRef.current !== null) {
4821
- runtimeScheduler.cancel(focusRestoreFrameRef.current);
4822
- focusRestoreFrameRef.current = null;
4823
- }
4824
- const result = { cancelled: true, reason: "unmount", success: false };
4825
- refs.stackRef.current.forEach(
4826
- ({ id }) => releaseSurfaceResources({ refs, result, surfaceId: id })
4827
- );
4828
- refs.stackRef.current = [];
4829
- refs.payloads.clear();
4830
- refs.records.clear();
4831
- refs.flowIndex.clear();
4832
- };
4833
- }, [clearChoreographyTimers, focusRestoreFrameRef, refs, runtimeScheduler]);
4678
+ function resolveDockVisualStyle(style, {
4679
+ isActive = false,
4680
+ isHovered = false
4681
+ } = {}) {
4682
+ const baseStyle = toObject(style);
4683
+ const stateStyle = isActive ? toStyleSections(baseStyle.active) : toStyleSections(baseStyle.inactive);
4684
+ const hoverStyle = isHovered ? toStyleSections(baseStyle.hover) : {};
4685
+ const sections = {
4686
+ card: {},
4687
+ icon: {},
4688
+ title: {},
4689
+ description: {}
4690
+ };
4691
+ for (const section of DOCK_STYLE_SECTIONS) {
4692
+ sections[section] = mergeStyleSection(
4693
+ baseStyle,
4694
+ stateStyle,
4695
+ hoverStyle,
4696
+ section
4697
+ );
4698
+ }
4699
+ sections.card = { ...pickCardSurfaceStyle(baseStyle), ...sections.card };
4700
+ return {
4701
+ ...sections,
4702
+ scale: hoverStyle?.card?.scale ?? stateStyle?.card?.scale ?? baseStyle?.scale
4703
+ };
4834
4704
  }
4835
- var SurfaceFlowContext = createContext(null);
4836
- function SurfaceFlowProvider({
4837
- children,
4838
- value
4839
- }) {
4840
- return /* @__PURE__ */ jsx(SurfaceFlowContext, { value, children });
4705
+ function useDockTheme() {
4706
+ return useTheme(dockTheme);
4841
4707
  }
4842
- function useSurfaceFlow(input) {
4843
- const context = use(SurfaceFlowContext);
4844
- const definition = useMemo(() => createSurfaceFlowDefinition(input), [input]);
4845
- const restoredFlowIdsRef = useRef(/* @__PURE__ */ new Set());
4846
- const flowId = definition?.id ?? null;
4847
- const activeFlow = useMemo(() => {
4848
- if (!flowId) return null;
4849
- return context?.surfaceState?.surfaceStack?.map((s) => s.flow).find((f) => f?.flowId === flowId) ?? null;
4850
- }, [context?.surfaceState?.surfaceStack, flowId]);
4851
- useEffect(() => {
4852
- if (!definition || !context?.restoreSurfaceFlow || restoredFlowIdsRef.current.has(definition.id))
4853
- return;
4854
- restoredFlowIdsRef.current.add(definition.id);
4855
- void context.restoreSurfaceFlow(definition);
4856
- }, [context, definition]);
4857
- const open = useCallback(
4858
- (flowInput = null) => {
4859
- if (!definition || !context?.openSurfaceFlow) {
4860
- return Promise.resolve({
4861
- success: false,
4862
- error: createSurfaceError(
4863
- "DOCK_SURFACE_FLOW_UNAVAILABLE",
4864
- "Dock surface flow is unavailable"
4865
- )
4866
- });
4867
- }
4868
- return context.openSurfaceFlow(definition, flowInput);
4869
- },
4870
- [context, definition]
4708
+ function useDockActionClass() {
4709
+ const { slots } = useDockTheme();
4710
+ return useCallback(
4711
+ (options) => resolveDockActionClass(slots, options),
4712
+ [slots]
4713
+ );
4714
+ }
4715
+ function useDockRegistration(config, options) {
4716
+ useModuleRegistration("dock", config, options);
4717
+ }
4718
+ function useDockBanner(banner, options) {
4719
+ const position = options?.position;
4720
+ const size = options?.size;
4721
+ const opacity = options?.opacity;
4722
+ const repeat = options?.repeat;
4723
+ const targetPath = options?.path;
4724
+ const config = useMemo(() => {
4725
+ if (!banner) return null;
4726
+ if (typeof banner === "string") {
4727
+ return {
4728
+ bannerUrl: banner,
4729
+ ...position ? { bannerPosition: position } : {},
4730
+ ...size ? { bannerSize: size } : {},
4731
+ ...opacity !== void 0 ? { bannerOpacity: opacity } : {},
4732
+ ...repeat ? { bannerRepeat: repeat } : {},
4733
+ ...targetPath ? { path: targetPath } : {}
4734
+ };
4735
+ }
4736
+ if (typeof banner === "object") {
4737
+ return {
4738
+ bannerUrl: banner.url || banner.bannerUrl,
4739
+ bannerPosition: banner.position || banner.bannerPosition || position,
4740
+ bannerSize: banner.size || banner.bannerSize || size,
4741
+ bannerOpacity: banner.opacity ?? banner.bannerOpacity ?? opacity,
4742
+ bannerRepeat: banner.repeat || banner.bannerRepeat || repeat,
4743
+ ...targetPath ? { path: targetPath } : {}
4744
+ };
4745
+ }
4746
+ return null;
4747
+ }, [banner, position, size, opacity, repeat, targetPath]);
4748
+ useDockRegistration(config, options);
4749
+ }
4750
+ var EMPTY_DOCK_STATE_STORE = createStore(EMPTY_DOCK_STATE);
4751
+ function selectDockConfigState(state) {
4752
+ return {
4753
+ activeItem: state.activeItem,
4754
+ dockHeight: state.dockHeight,
4755
+ expanded: state.expanded,
4756
+ pathname: state.pathname
4757
+ };
4758
+ }
4759
+ function useDockConfig(configOrTitle, options) {
4760
+ const normalizedConfig = useMemo(() => {
4761
+ if (!configOrTitle) return null;
4762
+ if (typeof configOrTitle === "string") return { title: configOrTitle };
4763
+ return configOrTitle;
4764
+ }, [configOrTitle]);
4765
+ useDockRegistration(normalizedConfig, options);
4766
+ const dockContext = use(DockContext);
4767
+ const actions = dockContext?.actions;
4768
+ const state = useStore(
4769
+ dockContext?.store ?? EMPTY_DOCK_STATE_STORE,
4770
+ selectDockConfigState,
4771
+ shallowEqual
4871
4772
  );
4872
4773
  return useMemo(
4873
4774
  () => ({
4874
- activeFlow,
4875
- cancel: (result = null) => flowId ? context?.cancelSurfaceFlow?.(flowId, result) : void 0,
4876
- complete: (result = null) => flowId ? context?.completeSurfaceFlow?.(flowId, result) : void 0,
4877
- flowId,
4878
- isOpen: activeFlow?.status === DOCK_SURFACE_FLOW_STATUS.OPEN,
4879
- open,
4880
- snapshot: activeFlow?.snapshot ?? definition?.initialSnapshot ?? null,
4881
- update: (snapshot) => flowId ? context?.updateSurfaceFlow?.(flowId, snapshot) : void 0
4775
+ activeItem: state.activeItem,
4776
+ clearHud: actions?.clearHud ?? (() => {
4777
+ }),
4778
+ closeAllSurfaces: actions?.closeAllSurfaces ?? (() => {
4779
+ }),
4780
+ closeSurface: actions?.closeSurface ?? (() => {
4781
+ }),
4782
+ expanded: state.expanded,
4783
+ dockHeight: state.dockHeight,
4784
+ navigate: actions?.navigate ?? (async () => false),
4785
+ openSurface: actions?.openSurface ?? (() => void 0),
4786
+ pathname: state.pathname,
4787
+ setExpanded: actions?.setExpanded ?? (() => {
4788
+ }),
4789
+ setHud: actions?.setHud ?? (() => {
4790
+ }),
4791
+ setSearchQuery: actions?.setSearchQuery ?? (() => {
4792
+ })
4882
4793
  }),
4883
- [activeFlow, context, definition?.initialSnapshot, flowId, open]
4794
+ [actions, state]
4795
+ );
4796
+ }
4797
+ function useDockDimensions() {
4798
+ const { dockHeight } = useDockHeight();
4799
+ const activeItem = useDockSelector((state) => state.activeItem);
4800
+ return {
4801
+ height: dockHeight,
4802
+ width: activeItem?.width ?? null,
4803
+ isSurface: Boolean(activeItem?.isSurface)
4804
+ };
4805
+ }
4806
+ function useSurfaceReturn() {
4807
+ const pathname = usePathname();
4808
+ const { continuity } = useDockActions();
4809
+ const dockReturnHandoffs = useDockSelector(
4810
+ (state) => state.dockReturnHandoffs
4811
+ );
4812
+ const entries = useMemo(
4813
+ () => (dockReturnHandoffs || []).filter(
4814
+ (handoff) => isSamePath(handoff.path, pathname)
4815
+ ),
4816
+ [dockReturnHandoffs, pathname]
4817
+ );
4818
+ const consume = useCallback(
4819
+ (handoffId = null) => continuity.consumeReturn(pathname, handoffId),
4820
+ [continuity, pathname]
4821
+ );
4822
+ return useMemo(
4823
+ () => ({ consume, entries, peek: () => entries[0] || null }),
4824
+ [consume, entries]
4825
+ );
4826
+ }
4827
+
4828
+ // src/modules/dock/runtime/pipeline.ts
4829
+ function isSameDockItem(item, candidate) {
4830
+ return Boolean(
4831
+ item?.path && item.path === candidate?.path || item?.name && item.name === candidate?.name
4832
+ );
4833
+ }
4834
+ var isSameItem = isSameDockItem;
4835
+ function getItemKey(link, index = 0) {
4836
+ const identity = link?.id ?? link?.path ?? link?.name ?? link?.type;
4837
+ return `dock-card:${identity == null ? `slot-${index}` : String(identity)}`;
4838
+ }
4839
+ function resolveDockHeaderKey({
4840
+ link,
4841
+ description = "",
4842
+ showVideoIcon = false
4843
+ }) {
4844
+ const statusPart = link?.isStatus ? `status:${link.statusType || link.type || "status"}` : "standard";
4845
+ const identityPart = link?.path || link?.name || link?.id || "item";
4846
+ const titlePart = link?.title || link?.name || "";
4847
+ const descPart = description || "";
4848
+ const iconPart = showVideoIcon ? "video" : link?.icon || "no-icon";
4849
+ return `${statusPart}:${identityPart}:${iconPart}:${titlePart}:${descPart}`;
4850
+ }
4851
+ function findDockItemIndex(dockItems, activeItem, pathname) {
4852
+ const normalizedPathname = normalizePath(pathname);
4853
+ const selectedDataSourceIndex = dockItems.findIndex(
4854
+ (item) => item.isDataSource && item.isSelected
4855
+ );
4856
+ if (selectedDataSourceIndex !== -1) return selectedDataSourceIndex;
4857
+ if (activeItem) {
4858
+ const matchedActiveIndex = dockItems.findIndex(
4859
+ (item) => item.path && isSamePath(item.path, activeItem.path) || item.name && item.name === activeItem.name
4860
+ );
4861
+ if (matchedActiveIndex !== -1) return matchedActiveIndex;
4862
+ }
4863
+ return dockItems.findIndex(
4864
+ (item) => isSamePath(item.path, normalizedPathname) || item.targetPath && isSamePath(item.targetPath, normalizedPathname)
4884
4865
  );
4885
4866
  }
4886
- var DockSurfaceHeaderRadiusContext = createContext(
4887
- null
4888
- );
4889
- function useDockSurfaceHeaderButtonModel({
4890
- children,
4891
- className = "",
4892
- disabled = false,
4893
- onClick,
4894
- ariaLabel,
4895
- icon = null
4867
+ function resolveActiveIndex({
4868
+ dockItems,
4869
+ activeItem,
4870
+ pathname
4896
4871
  }) {
4897
- const contextRadius = use(DockSurfaceHeaderRadiusContext);
4898
- return { contextRadius };
4872
+ return Math.max(0, findDockItemIndex(dockItems, activeItem, pathname));
4899
4873
  }
4900
- function useDockSurfaceControlsModel({
4901
- activeItem = null,
4902
- hasExtensions = false,
4903
- onClose = null,
4904
- onBack = null,
4905
- closeLabel = null,
4906
- backLabel = null,
4907
- className = "",
4908
- surfacePhase: propSurfacePhase
4909
- }) {
4910
- const phase = propSurfacePhase ?? activeItem?.surfacePhase;
4911
- const isBodyVisible = phase ? phase === DOCK_SURFACE_PHASE.EXPANDING_BODY || phase === DOCK_SURFACE_PHASE.OPEN : true;
4912
- const actionStore = use(SurfaceExtensionsContext)?.headerActions ?? null;
4913
- const surfaceId = activeItem?.surfaceId || "global";
4914
- const subscribeAction = useCallback(
4915
- (onStoreChange) => actionStore ? actionStore.subscribe(onStoreChange) : () => {
4916
- },
4917
- [actionStore]
4918
- );
4919
- const getActionSnapshot = useCallback(
4920
- () => actionStore ? actionStore.getAction(surfaceId) : null,
4921
- [actionStore, surfaceId]
4874
+ function shouldKeepAncestorItem(item, activePath) {
4875
+ const policy = item?.keepWhenDescendant;
4876
+ if (typeof policy === "function") {
4877
+ try {
4878
+ return Boolean(policy(activePath, item));
4879
+ } catch {
4880
+ return false;
4881
+ }
4882
+ }
4883
+ return policy === true;
4884
+ }
4885
+ function isAncestorPath(item, activePath) {
4886
+ const candidatePath = item?.path;
4887
+ if (!candidatePath || candidatePath === "/" || candidatePath === activePath) {
4888
+ return false;
4889
+ }
4890
+ return isPathPrefix(candidatePath, activePath) && !shouldKeepAncestorItem(item, activePath);
4891
+ }
4892
+ function removeAncestorDuplicates(items = []) {
4893
+ if (!Array.isArray(items) || items.length <= 1) return items;
4894
+ const activePath = items[0]?.path;
4895
+ if (!activePath) return items;
4896
+ return items.filter(
4897
+ (item, index) => index === 0 || !isAncestorPath(item, activePath)
4922
4898
  );
4923
- const dynamicAction = useSyncExternalStore(
4924
- subscribeAction,
4925
- getActionSnapshot,
4926
- () => null
4899
+ }
4900
+ function replaceActiveItem(items, activeIndex, activeItem) {
4901
+ if (activeIndex === -1 || !activeItem) return items;
4902
+ const nextItems = [...items];
4903
+ nextItems[activeIndex] = activeItem;
4904
+ return nextItems;
4905
+ }
4906
+ function removeInactiveLoadingItems(items = [], activeItem = null) {
4907
+ if (!Array.isArray(items) || items.length === 0) return items;
4908
+ return items.filter(
4909
+ (item) => !item?.isLoading || isSameItem(item, activeItem)
4927
4910
  );
4928
- return { isBodyVisible, dynamicAction };
4929
4911
  }
4930
- function SurfaceExtensionsProvider({
4931
- children
4932
- }) {
4933
- const [value] = useState(() => ({
4934
- extensions: new SurfaceExtensionsStore(),
4935
- headerActions: new SurfaceHeaderActionStore()
4936
- }));
4937
- return /* @__PURE__ */ jsx(SurfaceExtensionsContext, { value, children });
4912
+ function reorderItemsWithActiveFirst(items, activeIndex) {
4913
+ if (activeIndex === -1) return items;
4914
+ const active = items[activeIndex];
4915
+ const rest = [
4916
+ ...items.slice(0, activeIndex),
4917
+ ...items.slice(activeIndex + 1)
4918
+ ];
4919
+ rest.sort((a, b) => a.path === "/" ? 1 : b.path === "/" ? -1 : 0);
4920
+ return [active, ...rest];
4938
4921
  }
4939
- function useDockSurfaceExtension({
4940
- align = "left",
4941
- children,
4942
- className = "",
4943
- id,
4944
- order = 0,
4945
- unstyled = false
4922
+ function shouldRenderInlineAction({
4923
+ action,
4924
+ isLoading,
4925
+ isOverlay,
4926
+ path
4927
+ }, pathname) {
4928
+ return Boolean(action) && !isLoading && (isOverlay || !path || isInlineActionPathMatch(path, pathname));
4929
+ }
4930
+ function getIsItemActive(link, activeItem) {
4931
+ if (!link || !activeItem) return false;
4932
+ if (link.path && activeItem.path && isSamePath(link.path, activeItem.path))
4933
+ return true;
4934
+ if (link.targetPath && activeItem.targetPath && isSamePath(link.targetPath, activeItem.targetPath))
4935
+ return true;
4936
+ return Boolean(link.name && activeItem.name && link.name === activeItem.name);
4937
+ }
4938
+ function getViewportMaxHeight() {
4939
+ if (typeof window === "undefined") return Infinity;
4940
+ return window.innerHeight - VIEWPORT_MARGIN;
4941
+ }
4942
+ function getContainerHeight({
4943
+ cardContentHeight,
4944
+ isHud = false
4946
4945
  }) {
4947
- const store = use(SurfaceExtensionsContext)?.extensions ?? null;
4948
- const surfaceId = useSurfaceId();
4949
- const generatedId = useId();
4950
- const effectiveId = id || `dock-surface-ext-${generatedId}`;
4951
- useEffect(() => {
4952
- if (!store) return;
4953
- store.setExtension(surfaceId, {
4954
- align,
4955
- className,
4956
- content: children,
4957
- id: effectiveId,
4958
- order,
4959
- unstyled
4946
+ const chromeHeight = DOCK_CARD_LAYOUT.chromeHeight;
4947
+ const minCardHeight = isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight;
4948
+ const numericContentHeight = Number(cardContentHeight);
4949
+ const nextCardHeight = Math.max(
4950
+ minCardHeight,
4951
+ (Number.isFinite(numericContentHeight) ? numericContentHeight : 0) + chromeHeight
4952
+ );
4953
+ return Math.min(nextCardHeight, getViewportMaxHeight());
4954
+ }
4955
+ function getDockCardWidth({
4956
+ width
4957
+ } = {}) {
4958
+ if (typeof window === "undefined") return width ? Number(width) || 460 : 460;
4959
+ const windowWidth = window.innerWidth;
4960
+ const isDesktop = windowWidth >= 640;
4961
+ const targetWidth = Number(width);
4962
+ if (Number.isFinite(targetWidth) && targetWidth > 0) {
4963
+ return Math.min(
4964
+ targetWidth,
4965
+ Math.max(windowWidth - (isDesktop ? 32 : 16), 0)
4966
+ );
4967
+ }
4968
+ return Math.min(460, Math.max(windowWidth - 16, 0));
4969
+ }
4970
+ function getObservedHeight(entry, element) {
4971
+ const rawBorderBoxSize = entry?.borderBoxSize;
4972
+ const borderBoxSize = Array.isArray(rawBorderBoxSize) ? rawBorderBoxSize[0] : rawBorderBoxSize;
4973
+ if (borderBoxSize?.blockSize != null) {
4974
+ return Math.round(borderBoxSize.blockSize);
4975
+ }
4976
+ if (entry?.contentRect?.height != null) {
4977
+ return Math.round(entry.contentRect.height);
4978
+ }
4979
+ return Math.round(element?.offsetHeight || 0);
4980
+ }
4981
+ function hasMeaningfulHeightChange(previousHeight, nextHeight) {
4982
+ return Math.abs(Math.round(nextHeight) - Math.round(previousHeight)) > HEIGHT_EPSILON;
4983
+ }
4984
+ function useElementHeight(onHeightChange, elementRef, shouldMeasure, dependencyKey = null) {
4985
+ const lastHeightRef = useRef(0);
4986
+ const rafRef = useRef(null);
4987
+ const callbackRef = useRef(onHeightChange);
4988
+ useIsomorphicLayoutEffect(() => {
4989
+ callbackRef.current = onHeightChange;
4990
+ }, [onHeightChange]);
4991
+ useIsomorphicLayoutEffect(() => {
4992
+ if (rafRef.current !== null) {
4993
+ cancelAnimationFrame(rafRef.current);
4994
+ rafRef.current = null;
4995
+ }
4996
+ lastHeightRef.current = -1;
4997
+ if (!callbackRef.current) return;
4998
+ if (!shouldMeasure) {
4999
+ if (hasMeaningfulHeightChange(lastHeightRef.current, 0)) {
5000
+ lastHeightRef.current = 0;
5001
+ callbackRef.current(0);
5002
+ }
5003
+ return;
5004
+ }
5005
+ const element = elementRef?.current;
5006
+ if (!element) return;
5007
+ function publishHeight(nextHeight) {
5008
+ if (!hasMeaningfulHeightChange(lastHeightRef.current, nextHeight)) return;
5009
+ lastHeightRef.current = nextHeight;
5010
+ callbackRef.current?.(nextHeight);
5011
+ }
5012
+ let pendingHeight = null;
5013
+ function flushPendingHeight() {
5014
+ rafRef.current = null;
5015
+ if (pendingHeight == null) return;
5016
+ const heightToPublish = pendingHeight;
5017
+ pendingHeight = null;
5018
+ publishHeight(heightToPublish);
5019
+ }
5020
+ function scheduleMeasurement(nextHeight) {
5021
+ pendingHeight = nextHeight;
5022
+ if (rafRef.current !== null) return;
5023
+ rafRef.current = requestAnimationFrame(flushPendingHeight);
5024
+ }
5025
+ publishHeight(element.offsetHeight || 0);
5026
+ const observer = new ResizeObserver((entries) => {
5027
+ for (const entry of entries) {
5028
+ scheduleMeasurement(getObservedHeight(entry, element));
5029
+ }
4960
5030
  });
4961
- });
4962
- useEffect(() => {
5031
+ observer.observe(element);
5032
+ const handlePageShow = () => scheduleMeasurement(element.offsetHeight || 0);
5033
+ const handleVisibilityChange = () => {
5034
+ if (document.visibilityState === "visible") handlePageShow();
5035
+ };
5036
+ window.addEventListener("pageshow", handlePageShow);
5037
+ document.addEventListener("visibilitychange", handleVisibilityChange);
4963
5038
  return () => {
4964
- if (store) store.removeExtension(surfaceId, effectiveId);
5039
+ observer.disconnect();
5040
+ window.removeEventListener("pageshow", handlePageShow);
5041
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
5042
+ if (rafRef.current !== null) {
5043
+ cancelAnimationFrame(rafRef.current);
5044
+ rafRef.current = null;
5045
+ }
4965
5046
  };
4966
- }, [store, surfaceId, effectiveId]);
5047
+ }, [dependencyKey, elementRef, shouldMeasure]);
4967
5048
  }
4968
- function useIsSurfaceExtensionsVisible(activeItem) {
4969
- const store = use(SurfaceExtensionsContext)?.extensions ?? null;
4970
- const surfaceId = activeItem?.surfaceId || "global";
4971
- const isSurface = Boolean(activeItem?.isSurface);
4972
- const phase = activeItem?.surfacePhase;
4973
- const isBodyVisible = phase === DOCK_SURFACE_PHASE.EXPANDING_BODY || phase === DOCK_SURFACE_PHASE.OPEN;
4974
- const subscribe2 = useCallback(
4975
- (onStoreChange) => store ? store.subscribe(onStoreChange) : () => {
4976
- },
4977
- [store]
4978
- );
4979
- const getSnapshot2 = useCallback(
4980
- () => store ? store.getExtensionsForSurface(surfaceId).length : 0,
4981
- [store, surfaceId]
4982
- );
4983
- const dynamicCount = useSyncExternalStore(subscribe2, getSnapshot2, () => 0);
4984
- const descriptorCount = useMemo(() => {
4985
- const raw = activeItem?.surfaceExtensions || activeItem?.extensions || [];
4986
- return Array.isArray(raw) ? raw.length : 0;
4987
- }, [activeItem?.surfaceExtensions, activeItem?.extensions]);
4988
- return Boolean(
4989
- isSurface && isBodyVisible && (dynamicCount > 0 || descriptorCount > 0)
5049
+ function publishDockHeightCssVar(heightPx) {
5050
+ if (typeof document === "undefined") return;
5051
+ document.documentElement.style.setProperty(
5052
+ "--dock-h",
5053
+ `${Math.round(heightPx)}px`
4990
5054
  );
4991
5055
  }
4992
- function useDockSurfaceExtensionsBarModel({
4993
- activeItem
5056
+ function useDockHeightController({
5057
+ contentKey = null,
5058
+ isHud = false,
5059
+ setDockHeight,
5060
+ surfacePhase = DOCK_SURFACE_PHASE.IDLE
4994
5061
  }) {
4995
- const store = use(SurfaceExtensionsContext)?.extensions ?? null;
4996
- const surfaceId = activeItem?.surfaceId || "global";
4997
- const subscribe2 = useCallback(
4998
- (onStoreChange) => store ? store.subscribe(onStoreChange) : () => {
4999
- },
5000
- [store]
5001
- );
5002
- const getSnapshot2 = useCallback(
5003
- () => store ? store.getExtensionsForSurface(surfaceId) : [],
5004
- [store, surfaceId]
5062
+ const [containerHeight, setContainerHeight] = useState(
5063
+ isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight
5005
5064
  );
5006
- const dynamicExtensions = useSyncExternalStore(
5007
- subscribe2,
5008
- getSnapshot2,
5009
- () => []
5065
+ const heightRef = useRef({ content: 0 });
5066
+ const rafRef = useRef(null);
5067
+ const isHudRef = useRef(isHud);
5068
+ const surfacePhaseRef = useRef(surfacePhase);
5069
+ const lastAppliedContainerHeightRef = useRef(
5070
+ isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight
5010
5071
  );
5011
- const descriptorExtensions = useMemo(() => {
5012
- const raw = activeItem?.surfaceExtensions || activeItem?.extensions || [];
5013
- return Array.isArray(raw) ? raw.map(normalizeSurfaceExtension).filter((ext) => ext !== null) : [];
5014
- }, [activeItem?.surfaceExtensions, activeItem?.extensions]);
5015
- const allExtensions = useMemo(() => {
5016
- const map = /* @__PURE__ */ new Map();
5017
- descriptorExtensions.forEach((ext) => map.set(ext.id, ext));
5018
- dynamicExtensions.forEach((ext) => map.set(ext.id, ext));
5019
- return Array.from(map.values()).sort((a, b) => a.order - b.order);
5020
- }, [descriptorExtensions, dynamicExtensions]);
5021
- return { allExtensions };
5022
- }
5023
- var DRAG_RANGE = [...DOCK_SURFACE_DRAG_INTERPOLATION.DRAG_RANGE];
5024
- var OPACITY_RANGE = [...DOCK_SURFACE_DRAG_INTERPOLATION.OPACITY_RANGE];
5025
- var SCALE_RANGE = [...DOCK_SURFACE_DRAG_INTERPOLATION.SCALE_RANGE];
5026
- function useDockSurfaceShellModel({
5027
- ref,
5028
- title = "",
5029
- onClose = null,
5030
- onBack = null,
5031
- allowSwipeDismiss = true,
5032
- closeLabel = "Close surface",
5033
- backLabel = "Previous step",
5034
- showControls = false,
5035
- className = "",
5036
- contentClassName = "",
5037
- children,
5038
- onAnimationComplete = null,
5039
- isActive = true,
5040
- surfaceId = null,
5041
- surfacePhase = DOCK_SURFACE_PHASE.OPEN,
5042
- surfaceWidth = null
5043
- }) {
5044
- const surfaceElementRef = useRef(null);
5045
- const setSurfaceElementRef = useCallback(
5046
- (node) => {
5047
- surfaceElementRef.current = node;
5048
- if (typeof ref === "function") ref(node);
5049
- else if (ref) ref.current = node;
5050
- },
5051
- [ref]
5072
+ const lastAppliedSpacerHeightRef = useRef(
5073
+ (isHud ? DOCK_CARD_LAYOUT.hudHeight : DOCK_CARD_LAYOUT.baseHeight) + DOCK_HEIGHT_BUFFER
5052
5074
  );
5053
- const isFullyOpen = surfacePhase === DOCK_SURFACE_PHASE.OPEN;
5054
- const titleId = surfaceId == null ? void 0 : `dock-surface-title-${surfaceId}`;
5055
- const dragY = useMotionValue(0);
5056
- const dragOpacity = useTransform(dragY, DRAG_RANGE, OPACITY_RANGE);
5057
- const dragScale = useTransform(dragY, DRAG_RANGE, SCALE_RANGE);
5058
- useDockFocusTrap({
5059
- containerRef: surfaceElementRef,
5060
- enabled: isActive && isFullyOpen,
5061
- onDismiss: typeof onClose === "function" ? onClose : null
5062
- });
5063
- const handleDragEnd = (_event, info) => {
5064
- if (!isActive || !allowSwipeDismiss || typeof onClose !== "function" || !isFullyOpen)
5065
- return;
5066
- if (info.offset.y > DOCK_SURFACE_DRAG_THRESHOLDS.DISMISS_OFFSET_Y || info.velocity.y > DOCK_SURFACE_DRAG_THRESHOLDS.DISMISS_VELOCITY_Y) {
5067
- onClose();
5075
+ useIsomorphicLayoutEffect(() => {
5076
+ isHudRef.current = isHud;
5077
+ surfacePhaseRef.current = surfacePhase;
5078
+ }, [isHud, surfacePhase]);
5079
+ const applyHeight = useCallback(() => {
5080
+ if (rafRef.current !== null) {
5081
+ cancelAnimationFrame(rafRef.current);
5082
+ rafRef.current = null;
5068
5083
  }
5069
- };
5070
- const dragControls = useDragControls();
5071
- const canDragDismiss = Boolean(
5072
- isActive && allowSwipeDismiss && typeof onClose === "function" && isFullyOpen
5073
- );
5074
- const handleSurfacePointerDown = useCallback(
5075
- (event) => {
5076
- if (!canDragDismiss) return;
5077
- const target = event.target;
5078
- if (target?.closest?.(
5079
- 'input, textarea, select, [role="slider"], [data-no-surface-drag], [data-lenis-prevent]'
5080
- )) {
5081
- return;
5082
- }
5083
- dragControls.start(event);
5084
+ const { content } = heightRef.current;
5085
+ const currentPhase = surfacePhaseRef.current;
5086
+ const isLockedToBaseHeight = currentPhase === DOCK_SURFACE_PHASE.DISMISSING_ACTION || currentPhase === DOCK_SURFACE_PHASE.COLLAPSING_BODY || currentPhase === DOCK_SURFACE_PHASE.RESTORING_HEADER;
5087
+ const computedContentHeight = isLockedToBaseHeight ? 0 : content;
5088
+ const height = getContainerHeight({
5089
+ cardContentHeight: computedContentHeight,
5090
+ isHud: isHudRef.current
5091
+ });
5092
+ const totalSpacerHeight = height + DOCK_HEIGHT_BUFFER;
5093
+ if (Math.abs(height - lastAppliedContainerHeightRef.current) > 0.5) {
5094
+ lastAppliedContainerHeightRef.current = height;
5095
+ setContainerHeight(height);
5096
+ }
5097
+ if (Math.abs(totalSpacerHeight - lastAppliedSpacerHeightRef.current) > 0.5) {
5098
+ lastAppliedSpacerHeightRef.current = totalSpacerHeight;
5099
+ publishDockHeightCssVar(totalSpacerHeight);
5100
+ setDockHeight(totalSpacerHeight);
5101
+ }
5102
+ }, [setDockHeight]);
5103
+ const handleContentHeightChange = useCallback(
5104
+ (height) => {
5105
+ const numericHeight = Number(height);
5106
+ heightRef.current.content = Number.isFinite(numericHeight) ? Math.max(0, numericHeight) : 0;
5107
+ applyHeight();
5084
5108
  },
5085
- [canDragDismiss, dragControls]
5086
- );
5087
- const isBodyVisible = surfacePhase === DOCK_SURFACE_PHASE.EXPANDING_BODY || surfacePhase === DOCK_SURFACE_PHASE.OPEN;
5088
- const resolvedSurfaceId = surfaceId || "active";
5089
- const bodyVariants = useMemo(
5090
- () => ({
5091
- hidden: dockSurfaceBodyRole.hidden,
5092
- visible: {
5093
- ...dockSurfaceBodyRole.visible,
5094
- transition: surfacePhase === DOCK_SURFACE_PHASE.EXPANDING_BODY ? DOCK_SURFACE_BODY_ENTER_TRANSITION : DOCK_SURFACE_BODY_STEP_TRANSITION
5095
- },
5096
- exit: {
5097
- ...dockSurfaceBodyRole.exit,
5098
- transition: DOCK_SURFACE_BODY_EXIT_TRANSITION
5099
- }
5100
- }),
5101
- [surfacePhase]
5102
- );
5103
- const surfaceItemValue = useMemo(
5104
- () => ({ id: resolvedSurfaceId, width: surfaceWidth }),
5105
- [resolvedSurfaceId, surfaceWidth]
5109
+ [applyHeight]
5106
5110
  );
5107
- return {
5108
- setSurfaceElementRef,
5109
- titleId,
5110
- dragY,
5111
- dragOpacity,
5112
- dragScale,
5113
- handleDragEnd,
5114
- dragControls,
5115
- canDragDismiss,
5116
- handleSurfacePointerDown,
5117
- isBodyVisible,
5118
- bodyVariants,
5119
- surfaceItemValue
5120
- };
5111
+ useEffect(() => {
5112
+ return () => {
5113
+ if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
5114
+ };
5115
+ }, []);
5116
+ useIsomorphicLayoutEffect(() => applyHeight(), [applyHeight, contentKey]);
5117
+ useIsomorphicLayoutEffect(() => applyHeight(), [applyHeight, surfacePhase]);
5118
+ useIsomorphicLayoutEffect(() => applyHeight(), [applyHeight, isHud]);
5119
+ return { containerHeight, handleContentHeightChange };
5120
+ }
5121
+ function isSurfaceShapePhase(phase) {
5122
+ return phase === DOCK_SURFACE_PHASE.EXPANDING_BODY || phase === DOCK_SURFACE_PHASE.OPEN || phase === DOCK_SURFACE_PHASE.CLOSING_ANTICIPATION;
5123
+ }
5124
+ function getTargetItemWidth(activeItem) {
5125
+ if (!activeItem?.isSurface || isSurfaceShapePhase(activeItem.surfacePhase)) {
5126
+ return activeItem?.width;
5127
+ }
5128
+ return activeItem.restingWidth;
5129
+ }
5130
+ function useDockViewport(activeItem = null) {
5131
+ const targetWidth = getTargetItemWidth(activeItem);
5132
+ const [, setViewportRevision] = useState(0);
5133
+ const [portalTarget, setPortalTarget] = useState(null);
5134
+ const stackWidth = getDockCardWidth({ width: targetWidth });
5135
+ useIsomorphicLayoutEffect(() => {
5136
+ if (typeof document !== "undefined") setPortalTarget(document.body);
5137
+ }, []);
5138
+ useEffect(() => {
5139
+ if (typeof window === "undefined") return void 0;
5140
+ let resizeFrameId = null;
5141
+ const handleResize = () => {
5142
+ if (resizeFrameId !== null) return;
5143
+ resizeFrameId = window.requestAnimationFrame(() => {
5144
+ resizeFrameId = null;
5145
+ setViewportRevision((revision) => revision + 1);
5146
+ });
5147
+ };
5148
+ window.addEventListener("resize", handleResize, { passive: true });
5149
+ return () => {
5150
+ window.removeEventListener("resize", handleResize);
5151
+ if (resizeFrameId !== null) window.cancelAnimationFrame(resizeFrameId);
5152
+ };
5153
+ }, []);
5154
+ return { portalTarget, stackWidth };
5155
+ }
5156
+ function getItemMeasurementKey({
5157
+ link,
5158
+ expanded,
5159
+ isHud = false
5160
+ }) {
5161
+ const state = isHud ? "hud" : link.isLoading ? "loading" : link.isSurface ? `surface-${link.surfacePhase ?? "open"}` : "standard";
5162
+ return `${link.path || link.name || "item"}:${state}:${expanded ? "expanded" : "collapsed"}`;
5163
+ }
5164
+ function getRouteMeasurementKey(pathname, key) {
5165
+ return `${pathname || ""}:${key}`;
5121
5166
  }
5122
5167
  function createDockGuardRegistry() {
5123
5168
  const guards = /* @__PURE__ */ new Map();
@@ -8687,7 +8732,7 @@ function useDockOperationState() {
8687
8732
  );
8688
8733
  const operationIdRef = useRef(0);
8689
8734
  const operationStateRef = useRef(operationState);
8690
- useInsertionEffect(() => {
8735
+ useIsomorphicLayoutEffect(() => {
8691
8736
  operationStateRef.current = operationState;
8692
8737
  }, [operationState]);
8693
8738
  const start = useCallback((input = {}) => {
@@ -8752,13 +8797,10 @@ function useDockOperationState() {
8752
8797
  [operationState.entries]
8753
8798
  );
8754
8799
  const operationHud = useMemo(
8755
- () => (
8756
- // eslint-disable-next-line react-hooks/refs
8757
- createDockOperationHud(activeOperation, {
8758
- onCancel: cancel,
8759
- pendingCount
8760
- })
8761
- ),
8800
+ () => createDockOperationHud(activeOperation, {
8801
+ onCancel: cancel,
8802
+ pendingCount
8803
+ }),
8762
8804
  [activeOperation, cancel, pendingCount]
8763
8805
  );
8764
8806
  return { activeOperation, operationActions, operationHud, operationState };
@@ -11149,7 +11191,7 @@ function useSurfaceStack({
11149
11191
  const surfacePhaseRef = useRef(DOCK_SURFACE_PHASE.IDLE);
11150
11192
  const transitionRunnerRef = useRef(null);
11151
11193
  const onSurfaceFlowSettledRef = useRef(onSurfaceFlowSettled);
11152
- useInsertionEffect(() => {
11194
+ useIsomorphicLayoutEffect(() => {
11153
11195
  onSurfaceFlowSettledRef.current = onSurfaceFlowSettled;
11154
11196
  }, [onSurfaceFlowSettled]);
11155
11197
  const clearChoreographyTimers = useCallback(() => {
@@ -12045,7 +12087,7 @@ function useDockPage(slice, page) {
12045
12087
  useDockGuard(slice?.guard ?? NO_GUARD);
12046
12088
  useDockContextActions(slice?.actions ?? NO_ACTIONS);
12047
12089
  const surfacesRef = useRef(slice?.surfaces);
12048
- useInsertionEffect(() => {
12090
+ useIsomorphicLayoutEffect(() => {
12049
12091
  surfacesRef.current = slice?.surfaces;
12050
12092
  }, [slice?.surfaces]);
12051
12093
  return useMemo(
@@ -12086,6 +12128,6 @@ var dockModule = defineModule({
12086
12128
  }
12087
12129
  });
12088
12130
 
12089
- export { DOCK_EVENTS, DOCK_FADE_TRANSITION, DOCK_HUD_PRIORITY, DOCK_HUD_RENDER_MODE, DOCK_HUD_VARIANT, Dock, DockCardBanner, DockCardHeader, DockDescription, DockIcon, DockProvider, DockSurfaceAction, DockSurfaceExtension, DockSurfaceHeaderButton, DockTitle, ErrorActions, GuardActions, createDockGuardRegistry, createErrorStatus, createGuardStatus, createInlineSurfaceEntry, createSurfaceEntryDefinition, createSurfaceFlowBuilder, defineBreadcrumb, defineDockAction, defineHud, defineStepSurface, defineSurface, dockFadeVariants, dockListItemVariants, dockModule, dockTheme, isValidBannerUrl, textCrossfadeVariants, useDock, useDockActionClass, useDockActions, useDockBanner, useDockConfig, useDockContextActions, useDockDimensions, useDockGuard, useDockHeight, useDockHud, useDockRegistration, useDockSelector, useDockState, useHud, useOptionalDockActions, useOptionalDockState, useSurface, useSurfaceAction, useSurfaceDimensions, useSurfaceFlow, useSurfaceHeader, useSurfaceId, useSurfaceReturn, useSurfaceStep };
12131
+ export { DOCK_EVENTS, DOCK_FADE_TRANSITION, DOCK_HUD_PRIORITY, DOCK_HUD_RENDER_MODE, DOCK_HUD_VARIANT, Dock, DockCardBanner, DockCardHeader, DockDescription, DockIcon, DockProvider, DockSurfaceAction, DockSurfaceExtension, DockSurfaceHeaderButton, DockTitle, ErrorActions, GuardActions, createDockGuardRegistry, createErrorStatus, createGuardStatus, createInlineSurfaceEntry, createSurfaceEntryDefinition, createSurfaceFlowBuilder, defineBreadcrumb, defineDockAction, defineHud, defineStepSurface, defineSurface, dockFadeVariants, dockListItemVariants, dockModule, dockTheme, isValidBannerUrl, textCrossfadeVariants, useDock, useDockActionClass, useDockActions, useDockBanner, useDockConfig, useDockContextActions, useDockDimensions, useDockGuard, useDockHeight, useDockHud, useDockRegistration, useDockSelector, useDockState, useDockTheme, useHud, useOptionalDockActions, useOptionalDockState, useSurface, useSurfaceAction, useSurfaceDimensions, useSurfaceFlow, useSurfaceHeader, useSurfaceId, useSurfaceReturn, useSurfaceStep };
12090
12132
  //# sourceMappingURL=index.js.map
12091
12133
  //# sourceMappingURL=index.js.map