@flamingo-stack/openframe-frontend-core 0.0.690 → 0.0.691-2504.7650.1

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 (149) hide show
  1. package/dist/{chunk-22RSJ57Z.js → chunk-2YVTGPM7.js} +2 -2
  2. package/dist/{chunk-V5NOURSR.js → chunk-4HZXOQRM.js} +5 -5
  3. package/dist/{chunk-MDYTAKJ5.cjs → chunk-4N2JOPAQ.cjs} +22 -22
  4. package/dist/{chunk-MDYTAKJ5.cjs.map → chunk-4N2JOPAQ.cjs.map} +1 -1
  5. package/dist/{chunk-QNWQQ4RT.cjs → chunk-53O6PQ3F.cjs} +7 -7
  6. package/dist/{chunk-QNWQQ4RT.cjs.map → chunk-53O6PQ3F.cjs.map} +1 -1
  7. package/dist/{chunk-X4WBGDNW.cjs → chunk-5N2ZSB42.cjs} +18 -18
  8. package/dist/{chunk-X4WBGDNW.cjs.map → chunk-5N2ZSB42.cjs.map} +1 -1
  9. package/dist/{chunk-XRSJER2B.js → chunk-6Z26ZMR4.js} +4 -4
  10. package/dist/{chunk-HLEXYTOE.js → chunk-7EPGJOKN.js} +4 -4
  11. package/dist/{chunk-DOUG4UEZ.js → chunk-7WYWPELF.js} +11 -11
  12. package/dist/{chunk-ZBQZJAC7.cjs → chunk-BXAGR3PO.cjs} +7 -7
  13. package/dist/chunk-BXAGR3PO.cjs.map +1 -0
  14. package/dist/{chunk-QUGV2GT3.cjs → chunk-BXE2RZEV.cjs} +34 -34
  15. package/dist/{chunk-QUGV2GT3.cjs.map → chunk-BXE2RZEV.cjs.map} +1 -1
  16. package/dist/{chunk-DLRE5Y3G.js → chunk-C2SVJVEB.js} +2 -2
  17. package/dist/{chunk-OC3QH5WG.js → chunk-CMYDGVTZ.js} +2 -2
  18. package/dist/{chunk-VLWUKFQE.cjs → chunk-CWXRZVDI.cjs} +751 -710
  19. package/dist/chunk-CWXRZVDI.cjs.map +1 -0
  20. package/dist/{chunk-2PQKZWWP.js → chunk-DC3G4XGP.js} +6 -6
  21. package/dist/{chunk-KZXKT5AX.cjs → chunk-DDWLF2JV.cjs} +14 -14
  22. package/dist/{chunk-KZXKT5AX.cjs.map → chunk-DDWLF2JV.cjs.map} +1 -1
  23. package/dist/{chunk-NMXL34Z2.cjs → chunk-DVSPRY2V.cjs} +3 -3
  24. package/dist/{chunk-NMXL34Z2.cjs.map → chunk-DVSPRY2V.cjs.map} +1 -1
  25. package/dist/{chunk-OQVHGPHA.js → chunk-DZMDG7PC.js} +3 -3
  26. package/dist/{chunk-2MGTNYHW.cjs → chunk-ESFPY5FF.cjs} +100 -100
  27. package/dist/{chunk-2MGTNYHW.cjs.map → chunk-ESFPY5FF.cjs.map} +1 -1
  28. package/dist/{chunk-S5M5SEW2.js → chunk-FNMQ2J44.js} +5 -5
  29. package/dist/{chunk-UAKWSR3U.js → chunk-GDZ7IC5B.js} +2 -2
  30. package/dist/{chunk-OD2WMUAO.cjs → chunk-GZRU2A4L.cjs} +197 -197
  31. package/dist/{chunk-OD2WMUAO.cjs.map → chunk-GZRU2A4L.cjs.map} +1 -1
  32. package/dist/{chunk-3ZAIHRQH.cjs → chunk-H54RDLVC.cjs} +35 -35
  33. package/dist/{chunk-3ZAIHRQH.cjs.map → chunk-H54RDLVC.cjs.map} +1 -1
  34. package/dist/{chunk-Q76YVNCQ.js → chunk-HM7ZGABI.js} +2 -2
  35. package/dist/{chunk-YCKGUQA2.cjs → chunk-I27PWVWE.cjs} +17 -12
  36. package/dist/chunk-I27PWVWE.cjs.map +1 -0
  37. package/dist/{chunk-25X43LSD.js → chunk-JSG7WZKV.js} +4 -4
  38. package/dist/{chunk-AIXPQT3G.js → chunk-KMW2PXBU.js} +52 -11
  39. package/dist/chunk-KMW2PXBU.js.map +1 -0
  40. package/dist/{chunk-TBJWPNHU.js → chunk-KVDLL4LU.js} +4 -4
  41. package/dist/chunk-KVDLL4LU.js.map +1 -0
  42. package/dist/{chunk-54X75QTC.cjs → chunk-LCENRN2N.cjs} +14 -14
  43. package/dist/{chunk-54X75QTC.cjs.map → chunk-LCENRN2N.cjs.map} +1 -1
  44. package/dist/{chunk-BJHM2OEY.cjs → chunk-LNKLB7GM.cjs} +17 -17
  45. package/dist/{chunk-BJHM2OEY.cjs.map → chunk-LNKLB7GM.cjs.map} +1 -1
  46. package/dist/{chunk-JGVJRUGB.cjs → chunk-MIZU2OTO.cjs} +4 -4
  47. package/dist/{chunk-JGVJRUGB.cjs.map → chunk-MIZU2OTO.cjs.map} +1 -1
  48. package/dist/{chunk-TSENCZUL.js → chunk-MJI5GVT7.js} +11 -11
  49. package/dist/{chunk-BOM6MMWB.cjs → chunk-N3526CTH.cjs} +3 -3
  50. package/dist/{chunk-BOM6MMWB.cjs.map → chunk-N3526CTH.cjs.map} +1 -1
  51. package/dist/{chunk-D7BFS7N6.cjs → chunk-N4S7RBUM.cjs} +3 -3
  52. package/dist/{chunk-D7BFS7N6.cjs.map → chunk-N4S7RBUM.cjs.map} +1 -1
  53. package/dist/{chunk-4HBYEZ3I.cjs → chunk-SA4KQCNB.cjs} +82 -82
  54. package/dist/{chunk-4HBYEZ3I.cjs.map → chunk-SA4KQCNB.cjs.map} +1 -1
  55. package/dist/{chunk-RF2EYX3B.js → chunk-SC44YJCF.js} +2 -2
  56. package/dist/{chunk-2OD2PPNO.cjs → chunk-SDORFSFK.cjs} +37 -37
  57. package/dist/{chunk-2OD2PPNO.cjs.map → chunk-SDORFSFK.cjs.map} +1 -1
  58. package/dist/{chunk-42RQ4NIL.js → chunk-TSNGVWQO.js} +6 -6
  59. package/dist/{chunk-DMXI5D2Q.cjs → chunk-USKQURNF.cjs} +3 -3
  60. package/dist/{chunk-DMXI5D2Q.cjs.map → chunk-USKQURNF.cjs.map} +1 -1
  61. package/dist/{chunk-2KILTCND.js → chunk-UVUNBXS6.js} +6 -1
  62. package/dist/chunk-UVUNBXS6.js.map +1 -0
  63. package/dist/{chunk-5CBQBTGN.cjs → chunk-WZYIAKIH.cjs} +7 -7
  64. package/dist/{chunk-5CBQBTGN.cjs.map → chunk-WZYIAKIH.cjs.map} +1 -1
  65. package/dist/{chunk-2V4KPBNZ.js → chunk-XAOAYDGR.js} +3 -3
  66. package/dist/{chunk-U4OJCJTH.js → chunk-XBYF4SGD.js} +4 -4
  67. package/dist/{chunk-FCQR6VY4.cjs → chunk-Y3BC7TOJ.cjs} +26 -26
  68. package/dist/{chunk-FCQR6VY4.cjs.map → chunk-Y3BC7TOJ.cjs.map} +1 -1
  69. package/dist/{chunk-P27OGSQL.cjs → chunk-Y3Q76RAA.cjs} +37 -37
  70. package/dist/{chunk-P27OGSQL.cjs.map → chunk-Y3Q76RAA.cjs.map} +1 -1
  71. package/dist/{chunk-4BUPMSLY.js → chunk-Y7ZEVTLC.js} +3 -3
  72. package/dist/{chunk-Z622ACFD.js → chunk-Y7ZQZOE6.js} +5 -5
  73. package/dist/{chunk-5A3WSWZD.js → chunk-Z5FHXCXF.js} +8 -8
  74. package/dist/{chunk-UFXV64WQ.cjs → chunk-ZLYLHP64.cjs} +9 -9
  75. package/dist/{chunk-UFXV64WQ.cjs.map → chunk-ZLYLHP64.cjs.map} +1 -1
  76. package/dist/components/case-studies/index.cjs +20 -20
  77. package/dist/components/case-studies/index.js +7 -7
  78. package/dist/components/chat/index.cjs +4 -4
  79. package/dist/components/chat/index.js +3 -3
  80. package/dist/components/contact/index.cjs +8 -8
  81. package/dist/components/contact/index.js +7 -7
  82. package/dist/components/docs/index.cjs +11 -11
  83. package/dist/components/docs/index.js +10 -10
  84. package/dist/components/embeds/index.cjs +8 -8
  85. package/dist/components/embeds/index.js +7 -7
  86. package/dist/components/faq/index.cjs +9 -9
  87. package/dist/components/faq/index.js +8 -8
  88. package/dist/components/features/index.cjs +7 -7
  89. package/dist/components/features/index.js +6 -6
  90. package/dist/components/help-center-pages/index.cjs +101 -101
  91. package/dist/components/help-center-pages/index.js +21 -21
  92. package/dist/components/index.cjs +65 -65
  93. package/dist/components/index.js +21 -21
  94. package/dist/components/meeting-scheduler/index.cjs +58 -58
  95. package/dist/components/meeting-scheduler/index.js +8 -8
  96. package/dist/components/navigation/index.cjs +10 -10
  97. package/dist/components/navigation/index.js +9 -9
  98. package/dist/components/onboarding-guides/index.cjs +11 -11
  99. package/dist/components/onboarding-guides/index.js +10 -10
  100. package/dist/components/related-content/index.cjs +6 -6
  101. package/dist/components/related-content/index.js +5 -5
  102. package/dist/components/tickets/index.cjs +12 -12
  103. package/dist/components/tickets/index.js +11 -11
  104. package/dist/components/tickets/ticket-live-provider.d.ts.map +1 -1
  105. package/dist/components/ui/index.cjs +7 -7
  106. package/dist/components/ui/index.js +6 -6
  107. package/dist/hooks/index.cjs +4 -4
  108. package/dist/hooks/index.js +3 -3
  109. package/dist/index.cjs +12 -12
  110. package/dist/index.js +10 -10
  111. package/dist/utils/embed-authed-fetch.d.ts +18 -0
  112. package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
  113. package/dist/utils/index.cjs +50 -6
  114. package/dist/utils/index.cjs.map +1 -1
  115. package/dist/utils/index.js +50 -6
  116. package/dist/utils/index.js.map +1 -1
  117. package/dist/utils/sse-subscription.d.ts +29 -11
  118. package/dist/utils/sse-subscription.d.ts.map +1 -1
  119. package/package.json +1 -1
  120. package/src/components/tickets/ticket-live-provider.tsx +14 -12
  121. package/src/utils/embed-authed-fetch.ts +21 -0
  122. package/src/utils/sse-subscription.ts +107 -20
  123. package/dist/chunk-2KILTCND.js.map +0 -1
  124. package/dist/chunk-AIXPQT3G.js.map +0 -1
  125. package/dist/chunk-TBJWPNHU.js.map +0 -1
  126. package/dist/chunk-VLWUKFQE.cjs.map +0 -1
  127. package/dist/chunk-YCKGUQA2.cjs.map +0 -1
  128. package/dist/chunk-ZBQZJAC7.cjs.map +0 -1
  129. /package/dist/{chunk-22RSJ57Z.js.map → chunk-2YVTGPM7.js.map} +0 -0
  130. /package/dist/{chunk-V5NOURSR.js.map → chunk-4HZXOQRM.js.map} +0 -0
  131. /package/dist/{chunk-XRSJER2B.js.map → chunk-6Z26ZMR4.js.map} +0 -0
  132. /package/dist/{chunk-HLEXYTOE.js.map → chunk-7EPGJOKN.js.map} +0 -0
  133. /package/dist/{chunk-DOUG4UEZ.js.map → chunk-7WYWPELF.js.map} +0 -0
  134. /package/dist/{chunk-DLRE5Y3G.js.map → chunk-C2SVJVEB.js.map} +0 -0
  135. /package/dist/{chunk-OC3QH5WG.js.map → chunk-CMYDGVTZ.js.map} +0 -0
  136. /package/dist/{chunk-2PQKZWWP.js.map → chunk-DC3G4XGP.js.map} +0 -0
  137. /package/dist/{chunk-OQVHGPHA.js.map → chunk-DZMDG7PC.js.map} +0 -0
  138. /package/dist/{chunk-S5M5SEW2.js.map → chunk-FNMQ2J44.js.map} +0 -0
  139. /package/dist/{chunk-UAKWSR3U.js.map → chunk-GDZ7IC5B.js.map} +0 -0
  140. /package/dist/{chunk-Q76YVNCQ.js.map → chunk-HM7ZGABI.js.map} +0 -0
  141. /package/dist/{chunk-25X43LSD.js.map → chunk-JSG7WZKV.js.map} +0 -0
  142. /package/dist/{chunk-TSENCZUL.js.map → chunk-MJI5GVT7.js.map} +0 -0
  143. /package/dist/{chunk-RF2EYX3B.js.map → chunk-SC44YJCF.js.map} +0 -0
  144. /package/dist/{chunk-42RQ4NIL.js.map → chunk-TSNGVWQO.js.map} +0 -0
  145. /package/dist/{chunk-2V4KPBNZ.js.map → chunk-XAOAYDGR.js.map} +0 -0
  146. /package/dist/{chunk-U4OJCJTH.js.map → chunk-XBYF4SGD.js.map} +0 -0
  147. /package/dist/{chunk-4BUPMSLY.js.map → chunk-Y7ZEVTLC.js.map} +0 -0
  148. /package/dist/{chunk-Z622ACFD.js.map → chunk-Y7ZQZOE6.js.map} +0 -0
  149. /package/dist/{chunk-5A3WSWZD.js.map → chunk-Z5FHXCXF.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkYCKGUQA2cjs = require('./chunk-YCKGUQA2.cjs');
5
+ var _chunkI27PWVWEcjs = require('./chunk-I27PWVWE.cjs');
6
6
 
7
7
  // src/utils/search-params.ts
8
8
  function positiveInt(raw, fallback, { min = 1, max } = {}) {
@@ -122,9 +122,9 @@ function withQuery(path, queryString) {
122
122
 
123
123
  // src/utils/embed-content-fetch.ts
124
124
  var contentFetch = (input, init) => {
125
- if (!_chunkYCKGUQA2cjs.hasEmbedAuthAdapter.call(void 0, )) return fetch(input, init);
125
+ if (!_chunkI27PWVWEcjs.hasEmbedAuthAdapter.call(void 0, )) return fetch(input, init);
126
126
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
127
- return _chunkYCKGUQA2cjs.embedAuthedFetch.call(void 0, url, init);
127
+ return _chunkI27PWVWEcjs.embedAuthedFetch.call(void 0, url, init);
128
128
  };
129
129
 
130
130
 
@@ -142,4 +142,4 @@ var contentFetch = (input, init) => {
142
142
 
143
143
 
144
144
  exports.contentFetch = contentFetch; exports.positiveInt = positiveInt; exports.PAGE_PARAM_LIMITS = PAGE_PARAM_LIMITS; exports.pageCount = pageCount; exports.clampPageToTotal = clampPageToTotal; exports.defineParamSchema = defineParamSchema; exports.fromKeys = fromKeys; exports.firstParamValue = firstParamValue; exports.parseSchemaParams = parseSchemaParams; exports.shouldIncludeInUrl = shouldIncludeInUrl; exports.createSearchParams = createSearchParams; exports.serializeSchemaParams = serializeSchemaParams; exports.withQuery = withQuery;
145
- //# sourceMappingURL=chunk-JGVJRUGB.cjs.map
145
+ //# sourceMappingURL=chunk-MIZU2OTO.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-JGVJRUGB.cjs","../src/utils/search-params.ts","../src/utils/embed-content-fetch.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-JGVJRUGB.cjs","sourcesContent":[null,"/**\n * URL parameter schema engine — the ONE grammar for every URL/API integer,\n * the ONE omission rule, the ONE serializer.\n *\n * Hoisted out of `hooks/state/` so SERVER code (route handlers, DALs) and the\n * CLIENT hook parse the same URL with the same function: a param contract that\n * disagrees across the wire is the bug class this removes. JSX-free leaf with\n * its own `exports` subpath; `hooks/state/*` re-exports every name it used to own.\n */\n\n/** JavaScript types a URL parameter can carry. `int` is a whole number with a floor. */\nexport type JSType = 'string' | 'number' | 'int' | 'boolean' | 'array' | 'object';\n\n// ── The integer grammar ─────────────────────────────────────────────────────\n\nexport interface PositiveIntOptions {\n /** Smallest accepted value (default 1). Anything below falls back. */\n min?: number;\n /** Largest accepted value. Applied to the PARSED value AND to the fallback. */\n max?: number;\n}\n\n/**\n * THE integer grammar for URL params, env vars, and page sizes.\n *\n * `null`/`undefined`/`''` → fallback. Otherwise `Math.trunc(Number(raw))`, which\n * accepts `'12'`, `12`, `'12.9'` (→ 12) and `'0x10'` (→ 16), and rejects `'12abc'`\n * and `'abc'` (`NaN`) — the historical `parseInt` accepted `'12abc'` as 12 and\n * produced `NaN` for `'abc'`. A value below `min` falls back. The RESULT (parsed\n * value or fallback alike) is clamped to `max`, so \"never exceeds max\" has one owner.\n */\nexport function positiveInt<TFallback = number>(\n raw: string | number | null | undefined,\n fallback: TFallback,\n { min = 1, max }: PositiveIntOptions = {},\n): number | TFallback {\n // The fallback is returned AS GIVEN — capping only ever applies to a value\n // parsed out of the input, so a caller with no default (`null`) gets its\n // sentinel back untouched instead of `Math.min(null, max)`.\n const capped = (v: number) => (max == null ? v : Math.min(v, max));\n if (raw === null || raw === undefined || raw === '') {\n return typeof fallback === 'number' ? capped(fallback) : fallback;\n }\n const n = Math.trunc(Number(raw));\n if (!Number.isFinite(n) || n < min) {\n return typeof fallback === 'number' ? capped(fallback) : fallback;\n }\n return capped(n);\n}\n\n/** Ceilings every paged surface shares. */\nexport const PAGE_PARAM_LIMITS = { maxPage: 10_000, maxPageSize: 100 } as const;\n\n/** Total pages for a row count. Floors at 1, so an empty result is still \"page 1 of 1\". */\nexport function pageCount(total: number, pageSize: number): number {\n if (!Number.isFinite(total) || !Number.isFinite(pageSize) || pageSize <= 0) return 1;\n return Math.max(1, Math.ceil(total / pageSize));\n}\n\n/** Clamp a requested page into `1..totalPages`. */\nexport function clampPageToTotal(page: number, totalPages: number): number {\n return Math.min(page, Math.max(1, totalPages));\n}\n\n// ── Schema ──────────────────────────────────────────────────────────────────\n\ntype OutputTypeMap = {\n string: string;\n number: number;\n int: number;\n boolean: boolean;\n array: string[];\n object: Record<string, unknown>;\n};\n\nexport type OutputTypeForJSType<T extends JSType> = OutputTypeMap[T];\n\nexport interface BaseParamConfig<T extends JSType = JSType> {\n type: T;\n default?: OutputTypeMap[T];\n required?: boolean;\n}\n\n/** An integer parameter with an optional floor and ceiling. */\nexport interface IntParamConfig extends BaseParamConfig<'int'> {\n min?: number;\n max?: number;\n}\n\nexport type ParamConfig<T extends JSType = JSType> = T extends 'int' ? IntParamConfig : BaseParamConfig<T>;\n\nexport type ParamSchema = Record<string, ParamConfig>;\n\n/** Identity helper that preserves literal types through a schema object. */\nexport function defineParamSchema<T extends ParamSchema>(schema: T): T {\n return schema;\n}\n\n/**\n * Build a `Record<K, V>` from a key tuple. Keeps `keyof` EXACT when spreading a\n * shared config across derived keys (an inline `Object.fromEntries` widens to\n * `Record<string, V>` and loses the key union).\n */\nexport function fromKeys<K extends string, V>(keys: readonly K[], value: V): Record<K, V> {\n return Object.fromEntries(keys.map(k => [k, value])) as Record<K, V>;\n}\n\n// ── Reading ─────────────────────────────────────────────────────────────────\n\n/** Anything a URL can be read from: `URLSearchParams`, Next's `searchParams`, or a plain record. */\nexport type ParamInput = URLSearchParams | Record<string, string | string[] | undefined> | null | undefined;\n\n/** THE first-value rule for a duplicated key (`?a=1&a=2` reads as `1`). */\nexport function firstParamValue(input: ParamInput, key: string): string | undefined {\n if (!input) return undefined;\n if (input instanceof URLSearchParams) return input.get(key) ?? undefined;\n const value = input[key];\n return Array.isArray(value) ? value[0] : value;\n}\n\n/** Every value for a key (arrays read repeated params, then a `,`-joined single value). */\nfunction allParamValues(input: ParamInput, key: string): string[] {\n if (!input) return [];\n const raw = input instanceof URLSearchParams ? input.getAll(key) : input[key];\n const list = Array.isArray(raw) ? raw : raw === undefined ? [] : [raw];\n if (list.length === 1 && list[0]?.includes(',')) return list[0].split(',').filter(Boolean);\n return list.filter(v => v !== undefined && v !== '');\n}\n\nexport interface ParseSchemaOptions {\n /**\n * What an ABSENT scalar with no declared `default` resolves to.\n * `'undefined'` (default) keeps today's behaviour; `'null'` is the explicit\n * \"this filter is unset\" spelling that survives JSON and `URLSearchParams`.\n */\n absent?: 'undefined' | 'null';\n}\n\nexport type AbsentValue<Opts extends ParseSchemaOptions | undefined> = Opts extends { absent: 'null' }\n ? null\n : undefined;\n\n/**\n * The parsed shape of a schema: keys with a `default` and every array key are\n * non-nullable; an undefaulted scalar carries the caller's absent value.\n */\nexport type InferParsedParams<TSchema extends ParamSchema, Absent = undefined> = {\n [K in keyof TSchema]: TSchema[K] extends { type: 'array' }\n ? string[]\n : TSchema[K] extends { default: infer D }\n ? D\n : TSchema[K]['type'] extends infer T\n ? T extends JSType\n ? OutputTypeForJSType<T> | Absent\n : never\n : never;\n};\n\nfunction coerceScalar(raw: string | undefined, config: ParamConfig): unknown {\n if (raw === undefined || raw === '') return undefined;\n switch (config.type) {\n case 'int': {\n const int = config;\n // ONE integer grammar, default or not. Without a declared default the\n // fallback is `undefined` (an unparseable value is absent), which\n // `positiveInt` returns untouched — the branch used to open-code the\n // truncation, the min floor and the max clamp a second time.\n return positiveInt<undefined>(raw, int.default as undefined, { min: int.min, max: int.max });\n }\n case 'number': {\n const n = Number(raw);\n return Number.isFinite(n) ? n : undefined;\n }\n case 'boolean':\n return raw === 'true' || raw === '1';\n case 'object':\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n default:\n return raw;\n }\n}\n\n/**\n * Parse a URL into a schema's shape. THE reader — the server route and the\n * client hook both call this, so a URL means exactly one thing.\n *\n * PRECEDENCE: an absent or empty SCALAR takes the declared `default` when there\n * is one, else the `absent` value. ARRAY keys never take `absent`: they are `[]`.\n */\nexport function parseSchemaParams<TSchema extends ParamSchema, Opts extends ParseSchemaOptions = Record<string, never>>(\n schema: TSchema,\n input: ParamInput,\n options?: Opts,\n): InferParsedParams<TSchema, AbsentValue<Opts>> {\n const absent = options?.absent === 'null' ? null : undefined;\n const out: Record<string, unknown> = {};\n for (const [key, config] of Object.entries(schema)) {\n if (config.type === 'array') {\n const values = allParamValues(input, key);\n out[key] = values.length > 0 ? values : (config.default ?? []);\n continue;\n }\n const coerced = coerceScalar(firstParamValue(input, key), config);\n out[key] = coerced !== undefined ? coerced : (config.default ?? absent);\n }\n return out as InferParsedParams<TSchema, AbsentValue<Opts>>;\n}\n\n// ── Writing ─────────────────────────────────────────────────────────────────\n\n/**\n * A schema entry OR a legacy flattened param (which spells its default\n * `defaultValue`).\n *\n * A UNION, not a bag of two optional fields: with only `default?` and\n * `defaultValue?` this is a WEAK TYPE, and TypeScript rejects any argument\n * sharing neither — including `{ type: 'string' }`, the single most common\n * schema entry there is. Naming the real config shapes fixes that without an\n * index signature, which `FlattenedParam` (an interface) could never satisfy.\n */\ntype OmissionConfig = ParamConfig | { default?: unknown; defaultValue?: unknown };\n\n/**\n * THE omission rule: `null`, `undefined`, `''`, `[]`, and a value equal to the\n * declared default are left out, so a URL only ever carries what differs.\n */\nexport function shouldIncludeInUrl(value: unknown, config: OmissionConfig | undefined): boolean {\n if (value === null || value === undefined) return false;\n if (Array.isArray(value) && value.length === 0) return false;\n if (value === '') return false;\n // `in` narrowing rather than a cast: the union's members genuinely differ —\n // a schema entry spells its default `default`, a flattened param spells it\n // `defaultValue`, and neither is obliged to carry the other's field.\n const fromDefault = config && 'default' in config ? config.default : undefined;\n const fromLegacy = config && 'defaultValue' in config ? config.defaultValue : undefined;\n const declared = fromDefault !== undefined ? fromDefault : fromLegacy;\n if (declared !== undefined && value === declared) return false;\n return true;\n}\n\nexport interface CreateSearchParamsOptions {\n /** Join arrays with this separator instead of repeating the key (`;` for the hub's list APIs). */\n arrayJoin?: string;\n}\n\n/** Build `URLSearchParams` from a value record (arrays repeat the key by default). */\nexport function createSearchParams(\n params: Record<string, unknown>,\n { arrayJoin }: CreateSearchParamsOptions = {},\n): URLSearchParams {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n // The emptiness half of the omission rule has ONE owner. (No config here:\n // callers that must also drop schema DEFAULTS filter through\n // `shouldIncludeInUrl` with the config first — see `serializeSchemaParams`.)\n if (!shouldIncludeInUrl(value, undefined)) continue;\n if (Array.isArray(value)) {\n // Empty ELEMENTS are dropped, and an array that filters to nothing is\n // absent (a cleared multi-select must not leave `?tags=` behind).\n const items = value.filter(v => v !== null && v !== undefined && v !== '');\n if (items.length === 0) continue;\n if (arrayJoin) search.set(key, items.join(arrayJoin));\n else for (const item of items) search.append(key, String(item));\n continue;\n }\n if (typeof value === 'object') {\n search.set(key, JSON.stringify(value));\n continue;\n }\n search.set(key, String(value));\n }\n return search;\n}\n\n/** Serialize params to a query string, omitting everything `shouldIncludeInUrl` rejects. */\nexport function serializeSchemaParams<TSchema extends ParamSchema>(\n schema: TSchema,\n params: Record<string, unknown>,\n options?: CreateSearchParamsOptions,\n): string {\n const kept: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(params)) {\n if (shouldIncludeInUrl(value, schema[key])) kept[key] = value;\n }\n return createSearchParams(kept, options).toString();\n}\n\n/** Append a query string to a path, or return the path unchanged when it is empty. */\nexport function withQuery(path: string, queryString: string | null | undefined): string {\n return queryString ? `${path}?${queryString}` : path;\n}\n","'use client';\n\n/**\n * `contentFetch` — the fetch the lib's SELF-FETCHING content surfaces use:\n * roadmap (list + vote + per-task refresh), delivery, product releases,\n * onboarding guides (catalog + detail), legal docs, and the release-detail\n * injected roadmap/delivery sections.\n *\n * It deliberately reuses the SINGLE embed-auth knob — the registered\n * `EmbedAuthAdapter` — instead of introducing a second registration:\n * - adapter registered (host opted into embedded auth, e.g. openframe-frontend\n * for its embedded chat) → route through `embedAuthedFetch`, so content GETs/\n * POSTs carry the SAME bearer/cookie + 401-refresh as the chat.\n * - no adapter (the public hub, or the embedding example whose proxy injects\n * auth server-side) → a plain, byte-for-byte unchanged `fetch`.\n *\n * So a host wires auth ONCE (the chat adapter) and content inherits it; there is\n * no content-specific fetcher to configure.\n */\n\nimport { embedAuthedFetch, hasEmbedAuthAdapter } from './embed-authed-fetch';\n\nexport const contentFetch: typeof fetch = (input, init) => {\n if (!hasEmbedAuthAdapter()) return fetch(input, init);\n // embedAuthedFetch takes a string url; coerce the broader `fetch` input shape\n // (content surfaces always pass strings, but handle URL/Request defensively).\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n return embedAuthedFetch(url, init);\n};\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-MIZU2OTO.cjs","../src/utils/search-params.ts","../src/utils/embed-content-fetch.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-MIZU2OTO.cjs","sourcesContent":[null,"/**\n * URL parameter schema engine — the ONE grammar for every URL/API integer,\n * the ONE omission rule, the ONE serializer.\n *\n * Hoisted out of `hooks/state/` so SERVER code (route handlers, DALs) and the\n * CLIENT hook parse the same URL with the same function: a param contract that\n * disagrees across the wire is the bug class this removes. JSX-free leaf with\n * its own `exports` subpath; `hooks/state/*` re-exports every name it used to own.\n */\n\n/** JavaScript types a URL parameter can carry. `int` is a whole number with a floor. */\nexport type JSType = 'string' | 'number' | 'int' | 'boolean' | 'array' | 'object';\n\n// ── The integer grammar ─────────────────────────────────────────────────────\n\nexport interface PositiveIntOptions {\n /** Smallest accepted value (default 1). Anything below falls back. */\n min?: number;\n /** Largest accepted value. Applied to the PARSED value AND to the fallback. */\n max?: number;\n}\n\n/**\n * THE integer grammar for URL params, env vars, and page sizes.\n *\n * `null`/`undefined`/`''` → fallback. Otherwise `Math.trunc(Number(raw))`, which\n * accepts `'12'`, `12`, `'12.9'` (→ 12) and `'0x10'` (→ 16), and rejects `'12abc'`\n * and `'abc'` (`NaN`) — the historical `parseInt` accepted `'12abc'` as 12 and\n * produced `NaN` for `'abc'`. A value below `min` falls back. The RESULT (parsed\n * value or fallback alike) is clamped to `max`, so \"never exceeds max\" has one owner.\n */\nexport function positiveInt<TFallback = number>(\n raw: string | number | null | undefined,\n fallback: TFallback,\n { min = 1, max }: PositiveIntOptions = {},\n): number | TFallback {\n // The fallback is returned AS GIVEN — capping only ever applies to a value\n // parsed out of the input, so a caller with no default (`null`) gets its\n // sentinel back untouched instead of `Math.min(null, max)`.\n const capped = (v: number) => (max == null ? v : Math.min(v, max));\n if (raw === null || raw === undefined || raw === '') {\n return typeof fallback === 'number' ? capped(fallback) : fallback;\n }\n const n = Math.trunc(Number(raw));\n if (!Number.isFinite(n) || n < min) {\n return typeof fallback === 'number' ? capped(fallback) : fallback;\n }\n return capped(n);\n}\n\n/** Ceilings every paged surface shares. */\nexport const PAGE_PARAM_LIMITS = { maxPage: 10_000, maxPageSize: 100 } as const;\n\n/** Total pages for a row count. Floors at 1, so an empty result is still \"page 1 of 1\". */\nexport function pageCount(total: number, pageSize: number): number {\n if (!Number.isFinite(total) || !Number.isFinite(pageSize) || pageSize <= 0) return 1;\n return Math.max(1, Math.ceil(total / pageSize));\n}\n\n/** Clamp a requested page into `1..totalPages`. */\nexport function clampPageToTotal(page: number, totalPages: number): number {\n return Math.min(page, Math.max(1, totalPages));\n}\n\n// ── Schema ──────────────────────────────────────────────────────────────────\n\ntype OutputTypeMap = {\n string: string;\n number: number;\n int: number;\n boolean: boolean;\n array: string[];\n object: Record<string, unknown>;\n};\n\nexport type OutputTypeForJSType<T extends JSType> = OutputTypeMap[T];\n\nexport interface BaseParamConfig<T extends JSType = JSType> {\n type: T;\n default?: OutputTypeMap[T];\n required?: boolean;\n}\n\n/** An integer parameter with an optional floor and ceiling. */\nexport interface IntParamConfig extends BaseParamConfig<'int'> {\n min?: number;\n max?: number;\n}\n\nexport type ParamConfig<T extends JSType = JSType> = T extends 'int' ? IntParamConfig : BaseParamConfig<T>;\n\nexport type ParamSchema = Record<string, ParamConfig>;\n\n/** Identity helper that preserves literal types through a schema object. */\nexport function defineParamSchema<T extends ParamSchema>(schema: T): T {\n return schema;\n}\n\n/**\n * Build a `Record<K, V>` from a key tuple. Keeps `keyof` EXACT when spreading a\n * shared config across derived keys (an inline `Object.fromEntries` widens to\n * `Record<string, V>` and loses the key union).\n */\nexport function fromKeys<K extends string, V>(keys: readonly K[], value: V): Record<K, V> {\n return Object.fromEntries(keys.map(k => [k, value])) as Record<K, V>;\n}\n\n// ── Reading ─────────────────────────────────────────────────────────────────\n\n/** Anything a URL can be read from: `URLSearchParams`, Next's `searchParams`, or a plain record. */\nexport type ParamInput = URLSearchParams | Record<string, string | string[] | undefined> | null | undefined;\n\n/** THE first-value rule for a duplicated key (`?a=1&a=2` reads as `1`). */\nexport function firstParamValue(input: ParamInput, key: string): string | undefined {\n if (!input) return undefined;\n if (input instanceof URLSearchParams) return input.get(key) ?? undefined;\n const value = input[key];\n return Array.isArray(value) ? value[0] : value;\n}\n\n/** Every value for a key (arrays read repeated params, then a `,`-joined single value). */\nfunction allParamValues(input: ParamInput, key: string): string[] {\n if (!input) return [];\n const raw = input instanceof URLSearchParams ? input.getAll(key) : input[key];\n const list = Array.isArray(raw) ? raw : raw === undefined ? [] : [raw];\n if (list.length === 1 && list[0]?.includes(',')) return list[0].split(',').filter(Boolean);\n return list.filter(v => v !== undefined && v !== '');\n}\n\nexport interface ParseSchemaOptions {\n /**\n * What an ABSENT scalar with no declared `default` resolves to.\n * `'undefined'` (default) keeps today's behaviour; `'null'` is the explicit\n * \"this filter is unset\" spelling that survives JSON and `URLSearchParams`.\n */\n absent?: 'undefined' | 'null';\n}\n\nexport type AbsentValue<Opts extends ParseSchemaOptions | undefined> = Opts extends { absent: 'null' }\n ? null\n : undefined;\n\n/**\n * The parsed shape of a schema: keys with a `default` and every array key are\n * non-nullable; an undefaulted scalar carries the caller's absent value.\n */\nexport type InferParsedParams<TSchema extends ParamSchema, Absent = undefined> = {\n [K in keyof TSchema]: TSchema[K] extends { type: 'array' }\n ? string[]\n : TSchema[K] extends { default: infer D }\n ? D\n : TSchema[K]['type'] extends infer T\n ? T extends JSType\n ? OutputTypeForJSType<T> | Absent\n : never\n : never;\n};\n\nfunction coerceScalar(raw: string | undefined, config: ParamConfig): unknown {\n if (raw === undefined || raw === '') return undefined;\n switch (config.type) {\n case 'int': {\n const int = config;\n // ONE integer grammar, default or not. Without a declared default the\n // fallback is `undefined` (an unparseable value is absent), which\n // `positiveInt` returns untouched — the branch used to open-code the\n // truncation, the min floor and the max clamp a second time.\n return positiveInt<undefined>(raw, int.default as undefined, { min: int.min, max: int.max });\n }\n case 'number': {\n const n = Number(raw);\n return Number.isFinite(n) ? n : undefined;\n }\n case 'boolean':\n return raw === 'true' || raw === '1';\n case 'object':\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n default:\n return raw;\n }\n}\n\n/**\n * Parse a URL into a schema's shape. THE reader — the server route and the\n * client hook both call this, so a URL means exactly one thing.\n *\n * PRECEDENCE: an absent or empty SCALAR takes the declared `default` when there\n * is one, else the `absent` value. ARRAY keys never take `absent`: they are `[]`.\n */\nexport function parseSchemaParams<TSchema extends ParamSchema, Opts extends ParseSchemaOptions = Record<string, never>>(\n schema: TSchema,\n input: ParamInput,\n options?: Opts,\n): InferParsedParams<TSchema, AbsentValue<Opts>> {\n const absent = options?.absent === 'null' ? null : undefined;\n const out: Record<string, unknown> = {};\n for (const [key, config] of Object.entries(schema)) {\n if (config.type === 'array') {\n const values = allParamValues(input, key);\n out[key] = values.length > 0 ? values : (config.default ?? []);\n continue;\n }\n const coerced = coerceScalar(firstParamValue(input, key), config);\n out[key] = coerced !== undefined ? coerced : (config.default ?? absent);\n }\n return out as InferParsedParams<TSchema, AbsentValue<Opts>>;\n}\n\n// ── Writing ─────────────────────────────────────────────────────────────────\n\n/**\n * A schema entry OR a legacy flattened param (which spells its default\n * `defaultValue`).\n *\n * A UNION, not a bag of two optional fields: with only `default?` and\n * `defaultValue?` this is a WEAK TYPE, and TypeScript rejects any argument\n * sharing neither — including `{ type: 'string' }`, the single most common\n * schema entry there is. Naming the real config shapes fixes that without an\n * index signature, which `FlattenedParam` (an interface) could never satisfy.\n */\ntype OmissionConfig = ParamConfig | { default?: unknown; defaultValue?: unknown };\n\n/**\n * THE omission rule: `null`, `undefined`, `''`, `[]`, and a value equal to the\n * declared default are left out, so a URL only ever carries what differs.\n */\nexport function shouldIncludeInUrl(value: unknown, config: OmissionConfig | undefined): boolean {\n if (value === null || value === undefined) return false;\n if (Array.isArray(value) && value.length === 0) return false;\n if (value === '') return false;\n // `in` narrowing rather than a cast: the union's members genuinely differ —\n // a schema entry spells its default `default`, a flattened param spells it\n // `defaultValue`, and neither is obliged to carry the other's field.\n const fromDefault = config && 'default' in config ? config.default : undefined;\n const fromLegacy = config && 'defaultValue' in config ? config.defaultValue : undefined;\n const declared = fromDefault !== undefined ? fromDefault : fromLegacy;\n if (declared !== undefined && value === declared) return false;\n return true;\n}\n\nexport interface CreateSearchParamsOptions {\n /** Join arrays with this separator instead of repeating the key (`;` for the hub's list APIs). */\n arrayJoin?: string;\n}\n\n/** Build `URLSearchParams` from a value record (arrays repeat the key by default). */\nexport function createSearchParams(\n params: Record<string, unknown>,\n { arrayJoin }: CreateSearchParamsOptions = {},\n): URLSearchParams {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n // The emptiness half of the omission rule has ONE owner. (No config here:\n // callers that must also drop schema DEFAULTS filter through\n // `shouldIncludeInUrl` with the config first — see `serializeSchemaParams`.)\n if (!shouldIncludeInUrl(value, undefined)) continue;\n if (Array.isArray(value)) {\n // Empty ELEMENTS are dropped, and an array that filters to nothing is\n // absent (a cleared multi-select must not leave `?tags=` behind).\n const items = value.filter(v => v !== null && v !== undefined && v !== '');\n if (items.length === 0) continue;\n if (arrayJoin) search.set(key, items.join(arrayJoin));\n else for (const item of items) search.append(key, String(item));\n continue;\n }\n if (typeof value === 'object') {\n search.set(key, JSON.stringify(value));\n continue;\n }\n search.set(key, String(value));\n }\n return search;\n}\n\n/** Serialize params to a query string, omitting everything `shouldIncludeInUrl` rejects. */\nexport function serializeSchemaParams<TSchema extends ParamSchema>(\n schema: TSchema,\n params: Record<string, unknown>,\n options?: CreateSearchParamsOptions,\n): string {\n const kept: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(params)) {\n if (shouldIncludeInUrl(value, schema[key])) kept[key] = value;\n }\n return createSearchParams(kept, options).toString();\n}\n\n/** Append a query string to a path, or return the path unchanged when it is empty. */\nexport function withQuery(path: string, queryString: string | null | undefined): string {\n return queryString ? `${path}?${queryString}` : path;\n}\n","'use client';\n\n/**\n * `contentFetch` — the fetch the lib's SELF-FETCHING content surfaces use:\n * roadmap (list + vote + per-task refresh), delivery, product releases,\n * onboarding guides (catalog + detail), legal docs, and the release-detail\n * injected roadmap/delivery sections.\n *\n * It deliberately reuses the SINGLE embed-auth knob — the registered\n * `EmbedAuthAdapter` — instead of introducing a second registration:\n * - adapter registered (host opted into embedded auth, e.g. openframe-frontend\n * for its embedded chat) → route through `embedAuthedFetch`, so content GETs/\n * POSTs carry the SAME bearer/cookie + 401-refresh as the chat.\n * - no adapter (the public hub, or the embedding example whose proxy injects\n * auth server-side) → a plain, byte-for-byte unchanged `fetch`.\n *\n * So a host wires auth ONCE (the chat adapter) and content inherits it; there is\n * no content-specific fetcher to configure.\n */\n\nimport { embedAuthedFetch, hasEmbedAuthAdapter } from './embed-authed-fetch';\n\nexport const contentFetch: typeof fetch = (input, init) => {\n if (!hasEmbedAuthAdapter()) return fetch(input, init);\n // embedAuthedFetch takes a string url; coerce the broader `fetch` input shape\n // (content surfaces always pass strings, but handle URL/Request defensively).\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n return embedAuthedFetch(url, init);\n};\n"]}
@@ -1,20 +1,20 @@
1
1
  "use client";
2
2
  import {
3
3
  PersistentPaginationWrapper
4
- } from "./chunk-RF2EYX3B.js";
4
+ } from "./chunk-SC44YJCF.js";
5
5
  import {
6
6
  DeliveryRow,
7
7
  ListEmptyState
8
- } from "./chunk-25X43LSD.js";
8
+ } from "./chunk-JSG7WZKV.js";
9
9
  import {
10
10
  EmptyState
11
- } from "./chunk-Q76YVNCQ.js";
11
+ } from "./chunk-HM7ZGABI.js";
12
12
  import {
13
13
  DetailPageSkeleton
14
- } from "./chunk-OQVHGPHA.js";
14
+ } from "./chunk-DZMDG7PC.js";
15
15
  import {
16
16
  useSelfFetch
17
- } from "./chunk-DLRE5Y3G.js";
17
+ } from "./chunk-C2SVJVEB.js";
18
18
  import {
19
19
  Accordion,
20
20
  AccordionContent,
@@ -30,23 +30,23 @@ import {
30
30
  RichMarkdownRenderer,
31
31
  devSectionAnchorId,
32
32
  resolveContentHref
33
- } from "./chunk-AIXPQT3G.js";
33
+ } from "./chunk-KMW2PXBU.js";
34
34
  import {
35
35
  useHumanitySignals,
36
36
  useScrollToHash
37
- } from "./chunk-2V4KPBNZ.js";
37
+ } from "./chunk-XAOAYDGR.js";
38
38
  import {
39
39
  contentFetch,
40
40
  pageCount,
41
41
  positiveInt
42
- } from "./chunk-UAKWSR3U.js";
42
+ } from "./chunk-GDZ7IC5B.js";
43
43
  import {
44
44
  Video,
45
45
  executeNavigationImperative,
46
46
  isModifierClick,
47
47
  useCaptions,
48
48
  useIsHydrated
49
- } from "./chunk-4BUPMSLY.js";
49
+ } from "./chunk-Y7ZEVTLC.js";
50
50
  import {
51
51
  STICKY_HEADER_OFFSET_PX
52
52
  } from "./chunk-JUNOY5HK.js";
@@ -72,7 +72,7 @@ import {
72
72
  formatReleaseDate,
73
73
  toStripProfile,
74
74
  useEntityCardLink
75
- } from "./chunk-22RSJ57Z.js";
75
+ } from "./chunk-2YVTGPM7.js";
76
76
  import {
77
77
  next_image_default
78
78
  } from "./chunk-MDKCTSDS.js";
@@ -1373,4 +1373,4 @@ export {
1373
1373
  useLegalDocs,
1374
1374
  LegalDocumentPage
1375
1375
  };
1376
- //# sourceMappingURL=chunk-TSENCZUL.js.map
1376
+ //# sourceMappingURL=chunk-MJI5GVT7.js.map
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
2
 
3
3
 
4
- var _chunkNMXL34Z2cjs = require('./chunk-NMXL34Z2.cjs');
4
+ var _chunkDVSPRY2Vcjs = require('./chunk-DVSPRY2V.cjs');
5
5
 
6
6
 
7
7
  var _chunkX7CEWD6Vcjs = require('./chunk-X7CEWD6V.cjs');
@@ -116,7 +116,7 @@ function PersistentPaginationWrapper({
116
116
  },
117
117
  "aria-hidden": isPaginationDisabled,
118
118
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
119
- _chunkNMXL34Z2cjs.UnifiedPagination,
119
+ _chunkDVSPRY2Vcjs.UnifiedPagination,
120
120
  {
121
121
  currentPage: displayCurrentPage,
122
122
  totalPages: displayTotalPages,
@@ -136,4 +136,4 @@ function PersistentPaginationWrapper({
136
136
 
137
137
 
138
138
  exports.PersistentPagination = PersistentPagination; exports.usePaginationLoading = usePaginationLoading; exports.PersistentPaginationWrapper = PersistentPaginationWrapper;
139
- //# sourceMappingURL=chunk-BOM6MMWB.cjs.map
139
+ //# sourceMappingURL=chunk-N3526CTH.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BOM6MMWB.cjs","../src/components/persistent-pagination.tsx"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACwFQ,+CAAA;AA/BD,SAAS,oBAAA,CAAqB;AAAA,EACnC,SAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA,EAAkB,GAAA;AAAA,EAClB,mBAAA,EAAqB;AACvB,CAAA,EAA8B;AAI5B,EAAA,uBACE,8BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,qCAAA;AAAA,QACA,kCAAA;AAAA,QACA,UAAA,GAAa,qBAAA;AAAA,QACb;AAAA,MACF,CAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,OAAA,EAAS,UAAA,EAAY,gBAAA,EAAkB,CAAA;AAAA,QACvC,kBAAA,EAAoB,CAAA,EAAA;AACtB,MAAA;AACK,MAAA;AACM,MAAA;AACA,MAAA;AACG,MAAA;AAGb,MAAA;AACC,QAAA;AAA0D,UAAA;AACJ,UAAA;AAAY,UAAA;AAAK,UAAA;AACvE,QAAA;AAIF,wBAAA;AAAC,UAAA;AAAA,UAAA;AACe,YAAA;AACP,YAAA;AACL,cAAA;AACF,YAAA;AACa,YAAA;AAEZ,YAAA;AAAA,UAAA;AACH,QAAA;AAAA,MAAA;AAAA,IAAA;AAGF,EAAA;AAEJ;AAKgB;AACR,EAAA;AAEkB,EAAA;AACT,IAAA;AACG,IAAA;AACK,IAAA;AACD,IAAA;AACtB,EAAA;AAE0B,EAAA;AACT,IAAA;AACU,MAAA;AACzB,IAAA;AACe,IAAA;AACjB,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAcgB;AACd,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACmC;AAGhB,EAAA;AACO,EAAA;AACC,EAAA;AAErB,EAAA;AACgB,EAAA;AAGpB,EAAA;AAAC,IAAA;AAAA,IAAA;AACY,MAAA;AACT,QAAA;AACC,QAAA;AACD,QAAA;AACF,MAAA;AACO,MAAA;AACI,QAAA;AACW,QAAA;AACtB,MAAA;AACK,MAAA;AACM,MAAA;AACA,MAAA;AACG,MAAA;AACI,MAAA;AAGhB,MAAA;AAAc,QAAA;AAShB,wBAAA;AAAC,UAAA;AAAA,UAAA;AACe,YAAA;AACP,YAAA;AACL,cAAA;AACF,YAAA;AACa,YAAA;AAKb,YAAA;AAAC,cAAA;AAAA,cAAA;AACc,gBAAA;AACD,gBAAA;AACZ,gBAAA;AAAiD,gBAAA;AAAA,cAAA;AACnD,YAAA;AAAA,UAAA;AACF,QAAA;AAAA,MAAA;AAAA,IAAA;AAGF,EAAA;AAEJ;ADvF6B;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BOM6MMWB.cjs","sourcesContent":[null,"'use client';\n\nimport type { ReactNode } from 'react';\nimport { cn } from '../utils/cn';\nimport { UnifiedPagination } from './unified-pagination';\n\ninterface PersistentPaginationProps {\n /**\n * Whether pagination is currently in a loading state\n */\n isLoading: boolean;\n /**\n * The pagination component\n */\n children: ReactNode;\n /**\n * Current page number\n */\n currentPage: number;\n /**\n * Total number of pages\n */\n totalPages: number;\n /**\n * Additional CSS classes\n */\n className?: string;\n /**\n * Disabled opacity (0-1)\n */\n disabledOpacity?: number;\n /**\n * Transition duration in milliseconds\n */\n transitionDuration?: number;\n /**\n * Whether to show loading state in pagination\n */\n showLoadingState?: boolean;\n}\n\n/**\n * PersistentPagination\n *\n * A wrapper component that keeps pagination visible during loading states\n * but provides visual feedback that it's temporarily disabled.\n *\n * Features:\n * - Maintains pagination visibility during content loading\n * - Disables interaction but preserves layout\n * - Shows current page context even when loading\n * - Provides accessibility support for loading states\n * - Smooth transitions between enabled/disabled states\n *\n * Usage:\n * ```tsx\n * <PersistentPagination\n * isLoading={isLoadingVendors}\n * currentPage={currentPage}\n * totalPages={totalPages}\n * >\n * <Pagination currentPage={currentPage} totalPages={totalPages} />\n * </PersistentPagination>\n * ```\n */\nexport function PersistentPagination({\n isLoading,\n children,\n currentPage,\n totalPages,\n className,\n disabledOpacity = 0.5,\n transitionDuration = 300,\n}: PersistentPaginationProps) {\n // ALWAYS show pagination for predictable layout - just gray it out when not needed\n // Removed condition that hides pagination completely\n\n return (\n <div\n className={cn(\n 'relative transition-all ease-in-out',\n 'flex items-center justify-center',\n isLoading && 'pointer-events-none',\n className,\n )}\n style={{\n opacity: isLoading ? disabledOpacity : 1,\n transitionDuration: `${transitionDuration}ms`,\n }}\n role=\"navigation\"\n aria-label=\"Pagination\"\n aria-busy={isLoading}\n data-loading={isLoading}\n >\n {/* Loading state announcement for screen readers */}\n {isLoading && (\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n Pagination temporarily disabled while loading page {currentPage} of {totalPages}\n </div>\n )}\n\n {/* Pagination controls with disabled state */}\n <div\n className={cn('relative transition-all ease-in-out', isLoading && 'cursor-not-allowed')}\n style={{\n transitionDuration: `${transitionDuration}ms`,\n }}\n aria-hidden={isLoading}\n >\n {children}\n </div>\n\n {/* REMOVED: Loading overlay - only card skeletons should show during loading */}\n </div>\n );\n}\n\n/**\n * Hook for managing pagination loading states\n */\nexport function usePaginationLoading(isLoading: boolean, currentPage: number, totalPages: number) {\n const shouldShowPagination = totalPages > 1 || isLoading;\n\n const paginationProps = {\n 'aria-busy': isLoading,\n 'data-loading': isLoading,\n 'data-current-page': currentPage,\n 'data-total-pages': totalPages,\n };\n\n const getLoadingMessage = () => {\n if (isLoading) {\n return `Loading page ${currentPage} of ${totalPages}`;\n }\n return `Page ${currentPage} of ${totalPages}`;\n };\n\n return {\n shouldShowPagination,\n paginationProps,\n getLoadingMessage,\n };\n}\n\n/**\n * Enhanced pagination component that includes loading states\n */\ninterface PersistentPaginationWrapperProps {\n isLoading: boolean;\n currentPage: number;\n totalPages: number;\n onPageChange?: (page: number) => void;\n className?: string;\n variant?: 'vendor' | 'blog';\n}\n\nexport function PersistentPaginationWrapper({\n isLoading,\n currentPage,\n totalPages,\n onPageChange,\n className,\n}: PersistentPaginationWrapperProps) {\n // ALWAYS show pagination for predictable layout\n // For no results (totalPages = 0), show grayed out pagination\n const hasResults = totalPages > 0;\n const displayTotalPages = hasResults ? totalPages : 1;\n const displayCurrentPage = hasResults ? currentPage : 1;\n // Only disable during loading, but still show when no results (just grayed out)\n const isPaginationDisabled = isLoading;\n const hasNoResults = !hasResults && !isLoading;\n\n return (\n <div\n className={cn(\n 'relative flex items-center justify-center transition-all ease-in-out',\n (isPaginationDisabled || hasNoResults) && 'pointer-events-none',\n className,\n )}\n style={{\n opacity: isPaginationDisabled ? 0.3 : hasNoResults ? 0.5 : 1,\n transitionDuration: '300ms',\n }}\n role=\"navigation\"\n aria-label=\"Pagination\"\n aria-busy={isLoading}\n data-loading={isLoading}\n data-has-results={hasResults}\n >\n {/* Loading/no results state announcement for screen readers */}\n {(isLoading || !hasResults) && (\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {isLoading\n ? `Pagination temporarily disabled while loading page ${displayCurrentPage} of ${displayTotalPages}`\n : `No results available - pagination disabled`}\n </div>\n )}\n\n {/* Pagination controls with disabled state */}\n <div\n className={cn('relative transition-all ease-in-out', isPaginationDisabled && 'cursor-not-allowed')}\n style={{\n transitionDuration: '300ms',\n }}\n aria-hidden={isPaginationDisabled}\n >\n {/* UnifiedPagination directly — Pagination and BlogPagination in\n multi-platform-hub are both thin wrappers around it, so the wrapper\n never has to be handed down from the consuming app. */}\n <UnifiedPagination\n currentPage={displayCurrentPage}\n totalPages={displayTotalPages}\n onPageChange={hasResults ? onPageChange : () => {}} // Provide empty function instead of undefined\n />\n </div>\n\n {/* REMOVED: Loading overlays - only card skeletons should show during loading */}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-N3526CTH.cjs","../src/components/persistent-pagination.tsx"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACwFQ,+CAAA;AA/BD,SAAS,oBAAA,CAAqB;AAAA,EACnC,SAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA,EAAkB,GAAA;AAAA,EAClB,mBAAA,EAAqB;AACvB,CAAA,EAA8B;AAI5B,EAAA,uBACE,8BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,qCAAA;AAAA,QACA,kCAAA;AAAA,QACA,UAAA,GAAa,qBAAA;AAAA,QACb;AAAA,MACF,CAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,OAAA,EAAS,UAAA,EAAY,gBAAA,EAAkB,CAAA;AAAA,QACvC,kBAAA,EAAoB,CAAA,EAAA;AACtB,MAAA;AACK,MAAA;AACM,MAAA;AACA,MAAA;AACG,MAAA;AAGb,MAAA;AACC,QAAA;AAA0D,UAAA;AACJ,UAAA;AAAY,UAAA;AAAK,UAAA;AACvE,QAAA;AAIF,wBAAA;AAAC,UAAA;AAAA,UAAA;AACe,YAAA;AACP,YAAA;AACL,cAAA;AACF,YAAA;AACa,YAAA;AAEZ,YAAA;AAAA,UAAA;AACH,QAAA;AAAA,MAAA;AAAA,IAAA;AAGF,EAAA;AAEJ;AAKgB;AACR,EAAA;AAEkB,EAAA;AACT,IAAA;AACG,IAAA;AACK,IAAA;AACD,IAAA;AACtB,EAAA;AAE0B,EAAA;AACT,IAAA;AACU,MAAA;AACzB,IAAA;AACe,IAAA;AACjB,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAcgB;AACd,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACmC;AAGhB,EAAA;AACO,EAAA;AACC,EAAA;AAErB,EAAA;AACgB,EAAA;AAGpB,EAAA;AAAC,IAAA;AAAA,IAAA;AACY,MAAA;AACT,QAAA;AACC,QAAA;AACD,QAAA;AACF,MAAA;AACO,MAAA;AACI,QAAA;AACW,QAAA;AACtB,MAAA;AACK,MAAA;AACM,MAAA;AACA,MAAA;AACG,MAAA;AACI,MAAA;AAGhB,MAAA;AAAc,QAAA;AAShB,wBAAA;AAAC,UAAA;AAAA,UAAA;AACe,YAAA;AACP,YAAA;AACL,cAAA;AACF,YAAA;AACa,YAAA;AAKb,YAAA;AAAC,cAAA;AAAA,cAAA;AACc,gBAAA;AACD,gBAAA;AACZ,gBAAA;AAAiD,gBAAA;AAAA,cAAA;AACnD,YAAA;AAAA,UAAA;AACF,QAAA;AAAA,MAAA;AAAA,IAAA;AAGF,EAAA;AAEJ;ADvF6B;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-N3526CTH.cjs","sourcesContent":[null,"'use client';\n\nimport type { ReactNode } from 'react';\nimport { cn } from '../utils/cn';\nimport { UnifiedPagination } from './unified-pagination';\n\ninterface PersistentPaginationProps {\n /**\n * Whether pagination is currently in a loading state\n */\n isLoading: boolean;\n /**\n * The pagination component\n */\n children: ReactNode;\n /**\n * Current page number\n */\n currentPage: number;\n /**\n * Total number of pages\n */\n totalPages: number;\n /**\n * Additional CSS classes\n */\n className?: string;\n /**\n * Disabled opacity (0-1)\n */\n disabledOpacity?: number;\n /**\n * Transition duration in milliseconds\n */\n transitionDuration?: number;\n /**\n * Whether to show loading state in pagination\n */\n showLoadingState?: boolean;\n}\n\n/**\n * PersistentPagination\n *\n * A wrapper component that keeps pagination visible during loading states\n * but provides visual feedback that it's temporarily disabled.\n *\n * Features:\n * - Maintains pagination visibility during content loading\n * - Disables interaction but preserves layout\n * - Shows current page context even when loading\n * - Provides accessibility support for loading states\n * - Smooth transitions between enabled/disabled states\n *\n * Usage:\n * ```tsx\n * <PersistentPagination\n * isLoading={isLoadingVendors}\n * currentPage={currentPage}\n * totalPages={totalPages}\n * >\n * <Pagination currentPage={currentPage} totalPages={totalPages} />\n * </PersistentPagination>\n * ```\n */\nexport function PersistentPagination({\n isLoading,\n children,\n currentPage,\n totalPages,\n className,\n disabledOpacity = 0.5,\n transitionDuration = 300,\n}: PersistentPaginationProps) {\n // ALWAYS show pagination for predictable layout - just gray it out when not needed\n // Removed condition that hides pagination completely\n\n return (\n <div\n className={cn(\n 'relative transition-all ease-in-out',\n 'flex items-center justify-center',\n isLoading && 'pointer-events-none',\n className,\n )}\n style={{\n opacity: isLoading ? disabledOpacity : 1,\n transitionDuration: `${transitionDuration}ms`,\n }}\n role=\"navigation\"\n aria-label=\"Pagination\"\n aria-busy={isLoading}\n data-loading={isLoading}\n >\n {/* Loading state announcement for screen readers */}\n {isLoading && (\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n Pagination temporarily disabled while loading page {currentPage} of {totalPages}\n </div>\n )}\n\n {/* Pagination controls with disabled state */}\n <div\n className={cn('relative transition-all ease-in-out', isLoading && 'cursor-not-allowed')}\n style={{\n transitionDuration: `${transitionDuration}ms`,\n }}\n aria-hidden={isLoading}\n >\n {children}\n </div>\n\n {/* REMOVED: Loading overlay - only card skeletons should show during loading */}\n </div>\n );\n}\n\n/**\n * Hook for managing pagination loading states\n */\nexport function usePaginationLoading(isLoading: boolean, currentPage: number, totalPages: number) {\n const shouldShowPagination = totalPages > 1 || isLoading;\n\n const paginationProps = {\n 'aria-busy': isLoading,\n 'data-loading': isLoading,\n 'data-current-page': currentPage,\n 'data-total-pages': totalPages,\n };\n\n const getLoadingMessage = () => {\n if (isLoading) {\n return `Loading page ${currentPage} of ${totalPages}`;\n }\n return `Page ${currentPage} of ${totalPages}`;\n };\n\n return {\n shouldShowPagination,\n paginationProps,\n getLoadingMessage,\n };\n}\n\n/**\n * Enhanced pagination component that includes loading states\n */\ninterface PersistentPaginationWrapperProps {\n isLoading: boolean;\n currentPage: number;\n totalPages: number;\n onPageChange?: (page: number) => void;\n className?: string;\n variant?: 'vendor' | 'blog';\n}\n\nexport function PersistentPaginationWrapper({\n isLoading,\n currentPage,\n totalPages,\n onPageChange,\n className,\n}: PersistentPaginationWrapperProps) {\n // ALWAYS show pagination for predictable layout\n // For no results (totalPages = 0), show grayed out pagination\n const hasResults = totalPages > 0;\n const displayTotalPages = hasResults ? totalPages : 1;\n const displayCurrentPage = hasResults ? currentPage : 1;\n // Only disable during loading, but still show when no results (just grayed out)\n const isPaginationDisabled = isLoading;\n const hasNoResults = !hasResults && !isLoading;\n\n return (\n <div\n className={cn(\n 'relative flex items-center justify-center transition-all ease-in-out',\n (isPaginationDisabled || hasNoResults) && 'pointer-events-none',\n className,\n )}\n style={{\n opacity: isPaginationDisabled ? 0.3 : hasNoResults ? 0.5 : 1,\n transitionDuration: '300ms',\n }}\n role=\"navigation\"\n aria-label=\"Pagination\"\n aria-busy={isLoading}\n data-loading={isLoading}\n data-has-results={hasResults}\n >\n {/* Loading/no results state announcement for screen readers */}\n {(isLoading || !hasResults) && (\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {isLoading\n ? `Pagination temporarily disabled while loading page ${displayCurrentPage} of ${displayTotalPages}`\n : `No results available - pagination disabled`}\n </div>\n )}\n\n {/* Pagination controls with disabled state */}\n <div\n className={cn('relative transition-all ease-in-out', isPaginationDisabled && 'cursor-not-allowed')}\n style={{\n transitionDuration: '300ms',\n }}\n aria-hidden={isPaginationDisabled}\n >\n {/* UnifiedPagination directly — Pagination and BlogPagination in\n multi-platform-hub are both thin wrappers around it, so the wrapper\n never has to be handed down from the consuming app. */}\n <UnifiedPagination\n currentPage={displayCurrentPage}\n totalPages={displayTotalPages}\n onPageChange={hasResults ? onPageChange : () => {}} // Provide empty function instead of undefined\n />\n </div>\n\n {/* REMOVED: Loading overlays - only card skeletons should show during loading */}\n </div>\n );\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunkVLWUKFQEcjs = require('./chunk-VLWUKFQE.cjs');
4
+ var _chunkCWXRZVDIcjs = require('./chunk-CWXRZVDI.cjs');
5
5
 
6
6
 
7
7
  var _chunkQRA2JXE4cjs = require('./chunk-QRA2JXE4.cjs');
@@ -130,7 +130,7 @@ function useSectionNavigation(sections, options) {
130
130
  // src/components/navigation/multi-level-navigation.tsx
131
131
  var _lucidereact = require('lucide-react');
132
132
 
133
- var DEFAULT_FOLDER_INDEX_FILE2 = _chunkVLWUKFQEcjs.DEFAULT_FOLDER_INDEX_FILE;
133
+ var DEFAULT_FOLDER_INDEX_FILE2 = _chunkCWXRZVDIcjs.DEFAULT_FOLDER_INDEX_FILE;
134
134
  function isNodeVisuallySelected(node, selectedPath, folderIndexFile = DEFAULT_FOLDER_INDEX_FILE2) {
135
135
  const FOLDER_INDEX_FILE = folderIndexFile.toLowerCase();
136
136
  if (selectedPath === node.path) {
@@ -358,4 +358,4 @@ function MobileNavigationItem({
358
358
 
359
359
 
360
360
  exports.StickySectionNav = StickySectionNav; exports.useSectionNavigation = useSectionNavigation; exports.MultiLevelNavigation = MultiLevelNavigation; exports.MobileNavigationDropdown = MobileNavigationDropdown;
361
- //# sourceMappingURL=chunk-D7BFS7N6.cjs.map
361
+ //# sourceMappingURL=chunk-N4S7RBUM.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-D7BFS7N6.cjs","../src/components/navigation/sticky-section-nav.tsx","../src/components/navigation/multi-level-navigation.tsx"],"names":["jsx","DEFAULT_FOLDER_INDEX_FILE"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-D7BFS7N6.cjs","sourcesContent":[null,"'use client';\n\nimport type React from 'react';\nimport { useEffect, useState, useCallback, useRef } from 'react';\nimport { cn } from '../../utils';\nimport { scrollElementIntoView } from '../../utils/scroll-into-view';\n\nexport interface StickyNavSection {\n id: string;\n label: string;\n}\n\ninterface StickySectionNavProps {\n sections: StickyNavSection[];\n activeSection: string;\n onSectionClick: (sectionId: string) => void;\n className?: string;\n ribbonPosition?: 'left' | 'right';\n ribbonColor?: string;\n}\n\n/**\n * Reusable sticky navigation component for section-based navigation\n * Used in vendor detail pages, knowledge base, documentation, etc.\n */\nexport function StickySectionNav({\n sections,\n activeSection,\n onSectionClick,\n className,\n ribbonPosition = 'left',\n ribbonColor = 'var(--color-accent-primary)',\n}: StickySectionNavProps) {\n const navHeight = sections.length * 40; // 40px per item (h-10)\n\n return (\n <nav className={cn('relative bg-ods-bg', className)}>\n {/* Background gray vertical line for all nav items */}\n <div\n className=\"absolute bg-ods-border\"\n style={{\n width: '1px',\n height: `${navHeight}px`,\n [ribbonPosition === 'left' ? 'left' : 'right']: '-2px',\n top: '0px',\n }}\n />\n\n {sections.map(section => (\n <div key={section.id} className=\"relative flex h-10 w-full items-stretch transition-all duration-200\">\n {/* Yellow ribbon for active state */}\n {activeSection === section.id && (\n <div\n className=\"absolute z-10 transition-all duration-200\"\n style={{\n backgroundColor: ribbonColor,\n width: '4px',\n height: '24px',\n [ribbonPosition === 'left' ? 'left' : 'right']: '-2px',\n top: '8px',\n borderRadius: '2px',\n }}\n />\n )}\n\n {/* Navigation button */}\n <button\n onClick={() => onSectionClick(section.id)}\n // The ribbon is the visual; this is the same state for assistive\n // tech (a table of contents marks the reader's current location).\n aria-current={activeSection === section.id ? 'location' : undefined}\n className=\"relative flex flex-1 cursor-pointer items-center gap-2 px-3 py-2\"\n >\n <span\n className={cn(\n 'text-left transition-all duration-200 text-h6',\n activeSection === section.id\n ? 'text-ods-text-primary'\n : 'text-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {section.label}\n </span>\n </button>\n </div>\n ))}\n </nav>\n );\n}\n\n/**\n * SIMPLEST POSSIBLE IMPLEMENTATION - Just make it work\n */\nexport function useSectionNavigation(\n sections: { id: string; ref: React.RefObject<HTMLElement> }[],\n options?: {\n offset?: number;\n },\n) {\n const [activeSection, setActiveSection] = useState(sections[0]?.id || '');\n const isScrollingFromClick = useRef(false);\n const { offset = 100 } = options || {};\n\n // Handle click - scroll to the element via the canonical helper.\n // The `offset` prop maps to `headerOffset` (sticky chrome above the\n // section nav); same smooth-scroll mechanics every other anchor\n // surface in the app uses.\n const handleSectionClick = useCallback(\n (sectionId: string) => {\n const targetElement = document.getElementById(sectionId);\n if (!targetElement) return;\n\n // Prevent scroll spy while we're scrolling\n isScrollingFromClick.current = true;\n setActiveSection(sectionId);\n\n scrollElementIntoView(targetElement, { headerOffset: offset });\n\n // Allow scroll spy again after scroll completes\n setTimeout(() => {\n isScrollingFromClick.current = false;\n }, 500);\n },\n [offset],\n );\n\n // Make sure elements have IDs\n useEffect(() => {\n sections.forEach(section => {\n const el = section.ref.current;\n if (el && !el.id) {\n el.id = section.id;\n }\n });\n }, [sections]);\n\n // Simple scroll spy\n useEffect(() => {\n const handleScroll = () => {\n if (isScrollingFromClick.current) return;\n\n const scrollPosition = window.scrollY + offset + 50;\n\n // Find which section we're in\n let currentSection = sections[0]?.id || '';\n\n for (let i = sections.length - 1; i >= 0; i--) {\n const element = document.getElementById(sections[i].id);\n if (element && scrollPosition >= element.offsetTop) {\n currentSection = sections[i].id;\n break;\n }\n }\n\n setActiveSection(currentSection);\n };\n\n // Throttle the scroll handler\n let scrollTimer: NodeJS.Timeout;\n const throttledScroll = () => {\n clearTimeout(scrollTimer);\n scrollTimer = setTimeout(handleScroll, 100);\n };\n\n window.addEventListener('scroll', throttledScroll);\n handleScroll(); // Check initial position\n\n return () => {\n window.removeEventListener('scroll', throttledScroll);\n clearTimeout(scrollTimer);\n };\n }, [sections, offset]);\n\n return {\n activeSection,\n handleSectionClick,\n };\n}\n","'use client';\n\nimport { ChevronDown, ChevronUp, FileText, Folder, FolderOpen } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { DEFAULT_FOLDER_INDEX_FILE as CANONICAL_FOLDER_INDEX_FILE } from '../../utils/doc-tree-nav';\n\nexport interface NavigationNode {\n id: string;\n name: string;\n path: string;\n type: 'file' | 'folder';\n hasReadme?: boolean;\n children?: NavigationNode[];\n slug?: string;\n // Mirror DocNode's extra optional fields so a DocNode tree is structurally\n // assignable to NavigationNode[] without an `as` cast. NavigationNode\n // intentionally never reads these — they're carried for type-compat only.\n documentType?: 'markdown' | 'pdf' | 'google_sheet' | 'figma' | 'file';\n sortOrder?: number;\n}\n\ninterface MultiLevelNavigationProps {\n nodes: NavigationNode[];\n selectedPath: string;\n expandedNodes: Set<string>;\n onNodeClick: (node: NavigationNode) => void;\n onToggleExpand?: (nodeId: string) => void;\n isLoading?: boolean;\n className?: string;\n /** Folder-index filename (default `'README.md'`, case-insensitive). When the\n * selectedPath is a folder with this file, the visual ribbon moves to the\n * child file. */\n folderIndexFile?: string;\n}\n\n// SSOT lives in `doc-tree-nav` (canonical case `'README.md'`). The visual\n// comparator below lowercases both sides, so re-exporting the canonical\n// constant under the same local name preserves call-site syntax without\n// drifting from the SSOT.\nconst DEFAULT_FOLDER_INDEX_FILE = CANONICAL_FOLDER_INDEX_FILE;\n\n/**\n * Compute the visual \"selected\" state for a navigation node.\n *\n * Folder-with-README convention: when `selectedPath` is a folder that has a\n * README, the FOLDER doesn't get the ribbon — its README child does. The folder\n * is just a container; the README is the actual document being viewed.\n *\n * Mirror case: a README file whose parent folder is the `selectedPath` gets the\n * visual selection ribbon (implicit). This handles both the auto-select-first-\n * folder landing path AND the user-clicks-folder case.\n */\nfunction isNodeVisuallySelected(\n node: NavigationNode,\n selectedPath: string,\n folderIndexFile: string = DEFAULT_FOLDER_INDEX_FILE,\n): boolean {\n const FOLDER_INDEX_FILE = folderIndexFile.toLowerCase();\n // Path comparisons keyed on `node.path` (raw, includes `.md`). Do NOT use\n // `node.name` — the tree-builder runs it through `formatDocName` which strips\n // the `.md` extension, so `node.name.toLowerCase() === 'readme.md'` is always\n // false.\n if (selectedPath === node.path) {\n // Explicit match — but folder-with-README defers to its README child.\n return !(node.type === 'folder' && node.hasReadme);\n }\n if (node.type !== 'file') return false;\n const pathLower = node.path.toLowerCase();\n // Implicit: README child whose parent folder is the selectedPath.\n if (selectedPath !== '' && pathLower === `${selectedPath.toLowerCase()}/${FOLDER_INDEX_FILE}`) {\n return true;\n }\n // Implicit: ROOT README on the landing page (selectedPath === '' means\n // \"no explicit doc selected\" — the viewer falls back to the root folder-\n // index file, so that file IS the active document).\n if (selectedPath === '' && pathLower === FOLDER_INDEX_FILE) {\n return true;\n }\n return false;\n}\n\nexport function MultiLevelNavigation({\n nodes,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n isLoading,\n className,\n folderIndexFile = DEFAULT_FOLDER_INDEX_FILE,\n}: MultiLevelNavigationProps) {\n if (isLoading) {\n return (\n <div className={cn('space-y-2', className)}>\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"h-12 animate-pulse rounded-lg bg-ods-skeleton p-3\" />\n ))}\n </div>\n );\n }\n\n return (\n <div className={cn('space-y-2', className)} role=\"list\" aria-label=\"Navigation list\">\n {nodes.map(node => (\n <NavigationItem\n key={node.id}\n node={node}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={0}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n );\n}\n\ninterface NavigationItemProps {\n node: NavigationNode;\n selectedPath: string;\n expandedNodes: Set<string>;\n onNodeClick: (node: NavigationNode) => void;\n onToggleExpand?: (nodeId: string) => void;\n level: number;\n folderIndexFile: string;\n}\n\nfunction NavigationItem({\n node,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n level,\n folderIndexFile,\n}: NavigationItemProps) {\n const isExpanded = expandedNodes.has(node.id);\n const isSelected = isNodeVisuallySelected(node, selectedPath, folderIndexFile);\n // Bind the (optional) child list once so the render below maps a real array\n // rather than re-deriving `hasChildren` and asserting it away.\n const children = node.children ?? [];\n const hasChildren = children.length > 0;\n\n return (\n <div className=\"space-y-1\" role=\"listitem\">\n <div className={cn('px-2', level > 0 && 'ml-4')}>\n <div\n className={cn(\n 'relative w-full rounded-lg border transition-all duration-150',\n isSelected ? 'border-ods-border bg-ods-border' : 'border-ods-border bg-ods-card',\n )}\n >\n <div className=\"relative flex items-center\">\n <button\n className={cn(\n 'flex h-12 w-full items-center rounded-lg px-2 text-ods-text-primary transition-all duration-150 text-h6',\n !isSelected && 'hover:bg-ods-bg-hover',\n hasChildren && 'pr-12',\n )}\n onClick={() => onNodeClick(node)}\n aria-label={`${isSelected ? 'Selected' : 'Select'} ${node.name}`}\n >\n <span className=\"mr-2 flex-shrink-0\">\n {node.type === 'folder' ? (\n isExpanded ? (\n <FolderOpen className=\"h-4 w-4\" />\n ) : (\n <Folder className=\"h-4 w-4\" />\n )\n ) : (\n <FileText className=\"h-4 w-4\" />\n )}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-left\">\n {node.name.endsWith('.md') ? node.name.replace('.md', '') : node.name}\n </span>\n {node.type === 'folder' && node.hasReadme && (\n <span className=\"mr-2 rounded bg-ods-bg-surface px-1.5 py-0.5 text-ods-text-secondary text-h6\">\n README\n </span>\n )}\n </button>\n\n {hasChildren && (\n <button\n className=\"absolute right-0 top-0 flex h-12 w-12 items-center justify-center text-ods-text-secondary transition-colors duration-150 hover:text-ods-text-primary\"\n onClick={e => {\n e.stopPropagation();\n if (onToggleExpand) {\n onToggleExpand(node.id);\n } else {\n onNodeClick(node);\n }\n }}\n aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${node.name}`}\n >\n {isExpanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n )}\n </div>\n\n {isSelected && (\n <div className=\"absolute right-0 top-1/2 h-[calc(100%-8px)] w-1 -translate-y-1/2 rounded-l bg-ods-accent\" />\n )}\n </div>\n </div>\n\n {hasChildren && isExpanded && (\n <div className=\"space-y-1\">\n {children.map(child => (\n <NavigationItem\n key={child.id}\n node={child}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={level + 1}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function MobileNavigationDropdown({\n nodes,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n isLoading,\n className,\n folderIndexFile = DEFAULT_FOLDER_INDEX_FILE,\n}: MultiLevelNavigationProps) {\n if (isLoading) {\n return (\n <div className={cn('space-y-2', className)}>\n {[1, 2, 3].map(i => (\n <div key={i} className=\"h-10 animate-pulse rounded-lg bg-ods-skeleton p-3\" />\n ))}\n </div>\n );\n }\n\n return (\n <div className={cn('space-y-2', className)}>\n {nodes.map(node => (\n <MobileNavigationItem\n key={node.id}\n node={node}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={0}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n );\n}\n\nfunction MobileNavigationItem({\n node,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n level,\n folderIndexFile,\n}: NavigationItemProps) {\n const isExpanded = expandedNodes.has(node.id);\n const isSelected = isNodeVisuallySelected(node, selectedPath, folderIndexFile);\n // Same binding as the desktop item above — a real array to map, no assertion.\n const children = node.children ?? [];\n const hasChildren = children.length > 0;\n\n return (\n <div className=\"space-y-1\">\n <div className={cn('px-2', level > 0 && 'ml-3')}>\n <div\n className={cn(\n 'relative w-full rounded-lg border transition-all duration-150',\n isSelected ? 'border-ods-border bg-ods-border' : 'border-ods-border bg-ods-card',\n )}\n >\n <div className=\"relative flex items-center\">\n <button\n className={cn(\n 'flex h-11 w-full items-center rounded-lg px-2 text-ods-text-primary transition-all duration-150 text-h6',\n !isSelected && 'hover:bg-ods-bg-hover',\n hasChildren && 'pr-11',\n )}\n onClick={() => onNodeClick(node)}\n >\n <span className=\"mr-2 flex-shrink-0\">\n {node.type === 'folder' ? (\n isExpanded ? (\n <FolderOpen className=\"h-4 w-4\" />\n ) : (\n <Folder className=\"h-4 w-4\" />\n )\n ) : (\n <FileText className=\"h-4 w-4\" />\n )}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-left\">\n {node.name.endsWith('.md') ? node.name.replace('.md', '') : node.name}\n </span>\n {node.type === 'folder' && node.hasReadme && (\n <span className=\"mr-2 rounded bg-ods-bg-surface px-1.5 py-0.5 text-ods-text-secondary text-h6\">\n README\n </span>\n )}\n </button>\n\n {hasChildren && (\n <button\n className=\"absolute right-0 top-0 flex h-11 w-11 items-center justify-center text-ods-text-secondary transition-colors duration-150 hover:text-ods-text-primary\"\n onClick={e => {\n e.stopPropagation();\n if (onToggleExpand) {\n onToggleExpand(node.id);\n } else {\n onNodeClick(node);\n }\n }}\n aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${node.name}`}\n >\n {isExpanded ? <ChevronUp className=\"h-3.5 w-3.5\" /> : <ChevronDown className=\"h-3.5 w-3.5\" />}\n </button>\n )}\n </div>\n\n {isSelected && (\n <div className=\"absolute right-0 top-1/2 h-[calc(100%-8px)] w-1 -translate-y-1/2 rounded-l bg-ods-accent\" />\n )}\n </div>\n </div>\n\n {hasChildren && isExpanded && (\n <div className=\"space-y-1\">\n {children.map(child => (\n <MobileNavigationItem\n key={child.id}\n node={child}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={level + 1}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-N4S7RBUM.cjs","../src/components/navigation/sticky-section-nav.tsx","../src/components/navigation/multi-level-navigation.tsx"],"names":["jsx","DEFAULT_FOLDER_INDEX_FILE"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-N4S7RBUM.cjs","sourcesContent":[null,"'use client';\n\nimport type React from 'react';\nimport { useEffect, useState, useCallback, useRef } from 'react';\nimport { cn } from '../../utils';\nimport { scrollElementIntoView } from '../../utils/scroll-into-view';\n\nexport interface StickyNavSection {\n id: string;\n label: string;\n}\n\ninterface StickySectionNavProps {\n sections: StickyNavSection[];\n activeSection: string;\n onSectionClick: (sectionId: string) => void;\n className?: string;\n ribbonPosition?: 'left' | 'right';\n ribbonColor?: string;\n}\n\n/**\n * Reusable sticky navigation component for section-based navigation\n * Used in vendor detail pages, knowledge base, documentation, etc.\n */\nexport function StickySectionNav({\n sections,\n activeSection,\n onSectionClick,\n className,\n ribbonPosition = 'left',\n ribbonColor = 'var(--color-accent-primary)',\n}: StickySectionNavProps) {\n const navHeight = sections.length * 40; // 40px per item (h-10)\n\n return (\n <nav className={cn('relative bg-ods-bg', className)}>\n {/* Background gray vertical line for all nav items */}\n <div\n className=\"absolute bg-ods-border\"\n style={{\n width: '1px',\n height: `${navHeight}px`,\n [ribbonPosition === 'left' ? 'left' : 'right']: '-2px',\n top: '0px',\n }}\n />\n\n {sections.map(section => (\n <div key={section.id} className=\"relative flex h-10 w-full items-stretch transition-all duration-200\">\n {/* Yellow ribbon for active state */}\n {activeSection === section.id && (\n <div\n className=\"absolute z-10 transition-all duration-200\"\n style={{\n backgroundColor: ribbonColor,\n width: '4px',\n height: '24px',\n [ribbonPosition === 'left' ? 'left' : 'right']: '-2px',\n top: '8px',\n borderRadius: '2px',\n }}\n />\n )}\n\n {/* Navigation button */}\n <button\n onClick={() => onSectionClick(section.id)}\n // The ribbon is the visual; this is the same state for assistive\n // tech (a table of contents marks the reader's current location).\n aria-current={activeSection === section.id ? 'location' : undefined}\n className=\"relative flex flex-1 cursor-pointer items-center gap-2 px-3 py-2\"\n >\n <span\n className={cn(\n 'text-left transition-all duration-200 text-h6',\n activeSection === section.id\n ? 'text-ods-text-primary'\n : 'text-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {section.label}\n </span>\n </button>\n </div>\n ))}\n </nav>\n );\n}\n\n/**\n * SIMPLEST POSSIBLE IMPLEMENTATION - Just make it work\n */\nexport function useSectionNavigation(\n sections: { id: string; ref: React.RefObject<HTMLElement> }[],\n options?: {\n offset?: number;\n },\n) {\n const [activeSection, setActiveSection] = useState(sections[0]?.id || '');\n const isScrollingFromClick = useRef(false);\n const { offset = 100 } = options || {};\n\n // Handle click - scroll to the element via the canonical helper.\n // The `offset` prop maps to `headerOffset` (sticky chrome above the\n // section nav); same smooth-scroll mechanics every other anchor\n // surface in the app uses.\n const handleSectionClick = useCallback(\n (sectionId: string) => {\n const targetElement = document.getElementById(sectionId);\n if (!targetElement) return;\n\n // Prevent scroll spy while we're scrolling\n isScrollingFromClick.current = true;\n setActiveSection(sectionId);\n\n scrollElementIntoView(targetElement, { headerOffset: offset });\n\n // Allow scroll spy again after scroll completes\n setTimeout(() => {\n isScrollingFromClick.current = false;\n }, 500);\n },\n [offset],\n );\n\n // Make sure elements have IDs\n useEffect(() => {\n sections.forEach(section => {\n const el = section.ref.current;\n if (el && !el.id) {\n el.id = section.id;\n }\n });\n }, [sections]);\n\n // Simple scroll spy\n useEffect(() => {\n const handleScroll = () => {\n if (isScrollingFromClick.current) return;\n\n const scrollPosition = window.scrollY + offset + 50;\n\n // Find which section we're in\n let currentSection = sections[0]?.id || '';\n\n for (let i = sections.length - 1; i >= 0; i--) {\n const element = document.getElementById(sections[i].id);\n if (element && scrollPosition >= element.offsetTop) {\n currentSection = sections[i].id;\n break;\n }\n }\n\n setActiveSection(currentSection);\n };\n\n // Throttle the scroll handler\n let scrollTimer: NodeJS.Timeout;\n const throttledScroll = () => {\n clearTimeout(scrollTimer);\n scrollTimer = setTimeout(handleScroll, 100);\n };\n\n window.addEventListener('scroll', throttledScroll);\n handleScroll(); // Check initial position\n\n return () => {\n window.removeEventListener('scroll', throttledScroll);\n clearTimeout(scrollTimer);\n };\n }, [sections, offset]);\n\n return {\n activeSection,\n handleSectionClick,\n };\n}\n","'use client';\n\nimport { ChevronDown, ChevronUp, FileText, Folder, FolderOpen } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { DEFAULT_FOLDER_INDEX_FILE as CANONICAL_FOLDER_INDEX_FILE } from '../../utils/doc-tree-nav';\n\nexport interface NavigationNode {\n id: string;\n name: string;\n path: string;\n type: 'file' | 'folder';\n hasReadme?: boolean;\n children?: NavigationNode[];\n slug?: string;\n // Mirror DocNode's extra optional fields so a DocNode tree is structurally\n // assignable to NavigationNode[] without an `as` cast. NavigationNode\n // intentionally never reads these — they're carried for type-compat only.\n documentType?: 'markdown' | 'pdf' | 'google_sheet' | 'figma' | 'file';\n sortOrder?: number;\n}\n\ninterface MultiLevelNavigationProps {\n nodes: NavigationNode[];\n selectedPath: string;\n expandedNodes: Set<string>;\n onNodeClick: (node: NavigationNode) => void;\n onToggleExpand?: (nodeId: string) => void;\n isLoading?: boolean;\n className?: string;\n /** Folder-index filename (default `'README.md'`, case-insensitive). When the\n * selectedPath is a folder with this file, the visual ribbon moves to the\n * child file. */\n folderIndexFile?: string;\n}\n\n// SSOT lives in `doc-tree-nav` (canonical case `'README.md'`). The visual\n// comparator below lowercases both sides, so re-exporting the canonical\n// constant under the same local name preserves call-site syntax without\n// drifting from the SSOT.\nconst DEFAULT_FOLDER_INDEX_FILE = CANONICAL_FOLDER_INDEX_FILE;\n\n/**\n * Compute the visual \"selected\" state for a navigation node.\n *\n * Folder-with-README convention: when `selectedPath` is a folder that has a\n * README, the FOLDER doesn't get the ribbon — its README child does. The folder\n * is just a container; the README is the actual document being viewed.\n *\n * Mirror case: a README file whose parent folder is the `selectedPath` gets the\n * visual selection ribbon (implicit). This handles both the auto-select-first-\n * folder landing path AND the user-clicks-folder case.\n */\nfunction isNodeVisuallySelected(\n node: NavigationNode,\n selectedPath: string,\n folderIndexFile: string = DEFAULT_FOLDER_INDEX_FILE,\n): boolean {\n const FOLDER_INDEX_FILE = folderIndexFile.toLowerCase();\n // Path comparisons keyed on `node.path` (raw, includes `.md`). Do NOT use\n // `node.name` — the tree-builder runs it through `formatDocName` which strips\n // the `.md` extension, so `node.name.toLowerCase() === 'readme.md'` is always\n // false.\n if (selectedPath === node.path) {\n // Explicit match — but folder-with-README defers to its README child.\n return !(node.type === 'folder' && node.hasReadme);\n }\n if (node.type !== 'file') return false;\n const pathLower = node.path.toLowerCase();\n // Implicit: README child whose parent folder is the selectedPath.\n if (selectedPath !== '' && pathLower === `${selectedPath.toLowerCase()}/${FOLDER_INDEX_FILE}`) {\n return true;\n }\n // Implicit: ROOT README on the landing page (selectedPath === '' means\n // \"no explicit doc selected\" — the viewer falls back to the root folder-\n // index file, so that file IS the active document).\n if (selectedPath === '' && pathLower === FOLDER_INDEX_FILE) {\n return true;\n }\n return false;\n}\n\nexport function MultiLevelNavigation({\n nodes,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n isLoading,\n className,\n folderIndexFile = DEFAULT_FOLDER_INDEX_FILE,\n}: MultiLevelNavigationProps) {\n if (isLoading) {\n return (\n <div className={cn('space-y-2', className)}>\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"h-12 animate-pulse rounded-lg bg-ods-skeleton p-3\" />\n ))}\n </div>\n );\n }\n\n return (\n <div className={cn('space-y-2', className)} role=\"list\" aria-label=\"Navigation list\">\n {nodes.map(node => (\n <NavigationItem\n key={node.id}\n node={node}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={0}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n );\n}\n\ninterface NavigationItemProps {\n node: NavigationNode;\n selectedPath: string;\n expandedNodes: Set<string>;\n onNodeClick: (node: NavigationNode) => void;\n onToggleExpand?: (nodeId: string) => void;\n level: number;\n folderIndexFile: string;\n}\n\nfunction NavigationItem({\n node,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n level,\n folderIndexFile,\n}: NavigationItemProps) {\n const isExpanded = expandedNodes.has(node.id);\n const isSelected = isNodeVisuallySelected(node, selectedPath, folderIndexFile);\n // Bind the (optional) child list once so the render below maps a real array\n // rather than re-deriving `hasChildren` and asserting it away.\n const children = node.children ?? [];\n const hasChildren = children.length > 0;\n\n return (\n <div className=\"space-y-1\" role=\"listitem\">\n <div className={cn('px-2', level > 0 && 'ml-4')}>\n <div\n className={cn(\n 'relative w-full rounded-lg border transition-all duration-150',\n isSelected ? 'border-ods-border bg-ods-border' : 'border-ods-border bg-ods-card',\n )}\n >\n <div className=\"relative flex items-center\">\n <button\n className={cn(\n 'flex h-12 w-full items-center rounded-lg px-2 text-ods-text-primary transition-all duration-150 text-h6',\n !isSelected && 'hover:bg-ods-bg-hover',\n hasChildren && 'pr-12',\n )}\n onClick={() => onNodeClick(node)}\n aria-label={`${isSelected ? 'Selected' : 'Select'} ${node.name}`}\n >\n <span className=\"mr-2 flex-shrink-0\">\n {node.type === 'folder' ? (\n isExpanded ? (\n <FolderOpen className=\"h-4 w-4\" />\n ) : (\n <Folder className=\"h-4 w-4\" />\n )\n ) : (\n <FileText className=\"h-4 w-4\" />\n )}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-left\">\n {node.name.endsWith('.md') ? node.name.replace('.md', '') : node.name}\n </span>\n {node.type === 'folder' && node.hasReadme && (\n <span className=\"mr-2 rounded bg-ods-bg-surface px-1.5 py-0.5 text-ods-text-secondary text-h6\">\n README\n </span>\n )}\n </button>\n\n {hasChildren && (\n <button\n className=\"absolute right-0 top-0 flex h-12 w-12 items-center justify-center text-ods-text-secondary transition-colors duration-150 hover:text-ods-text-primary\"\n onClick={e => {\n e.stopPropagation();\n if (onToggleExpand) {\n onToggleExpand(node.id);\n } else {\n onNodeClick(node);\n }\n }}\n aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${node.name}`}\n >\n {isExpanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n )}\n </div>\n\n {isSelected && (\n <div className=\"absolute right-0 top-1/2 h-[calc(100%-8px)] w-1 -translate-y-1/2 rounded-l bg-ods-accent\" />\n )}\n </div>\n </div>\n\n {hasChildren && isExpanded && (\n <div className=\"space-y-1\">\n {children.map(child => (\n <NavigationItem\n key={child.id}\n node={child}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={level + 1}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function MobileNavigationDropdown({\n nodes,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n isLoading,\n className,\n folderIndexFile = DEFAULT_FOLDER_INDEX_FILE,\n}: MultiLevelNavigationProps) {\n if (isLoading) {\n return (\n <div className={cn('space-y-2', className)}>\n {[1, 2, 3].map(i => (\n <div key={i} className=\"h-10 animate-pulse rounded-lg bg-ods-skeleton p-3\" />\n ))}\n </div>\n );\n }\n\n return (\n <div className={cn('space-y-2', className)}>\n {nodes.map(node => (\n <MobileNavigationItem\n key={node.id}\n node={node}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={0}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n );\n}\n\nfunction MobileNavigationItem({\n node,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n level,\n folderIndexFile,\n}: NavigationItemProps) {\n const isExpanded = expandedNodes.has(node.id);\n const isSelected = isNodeVisuallySelected(node, selectedPath, folderIndexFile);\n // Same binding as the desktop item above — a real array to map, no assertion.\n const children = node.children ?? [];\n const hasChildren = children.length > 0;\n\n return (\n <div className=\"space-y-1\">\n <div className={cn('px-2', level > 0 && 'ml-3')}>\n <div\n className={cn(\n 'relative w-full rounded-lg border transition-all duration-150',\n isSelected ? 'border-ods-border bg-ods-border' : 'border-ods-border bg-ods-card',\n )}\n >\n <div className=\"relative flex items-center\">\n <button\n className={cn(\n 'flex h-11 w-full items-center rounded-lg px-2 text-ods-text-primary transition-all duration-150 text-h6',\n !isSelected && 'hover:bg-ods-bg-hover',\n hasChildren && 'pr-11',\n )}\n onClick={() => onNodeClick(node)}\n >\n <span className=\"mr-2 flex-shrink-0\">\n {node.type === 'folder' ? (\n isExpanded ? (\n <FolderOpen className=\"h-4 w-4\" />\n ) : (\n <Folder className=\"h-4 w-4\" />\n )\n ) : (\n <FileText className=\"h-4 w-4\" />\n )}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-left\">\n {node.name.endsWith('.md') ? node.name.replace('.md', '') : node.name}\n </span>\n {node.type === 'folder' && node.hasReadme && (\n <span className=\"mr-2 rounded bg-ods-bg-surface px-1.5 py-0.5 text-ods-text-secondary text-h6\">\n README\n </span>\n )}\n </button>\n\n {hasChildren && (\n <button\n className=\"absolute right-0 top-0 flex h-11 w-11 items-center justify-center text-ods-text-secondary transition-colors duration-150 hover:text-ods-text-primary\"\n onClick={e => {\n e.stopPropagation();\n if (onToggleExpand) {\n onToggleExpand(node.id);\n } else {\n onNodeClick(node);\n }\n }}\n aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${node.name}`}\n >\n {isExpanded ? <ChevronUp className=\"h-3.5 w-3.5\" /> : <ChevronDown className=\"h-3.5 w-3.5\" />}\n </button>\n )}\n </div>\n\n {isSelected && (\n <div className=\"absolute right-0 top-1/2 h-[calc(100%-8px)] w-1 -translate-y-1/2 rounded-l bg-ods-accent\" />\n )}\n </div>\n </div>\n\n {hasChildren && isExpanded && (\n <div className=\"space-y-1\">\n {children.map(child => (\n <MobileNavigationItem\n key={child.id}\n node={child}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={level + 1}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}