@kywi-software/core 0.10.0 → 0.12.0

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 (248) hide show
  1. package/AGENT-PATTERNS.md +111 -23
  2. package/README.md +67 -0
  3. package/dist/admin/content-form.d.ts.map +1 -1
  4. package/dist/admin/content-form.js +97 -10
  5. package/dist/admin/content-form.js.map +1 -1
  6. package/dist/admin/layout-editor/component-actions.d.ts +219 -22
  7. package/dist/admin/layout-editor/component-actions.d.ts.map +1 -1
  8. package/dist/admin/layout-editor/component-actions.js +356 -36
  9. package/dist/admin/layout-editor/component-actions.js.map +1 -1
  10. package/dist/admin/layout-editor/component-fork.d.ts +224 -0
  11. package/dist/admin/layout-editor/component-fork.d.ts.map +1 -0
  12. package/dist/admin/layout-editor/component-fork.js +342 -0
  13. package/dist/admin/layout-editor/component-fork.js.map +1 -0
  14. package/dist/admin/layout-editor/editor-canvas.d.ts +28 -7
  15. package/dist/admin/layout-editor/editor-canvas.d.ts.map +1 -1
  16. package/dist/admin/layout-editor/editor-canvas.js +145 -32
  17. package/dist/admin/layout-editor/editor-canvas.js.map +1 -1
  18. package/dist/admin/layout-editor/editor-dialog.d.ts +164 -0
  19. package/dist/admin/layout-editor/editor-dialog.d.ts.map +1 -0
  20. package/dist/admin/layout-editor/editor-dialog.js +195 -0
  21. package/dist/admin/layout-editor/editor-dialog.js.map +1 -0
  22. package/dist/admin/layout-editor/editor-state.d.ts +76 -0
  23. package/dist/admin/layout-editor/editor-state.d.ts.map +1 -1
  24. package/dist/admin/layout-editor/editor-state.js +47 -1
  25. package/dist/admin/layout-editor/editor-state.js.map +1 -1
  26. package/dist/admin/layout-editor/editor.css +366 -4
  27. package/dist/admin/layout-editor/index.d.ts +10 -1
  28. package/dist/admin/layout-editor/index.d.ts.map +1 -1
  29. package/dist/admin/layout-editor/index.js +11 -1
  30. package/dist/admin/layout-editor/index.js.map +1 -1
  31. package/dist/admin/layout-editor/inline-text.d.ts +56 -4
  32. package/dist/admin/layout-editor/inline-text.d.ts.map +1 -1
  33. package/dist/admin/layout-editor/inline-text.js +134 -11
  34. package/dist/admin/layout-editor/inline-text.js.map +1 -1
  35. package/dist/admin/layout-editor/layout-editor.d.ts +58 -1
  36. package/dist/admin/layout-editor/layout-editor.d.ts.map +1 -1
  37. package/dist/admin/layout-editor/layout-editor.js +160 -190
  38. package/dist/admin/layout-editor/layout-editor.js.map +1 -1
  39. package/dist/admin/layout-editor/linked-component.d.ts +39 -0
  40. package/dist/admin/layout-editor/linked-component.d.ts.map +1 -0
  41. package/dist/admin/layout-editor/linked-component.js +42 -0
  42. package/dist/admin/layout-editor/linked-component.js.map +1 -0
  43. package/dist/admin/layout-editor/module-overlay.d.ts +36 -14
  44. package/dist/admin/layout-editor/module-overlay.d.ts.map +1 -1
  45. package/dist/admin/layout-editor/module-overlay.js +233 -25
  46. package/dist/admin/layout-editor/module-overlay.js.map +1 -1
  47. package/dist/admin/layout-editor/node-lookup.d.ts +35 -0
  48. package/dist/admin/layout-editor/node-lookup.d.ts.map +1 -1
  49. package/dist/admin/layout-editor/node-lookup.js +91 -0
  50. package/dist/admin/layout-editor/node-lookup.js.map +1 -1
  51. package/dist/admin/layout-editor/overlay-shell.d.ts.map +1 -1
  52. package/dist/admin/layout-editor/overlay-shell.js +150 -32
  53. package/dist/admin/layout-editor/overlay-shell.js.map +1 -1
  54. package/dist/admin/layout-editor/props-panel.d.ts +54 -1
  55. package/dist/admin/layout-editor/props-panel.d.ts.map +1 -1
  56. package/dist/admin/layout-editor/props-panel.js +119 -10
  57. package/dist/admin/layout-editor/props-panel.js.map +1 -1
  58. package/dist/admin/layout-editor/section-overlay.d.ts +16 -1
  59. package/dist/admin/layout-editor/section-overlay.d.ts.map +1 -1
  60. package/dist/admin/layout-editor/section-overlay.js +21 -11
  61. package/dist/admin/layout-editor/section-overlay.js.map +1 -1
  62. package/dist/admin/layout-editor/section-template-picker.d.ts +27 -1
  63. package/dist/admin/layout-editor/section-template-picker.d.ts.map +1 -1
  64. package/dist/admin/layout-editor/section-template-picker.js +71 -17
  65. package/dist/admin/layout-editor/section-template-picker.js.map +1 -1
  66. package/dist/admin/layout-editor/section-variant-container-config.js +5 -5
  67. package/dist/admin/layout-editor/section-variant-container-config.js.map +1 -1
  68. package/dist/admin/layout-editor/use-chrome-flip.d.ts +111 -11
  69. package/dist/admin/layout-editor/use-chrome-flip.d.ts.map +1 -1
  70. package/dist/admin/layout-editor/use-chrome-flip.js +233 -33
  71. package/dist/admin/layout-editor/use-chrome-flip.js.map +1 -1
  72. package/dist/admin/layout-editor/use-component-connect.d.ts +61 -0
  73. package/dist/admin/layout-editor/use-component-connect.d.ts.map +1 -0
  74. package/dist/admin/layout-editor/use-component-connect.js +292 -0
  75. package/dist/admin/layout-editor/use-component-connect.js.map +1 -0
  76. package/dist/admin/layout-editor/use-component-fork.d.ts +123 -0
  77. package/dist/admin/layout-editor/use-component-fork.d.ts.map +1 -0
  78. package/dist/admin/layout-editor/use-component-fork.js +346 -0
  79. package/dist/admin/layout-editor/use-component-fork.js.map +1 -0
  80. package/dist/admin/layout-editor/use-component-library.d.ts +94 -0
  81. package/dist/admin/layout-editor/use-component-library.d.ts.map +1 -0
  82. package/dist/admin/layout-editor/use-component-library.js +55 -0
  83. package/dist/admin/layout-editor/use-component-library.js.map +1 -0
  84. package/dist/admin/registry.d.ts +11 -2
  85. package/dist/admin/registry.d.ts.map +1 -1
  86. package/dist/admin/registry.js +40 -4
  87. package/dist/admin/registry.js.map +1 -1
  88. package/dist/admin/server.d.ts +1 -0
  89. package/dist/admin/server.d.ts.map +1 -1
  90. package/dist/admin/server.js +5 -0
  91. package/dist/admin/server.js.map +1 -1
  92. package/dist/admin/styles/admin.css +57 -1
  93. package/dist/admin/surfaces/components/component-edit.d.ts +3 -0
  94. package/dist/admin/surfaces/components/component-edit.d.ts.map +1 -0
  95. package/dist/admin/surfaces/components/component-edit.js +235 -0
  96. package/dist/admin/surfaces/components/component-edit.js.map +1 -0
  97. package/dist/admin/surfaces/components/components-data.d.ts +153 -0
  98. package/dist/admin/surfaces/components/components-data.d.ts.map +1 -0
  99. package/dist/admin/surfaces/components/components-data.js +154 -0
  100. package/dist/admin/surfaces/components/components-data.js.map +1 -0
  101. package/dist/admin/surfaces/components/components-list.d.ts +3 -0
  102. package/dist/admin/surfaces/components/components-list.d.ts.map +1 -0
  103. package/dist/admin/surfaces/components/components-list.js +196 -0
  104. package/dist/admin/surfaces/components/components-list.js.map +1 -0
  105. package/dist/admin/surfaces/components/definition-canvas.d.ts +73 -0
  106. package/dist/admin/surfaces/components/definition-canvas.d.ts.map +1 -0
  107. package/dist/admin/surfaces/components/definition-canvas.js +207 -0
  108. package/dist/admin/surfaces/components/definition-canvas.js.map +1 -0
  109. package/dist/admin/surfaces/layouts/layout-editor-client.d.ts.map +1 -1
  110. package/dist/admin/surfaces/layouts/layout-editor-client.js +14 -2
  111. package/dist/admin/surfaces/layouts/layout-editor-client.js.map +1 -1
  112. package/dist/admin/surfaces/layouts/layouts-data.d.ts +3 -14
  113. package/dist/admin/surfaces/layouts/layouts-data.d.ts.map +1 -1
  114. package/dist/admin/surfaces/layouts/layouts-data.js +4 -7
  115. package/dist/admin/surfaces/layouts/layouts-data.js.map +1 -1
  116. package/dist/admin/surfaces/layouts/layouts-list.d.ts.map +1 -1
  117. package/dist/admin/surfaces/layouts/layouts-list.js +16 -22
  118. package/dist/admin/surfaces/layouts/layouts-list.js.map +1 -1
  119. package/dist/admin/surfaces/settings/admin-features.d.ts.map +1 -1
  120. package/dist/admin/surfaces/settings/admin-features.js +1 -0
  121. package/dist/admin/surfaces/settings/admin-features.js.map +1 -1
  122. package/dist/api/bulk-routes.d.ts.map +1 -1
  123. package/dist/api/bulk-routes.js +25 -0
  124. package/dist/api/bulk-routes.js.map +1 -1
  125. package/dist/api/changeset-routes.d.ts.map +1 -1
  126. package/dist/api/changeset-routes.js +17 -3
  127. package/dist/api/changeset-routes.js.map +1 -1
  128. package/dist/api/component-routes.d.ts.map +1 -1
  129. package/dist/api/component-routes.js +228 -8
  130. package/dist/api/component-routes.js.map +1 -1
  131. package/dist/api/content-routes.d.ts.map +1 -1
  132. package/dist/api/content-routes.js +64 -0
  133. package/dist/api/content-routes.js.map +1 -1
  134. package/dist/api/feed-routes.d.ts +6 -6
  135. package/dist/api/graphql/resolvers.d.ts +4 -4
  136. package/dist/api/layout-routes.d.ts.map +1 -1
  137. package/dist/api/layout-routes.js +4 -17
  138. package/dist/api/layout-routes.js.map +1 -1
  139. package/dist/api/layout-write-guard.d.ts +80 -0
  140. package/dist/api/layout-write-guard.d.ts.map +1 -0
  141. package/dist/api/layout-write-guard.js +88 -0
  142. package/dist/api/layout-write-guard.js.map +1 -0
  143. package/dist/api/section-template-routes.d.ts.map +1 -1
  144. package/dist/api/section-template-routes.js +11 -0
  145. package/dist/api/section-template-routes.js.map +1 -1
  146. package/dist/api/translation-routes.d.ts.map +1 -1
  147. package/dist/api/translation-routes.js +8 -0
  148. package/dist/api/translation-routes.js.map +1 -1
  149. package/dist/api/version-routes.d.ts.map +1 -1
  150. package/dist/api/version-routes.js +20 -8
  151. package/dist/api/version-routes.js.map +1 -1
  152. package/dist/api/workflow-routes.d.ts.map +1 -1
  153. package/dist/api/workflow-routes.js +53 -8
  154. package/dist/api/workflow-routes.js.map +1 -1
  155. package/dist/db/analytics-queries.d.ts +3 -3
  156. package/dist/db/apply-snapshot.d.ts +75 -8
  157. package/dist/db/apply-snapshot.d.ts.map +1 -1
  158. package/dist/db/apply-snapshot.js +104 -8
  159. package/dist/db/apply-snapshot.js.map +1 -1
  160. package/dist/db/audit-queries.d.ts +12 -12
  161. package/dist/db/category-queries.d.ts +3 -3
  162. package/dist/db/changeset-audit-queries.d.ts +4 -4
  163. package/dist/db/changeset-queries.d.ts +7 -7
  164. package/dist/db/comment-queries.d.ts +8 -8
  165. package/dist/db/comment-subscription-queries.d.ts +2 -2
  166. package/dist/db/component-instance-queries.d.ts +197 -0
  167. package/dist/db/component-instance-queries.d.ts.map +1 -0
  168. package/dist/db/component-instance-queries.js +405 -0
  169. package/dist/db/component-instance-queries.js.map +1 -0
  170. package/dist/db/content-queries.d.ts.map +1 -1
  171. package/dist/db/content-queries.js +39 -2
  172. package/dist/db/content-queries.js.map +1 -1
  173. package/dist/db/extension-queries.d.ts +7 -7
  174. package/dist/db/feed-queries.d.ts +3 -3
  175. package/dist/db/index.d.ts +1 -0
  176. package/dist/db/index.d.ts.map +1 -1
  177. package/dist/db/index.js +1 -0
  178. package/dist/db/index.js.map +1 -1
  179. package/dist/db/layout-queries.d.ts +13 -1
  180. package/dist/db/layout-queries.d.ts.map +1 -1
  181. package/dist/db/layout-queries.js +52 -7
  182. package/dist/db/layout-queries.js.map +1 -1
  183. package/dist/db/locale-queries.d.ts.map +1 -1
  184. package/dist/db/locale-queries.js +0 -0
  185. package/dist/db/locale-queries.js.map +1 -1
  186. package/dist/db/media-queries.d.ts +12 -12
  187. package/dist/db/revision-queries.d.ts +3 -3
  188. package/dist/db/site-admin-queries.d.ts +2 -2
  189. package/dist/db/site-membership-queries.d.ts +3 -3
  190. package/dist/db/site-plugin-queries.d.ts +2 -2
  191. package/dist/db/site-queries.d.ts +6 -6
  192. package/dist/db/version-queries.d.ts +12 -12
  193. package/dist/db/webhook-queries.d.ts +11 -11
  194. package/dist/db/workflow-queries.d.ts +6 -6
  195. package/dist/db/workflow-queries.d.ts.map +1 -1
  196. package/dist/db/workflow-queries.js +11 -2
  197. package/dist/db/workflow-queries.js.map +1 -1
  198. package/dist/layout/comments-module.d.ts.map +1 -1
  199. package/dist/layout/comments-module.js +6 -2
  200. package/dist/layout/comments-module.js.map +1 -1
  201. package/dist/layout/component-refs.d.ts +465 -0
  202. package/dist/layout/component-refs.d.ts.map +1 -0
  203. package/dist/layout/component-refs.js +848 -0
  204. package/dist/layout/component-refs.js.map +1 -0
  205. package/dist/layout/index.d.ts +2 -1
  206. package/dist/layout/index.d.ts.map +1 -1
  207. package/dist/layout/index.js +1 -0
  208. package/dist/layout/index.js.map +1 -1
  209. package/dist/layout/module-permissions.d.ts +49 -1
  210. package/dist/layout/module-permissions.d.ts.map +1 -1
  211. package/dist/layout/module-permissions.js +117 -6
  212. package/dist/layout/module-permissions.js.map +1 -1
  213. package/dist/layout/module-render-context.d.ts +32 -0
  214. package/dist/layout/module-render-context.d.ts.map +1 -1
  215. package/dist/layout/module-render-context.js +32 -1
  216. package/dist/layout/module-render-context.js.map +1 -1
  217. package/dist/layout/modules-extended.d.ts.map +1 -1
  218. package/dist/layout/modules-extended.js +21 -11
  219. package/dist/layout/modules-extended.js.map +1 -1
  220. package/dist/layout/modules.d.ts.map +1 -1
  221. package/dist/layout/modules.js +15 -10
  222. package/dist/layout/modules.js.map +1 -1
  223. package/dist/layout/renderer.d.ts +51 -4
  224. package/dist/layout/renderer.d.ts.map +1 -1
  225. package/dist/layout/renderer.js +36 -38
  226. package/dist/layout/renderer.js.map +1 -1
  227. package/dist/layout/types.d.ts +42 -0
  228. package/dist/layout/types.d.ts.map +1 -1
  229. package/dist/layout/types.js +54 -11
  230. package/dist/layout/types.js.map +1 -1
  231. package/dist/schema/base.d.ts +1 -1
  232. package/dist/schema/components.d.ts +150 -0
  233. package/dist/schema/components.d.ts.map +1 -1
  234. package/dist/schema/components.js +50 -4
  235. package/dist/schema/components.js.map +1 -1
  236. package/dist/scope/inline-editing.d.ts.map +1 -1
  237. package/dist/scope/inline-editing.js +10 -0
  238. package/dist/scope/inline-editing.js.map +1 -1
  239. package/dist/scope/sticky-offset.d.ts +185 -0
  240. package/dist/scope/sticky-offset.d.ts.map +1 -0
  241. package/dist/scope/sticky-offset.js +432 -0
  242. package/dist/scope/sticky-offset.js.map +1 -0
  243. package/dist/site/styles.css +41 -3
  244. package/dist/versioning/scheduler.d.ts +8 -0
  245. package/dist/versioning/scheduler.d.ts.map +1 -1
  246. package/dist/versioning/scheduler.js +21 -7
  247. package/dist/versioning/scheduler.js.map +1 -1
  248. package/package.json +1 -1
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Site-authored top-anchored chrome vs. the editor's own fixed bars
3
+ * (kywi-cms#146).
4
+ *
5
+ * Both front-of-site bars are `position: fixed; top: 0` — the edit-mode
6
+ * `.kywi-overlay-toolbar` (46px) and the browse-mode `.kywi-edit-toolbar`
7
+ * (44px). Each pushes the DOCUMENT down by its own height
8
+ * (`.kywi-frontend-edit--editing` / `--browsing` in editor.css), which is
9
+ * enough for a header in normal flow: it starts one bar lower and stays
10
+ * visible.
11
+ *
12
+ * It is NOT enough for `position: sticky; top: 0`, the most common theme
13
+ * pattern there is — and the one the marketing site uses. Document padding
14
+ * moves an element's FLOW position; it does nothing to where the element pins
15
+ * once it sticks. So the moment the reader scrolls, a `top: 0` header pins to
16
+ * viewport y=0, underneath a bar that outranks it by ~950 z-index tiers, and
17
+ * the top 46px of the site's own navigation is covered and unclickable for the
18
+ * rest of the session. Measured on the reference app at 1280×720: header rect
19
+ * `top: 0, height: 65` against a bar occupying 0–46.
20
+ *
21
+ * z-index cannot fix this (the bar has to stay on top — it is the way out of
22
+ * edit mode), and neither can CSS alone: the offset a sticky element needs is
23
+ * `top: <bar height>`, a declaration only the site's own stylesheet can carry
24
+ * and only for the elements it knows are sticky. So the editor patches it, for
25
+ * exactly as long as a bar is mounted, and puts it back on the way out.
26
+ *
27
+ * Two things come out of one scan:
28
+ * - each detected element gets an inline `top: var(<bar height token>, <px>)`
29
+ * — plus its own original offset, for a second tier — so the theme's whole
30
+ * top stack pins UNDER the bar instead of behind it, gaps intact;
31
+ * - the tallest detected top BAND is published as `--kywi-editor-sticky-offset`
32
+ * on `documentElement`, which `useChromeFlip` (use-chrome-flip.ts) adds to
33
+ * the strip it clamps in-place chrome below — otherwise the chrome clears
34
+ * the editor's bar only to land inside the site's nav (kywi-cms#122 pinned
35
+ * it at y≈62 into a nav band of 46–111).
36
+ *
37
+ * Lives in `scope/` rather than `admin/layout-editor/` because BOTH bars need
38
+ * it and only one of them is the layout editor: `scope/inline-editing.tsx`
39
+ * (the browse toolbar) is a public-site module, and `admin/` already imports
40
+ * `scope/` while nothing in `scope/` imports `admin/`. Putting it the other way
41
+ * round would invert that.
42
+ */
43
+ /**
44
+ * Height of the tallest site-authored top band the editor offset, in px, on
45
+ * `documentElement` — 0/absent when the page has none.
46
+ *
47
+ * Published as an INLINE custom property deliberately, not a stylesheet rule:
48
+ * `useChromeFlip` reads it on every scroll frame, and
49
+ * `documentElement.style.getPropertyValue()` is a CSSOM read of a declaration
50
+ * this module wrote, not a `getComputedStyle` that can force a style recalc.
51
+ * It is an editor-published measurement, not a theme token — a theme has
52
+ * nothing useful to say about it.
53
+ */
54
+ export declare const STICKY_OFFSET_VAR = "--kywi-editor-sticky-offset";
55
+ /** Height token of the edit-mode bar (`.kywi-overlay-toolbar`), editor.css. */
56
+ export declare const EDIT_BAR_HEIGHT_VAR = "--kywi-overlay-bar-height";
57
+ /**
58
+ * The literal behind {@link EDIT_BAR_HEIGHT_VAR}, for the `var()` fallback.
59
+ *
60
+ * Kept beside the token rather than only in CSS because a `var()` whose token
61
+ * is undefined resolves to the property's INITIAL value, and `top`'s initial
62
+ * value is `auto` — i.e. the patch below would un-stick every header it touched
63
+ * rather than merely mis-offsetting it. See {@link applyStickyOffset}.
64
+ */
65
+ export declare const EDIT_BAR_HEIGHT_PX = 46;
66
+ /** Height token of the browse-mode bar (`.kywi-edit-toolbar`), editor.css. */
67
+ export declare const BROWSE_BAR_HEIGHT_VAR = "--kywi-browse-bar-height";
68
+ /** The literal behind {@link BROWSE_BAR_HEIGHT_VAR} — see {@link EDIT_BAR_HEIGHT_PX}. */
69
+ export declare const BROWSE_BAR_HEIGHT_PX = 44;
70
+ /**
71
+ * Marks an element the editor is currently holding an offset on. Public so a
72
+ * test (or a confused developer looking at the DOM) can tell an editor-applied
73
+ * `top` from one the site authored, and so a second `applyStickyOffset` can
74
+ * never capture an already-patched value as the "original".
75
+ */
76
+ export declare const STICKY_PATCH_ATTR = "data-kywi-sticky-offset";
77
+ /**
78
+ * How far from y=0 still counts as "anchored to the top of the viewport" — the
79
+ * FIRST tier of a theme's sticky stack, offset by exactly one bar height.
80
+ *
81
+ * Not just `top: 0`: a sub-pixel or `0.5px` theme value is the same intent, and
82
+ * `top: -1px` is a widely-copied trick for detecting the stuck state with an
83
+ * IntersectionObserver sentinel.
84
+ */
85
+ export declare const STICKY_TOP_THRESHOLD_PX = 4;
86
+ /**
87
+ * How far down a SECOND sticky tier may be anchored and still be treated as
88
+ * part of the same top stack (kywi-cms#146 fix round).
89
+ *
90
+ * A theme that stacks — `header { top: 0 }` with a sub-nav at `top: 64px`, the
91
+ * pattern behind every docs site's category bar — used to be half-patched: the
92
+ * header moved to 46 and the sub-nav did not, so a 65px-tall header now covered
93
+ * 46–111 and ate the sub-nav's 64–104 outright. Leaving the second tier alone
94
+ * was not neutral; it was an overlap the PATCH created, and the earlier claim
95
+ * that such an element "is already positioned relative to something" only holds
96
+ * while the thing it is positioned relative to has not moved.
97
+ *
98
+ * So the whole stack shifts by the same delta — `calc(var(<bar>) + <original>)`
99
+ * — which preserves every gap the theme built rather than flattening them.
100
+ *
101
+ * 200px is the ceiling because it is comfortably taller than any real header +
102
+ * sub-nav pair and comfortably shorter than "parked in the middle of the page":
103
+ * a sticky filter panel at `top: 320px` is anchored to page content, not to the
104
+ * top of the viewport, and moving it would be the very break this avoids.
105
+ */
106
+ export declare const STICKY_SECOND_TIER_MAX_PX = 200;
107
+ /** A live offset patch. Reverting twice is a no-op, not a double-restore. */
108
+ export interface StickyOffsetPatch {
109
+ /** Elements currently carrying an editor-applied `top`. Exposed for tests. */
110
+ readonly elements: readonly HTMLElement[];
111
+ /**
112
+ * Height published as {@link STICKY_OFFSET_VAR} AT MOUNT, in px (0 when the
113
+ * page has no top band). A snapshot, not a live value — the observer below
114
+ * can re-publish a taller one; read {@link readStickyOffsetPx} for current.
115
+ */
116
+ readonly bandPx: number;
117
+ /** Puts every `top` back exactly as it was and drops the published band. */
118
+ revert(): void;
119
+ }
120
+ /**
121
+ * The inline `top` the editor writes for one tier of a theme's sticky stack.
122
+ *
123
+ * The token, not its resolved px: `var()` in an inline style resolves at
124
+ * used-value time, so a bar height that changes (a theme override, a media
125
+ * query) moves everything this patched without a re-scan. The fallback is
126
+ * mandatory — see {@link applyStickyOffset}'s `fallbackPx`.
127
+ *
128
+ * A pure function so the exact declaration can be pinned by a test: happy-dom's
129
+ * CSS value parser understands neither `calc(var(…))` nor a `var()` fallback
130
+ * written with a space after the comma, and silently DROPS the whole
131
+ * declaration in both cases, so a DOM read-back cannot see this string. Which is
132
+ * also why the comma is written tight — browsers treat `var(--x,4px)` and
133
+ * `var(--x, 4px)` identically, and the tight form is the one that survives the
134
+ * test environment, keeping the first-tier DOM assertions (and the integration
135
+ * ones in inline-editing / overlay-editor) real rather than vacuous.
136
+ *
137
+ * @param shift how far down the stack this tier sits — 0 for the first.
138
+ */
139
+ export declare function stickyTopValue(barHeightVar: string, fallbackPx: number, shift: number): string;
140
+ /**
141
+ * Offset every site-authored top-anchored element by one bar height, for as
142
+ * long as the returned patch is held.
143
+ *
144
+ * ONE scan, at bar mount. A `MutationObserver` is deliberately not part of
145
+ * this: it would have to re-run `getComputedStyle` on every subtree the site
146
+ * (or the editor's own canvas) inserts for the whole session, and the elements
147
+ * this exists for — the site's header — are in the document before the editor
148
+ * mounts. **Accepted limitation:** a sticky bar that MOUNTS mid-session (a
149
+ * cookie banner that appears on a timer, a mobile menu that is `display: none`
150
+ * at editor open, a site header rendered by a client-only component that has
151
+ * not hydrated yet) is not offset, and pins under the bar as it does today.
152
+ * Re-entering edit mode re-scans.
153
+ *
154
+ * The measured band IS re-published when a patched element resizes (a header
155
+ * whose nav wraps to two rows on a narrower viewport, or the responsive-preview
156
+ * switch), via a `ResizeObserver` — the same reason `useChromeFlip` grew one in
157
+ * kywi-cms#132's fix round: no window event fires for that.
158
+ *
159
+ * @param barHeightVar the CSS custom property holding the mounted bar's height
160
+ * — {@link EDIT_BAR_HEIGHT_VAR} or {@link BROWSE_BAR_HEIGHT_VAR}. Passed as a
161
+ * token rather than a number so a theme that re-declares the token moves the
162
+ * offset with the bar, and so nothing here has to re-parse px.
163
+ * @param fallbackPx that token's literal — {@link EDIT_BAR_HEIGHT_PX} or
164
+ * {@link BROWSE_BAR_HEIGHT_PX} — emitted as the `var()` fallback. NOT
165
+ * optional, because the case it covers is the default one on a generated app:
166
+ * both tokens are declared in `editor.css`, which a scaffold loads LAZILY
167
+ * (behind `next/dynamic`, only once the overlay editor opens — see
168
+ * `create-kywi-app/lib/templates.mjs`), so the BROWSE bar renders with
169
+ * neither token defined. A fallback-less `var()` there resolves to `top`'s
170
+ * initial value, `auto`, and a sticky header with `top: auto` stops sticking
171
+ * at all — strictly worse than the overlap this fixes, and for the whole
172
+ * session. `site/styles.css` declares both on `:root` for the same reason
173
+ * (belt to this brace); the fallback is what holds if a host ships neither
174
+ * stylesheet.
175
+ */
176
+ export declare function applyStickyOffset(barHeightVar: string, fallbackPx: number): StickyOffsetPatch;
177
+ /**
178
+ * The published sticky band, in px — 0 when the page has no offset top band.
179
+ *
180
+ * Cheap enough for a scroll handler: this reads the inline declaration
181
+ * {@link applyStickyOffset} wrote, so unlike `getComputedStyle` it cannot force
182
+ * a style recalculation.
183
+ */
184
+ export declare function readStickyOffsetPx(): number;
185
+ //# sourceMappingURL=sticky-offset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sticky-offset.d.ts","sourceRoot":"","sources":["../../src/scope/sticky-offset.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,gCAAgC,CAAA;AAE9D,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,8BAA8B,CAAA;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,6BAA6B,CAAA;AAE/D,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,4BAA4B,CAAA;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AA2E5C,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;IACzC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,MAAM,IAAI,IAAI,CAAA;CACf;AAaD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9F;AAkFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAwF7F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAK3C"}
@@ -0,0 +1,432 @@
1
+ 'use client';
2
+ /**
3
+ * Site-authored top-anchored chrome vs. the editor's own fixed bars
4
+ * (kywi-cms#146).
5
+ *
6
+ * Both front-of-site bars are `position: fixed; top: 0` — the edit-mode
7
+ * `.kywi-overlay-toolbar` (46px) and the browse-mode `.kywi-edit-toolbar`
8
+ * (44px). Each pushes the DOCUMENT down by its own height
9
+ * (`.kywi-frontend-edit--editing` / `--browsing` in editor.css), which is
10
+ * enough for a header in normal flow: it starts one bar lower and stays
11
+ * visible.
12
+ *
13
+ * It is NOT enough for `position: sticky; top: 0`, the most common theme
14
+ * pattern there is — and the one the marketing site uses. Document padding
15
+ * moves an element's FLOW position; it does nothing to where the element pins
16
+ * once it sticks. So the moment the reader scrolls, a `top: 0` header pins to
17
+ * viewport y=0, underneath a bar that outranks it by ~950 z-index tiers, and
18
+ * the top 46px of the site's own navigation is covered and unclickable for the
19
+ * rest of the session. Measured on the reference app at 1280×720: header rect
20
+ * `top: 0, height: 65` against a bar occupying 0–46.
21
+ *
22
+ * z-index cannot fix this (the bar has to stay on top — it is the way out of
23
+ * edit mode), and neither can CSS alone: the offset a sticky element needs is
24
+ * `top: <bar height>`, a declaration only the site's own stylesheet can carry
25
+ * and only for the elements it knows are sticky. So the editor patches it, for
26
+ * exactly as long as a bar is mounted, and puts it back on the way out.
27
+ *
28
+ * Two things come out of one scan:
29
+ * - each detected element gets an inline `top: var(<bar height token>, <px>)`
30
+ * — plus its own original offset, for a second tier — so the theme's whole
31
+ * top stack pins UNDER the bar instead of behind it, gaps intact;
32
+ * - the tallest detected top BAND is published as `--kywi-editor-sticky-offset`
33
+ * on `documentElement`, which `useChromeFlip` (use-chrome-flip.ts) adds to
34
+ * the strip it clamps in-place chrome below — otherwise the chrome clears
35
+ * the editor's bar only to land inside the site's nav (kywi-cms#122 pinned
36
+ * it at y≈62 into a nav band of 46–111).
37
+ *
38
+ * Lives in `scope/` rather than `admin/layout-editor/` because BOTH bars need
39
+ * it and only one of them is the layout editor: `scope/inline-editing.tsx`
40
+ * (the browse toolbar) is a public-site module, and `admin/` already imports
41
+ * `scope/` while nothing in `scope/` imports `admin/`. Putting it the other way
42
+ * round would invert that.
43
+ */
44
+ /**
45
+ * Height of the tallest site-authored top band the editor offset, in px, on
46
+ * `documentElement` — 0/absent when the page has none.
47
+ *
48
+ * Published as an INLINE custom property deliberately, not a stylesheet rule:
49
+ * `useChromeFlip` reads it on every scroll frame, and
50
+ * `documentElement.style.getPropertyValue()` is a CSSOM read of a declaration
51
+ * this module wrote, not a `getComputedStyle` that can force a style recalc.
52
+ * It is an editor-published measurement, not a theme token — a theme has
53
+ * nothing useful to say about it.
54
+ */
55
+ export const STICKY_OFFSET_VAR = '--kywi-editor-sticky-offset';
56
+ /** Height token of the edit-mode bar (`.kywi-overlay-toolbar`), editor.css. */
57
+ export const EDIT_BAR_HEIGHT_VAR = '--kywi-overlay-bar-height';
58
+ /**
59
+ * The literal behind {@link EDIT_BAR_HEIGHT_VAR}, for the `var()` fallback.
60
+ *
61
+ * Kept beside the token rather than only in CSS because a `var()` whose token
62
+ * is undefined resolves to the property's INITIAL value, and `top`'s initial
63
+ * value is `auto` — i.e. the patch below would un-stick every header it touched
64
+ * rather than merely mis-offsetting it. See {@link applyStickyOffset}.
65
+ */
66
+ export const EDIT_BAR_HEIGHT_PX = 46;
67
+ /** Height token of the browse-mode bar (`.kywi-edit-toolbar`), editor.css. */
68
+ export const BROWSE_BAR_HEIGHT_VAR = '--kywi-browse-bar-height';
69
+ /** The literal behind {@link BROWSE_BAR_HEIGHT_VAR} — see {@link EDIT_BAR_HEIGHT_PX}. */
70
+ export const BROWSE_BAR_HEIGHT_PX = 44;
71
+ /**
72
+ * Marks an element the editor is currently holding an offset on. Public so a
73
+ * test (or a confused developer looking at the DOM) can tell an editor-applied
74
+ * `top` from one the site authored, and so a second `applyStickyOffset` can
75
+ * never capture an already-patched value as the "original".
76
+ */
77
+ export const STICKY_PATCH_ATTR = 'data-kywi-sticky-offset';
78
+ /**
79
+ * How far from y=0 still counts as "anchored to the top of the viewport" — the
80
+ * FIRST tier of a theme's sticky stack, offset by exactly one bar height.
81
+ *
82
+ * Not just `top: 0`: a sub-pixel or `0.5px` theme value is the same intent, and
83
+ * `top: -1px` is a widely-copied trick for detecting the stuck state with an
84
+ * IntersectionObserver sentinel.
85
+ */
86
+ export const STICKY_TOP_THRESHOLD_PX = 4;
87
+ /**
88
+ * How far down a SECOND sticky tier may be anchored and still be treated as
89
+ * part of the same top stack (kywi-cms#146 fix round).
90
+ *
91
+ * A theme that stacks — `header { top: 0 }` with a sub-nav at `top: 64px`, the
92
+ * pattern behind every docs site's category bar — used to be half-patched: the
93
+ * header moved to 46 and the sub-nav did not, so a 65px-tall header now covered
94
+ * 46–111 and ate the sub-nav's 64–104 outright. Leaving the second tier alone
95
+ * was not neutral; it was an overlap the PATCH created, and the earlier claim
96
+ * that such an element "is already positioned relative to something" only holds
97
+ * while the thing it is positioned relative to has not moved.
98
+ *
99
+ * So the whole stack shifts by the same delta — `calc(var(<bar>) + <original>)`
100
+ * — which preserves every gap the theme built rather than flattening them.
101
+ *
102
+ * 200px is the ceiling because it is comfortably taller than any real header +
103
+ * sub-nav pair and comfortably shorter than "parked in the middle of the page":
104
+ * a sticky filter panel at `top: 320px` is anchored to page content, not to the
105
+ * top of the viewport, and moving it would be the very break this avoids.
106
+ */
107
+ export const STICKY_SECOND_TIER_MAX_PX = 200;
108
+ /**
109
+ * Below this in either dimension an element is a probe, not chrome. Next's dev
110
+ * overlay parks a 1×1 `position: fixed; top: 0` div at (-1, -1) on every page,
111
+ * and giving it an inline `top` is pure noise in the DOM the owner inspects.
112
+ */
113
+ const MIN_PATCHABLE_PX = 8;
114
+ /**
115
+ * A top-anchored element taller than this share of the viewport is a drawer, a
116
+ * scrim or a full-height sidebar — not a band across the top of the page — so
117
+ * it must not drag the chrome clamp down with it. It still gets the offset (it
118
+ * is still covered by the bar); it just does not define a band to clamp below.
119
+ */
120
+ const MAX_BAND_VIEWPORT_FRACTION = 0.25;
121
+ /**
122
+ * …but a band no taller than this is a header whatever the viewport is doing,
123
+ * so it publishes even when the fraction above says otherwise (kywi-cms#146 fix
124
+ * round).
125
+ *
126
+ * The fraction alone is a trap on a short window: a nav whose links wrap to two
127
+ * rows measures ~170px, and at a 640px-tall viewport (a laptop with devtools
128
+ * docked, or the editor's own responsive preview) a quarter is 160 — so the
129
+ * band silently published as 0 while the elements stayed patched, and the
130
+ * chrome clamped straight back INTO the nav, which is the exact collision the
131
+ * band exists to end. The two rules are a UNION, never an intersection: a band
132
+ * that is short in ABSOLUTE terms or short RELATIVE to the viewport publishes;
133
+ * only one that is both tall and viewport-dominating is read as a drawer. A
134
+ * 700px full-height drawer fails both at any viewport, which is the case the
135
+ * fraction was written for and still catches.
136
+ */
137
+ const ALWAYS_A_BAND_MAX_PX = 200;
138
+ /**
139
+ * The editor's OWN top-anchored chrome, which must never be offset: these
140
+ * elements ARE the bars being cleared, and pushing them down by their own
141
+ * height would walk them off the screen one mode-switch at a time.
142
+ *
143
+ * An explicit list, not a `[class*="kywi-overlay"]` wildcard, for one specific
144
+ * reason: `.kywi-overlay-shell` is `display: contents` and WRAPS the whole
145
+ * in-place canvas, i.e. the site's own sections and modules. A wildcard would
146
+ * match it, and `closest()` would then exclude every sticky element the owner
147
+ * is actually editing. `[data-kywi-editor-chrome]` is the extension point —
148
+ * new chrome marks itself with it (the attribute already exists for the
149
+ * inline-edit exclusions) rather than growing this list.
150
+ */
151
+ const EDITOR_CHROME_SELECTOR = [
152
+ '[data-kywi-editor-chrome]',
153
+ '.kywi-edit-toolbar',
154
+ '.kywi-overlay-toolbar',
155
+ '.kywi-overlay-rail',
156
+ '.kywi-overlay-dialog-host',
157
+ '.kywi-editor-template-overlay',
158
+ '.kywi-admin-shell',
159
+ '.kywi-ip-chrome',
160
+ '.kywi-ip-chip',
161
+ ].join(',');
162
+ /**
163
+ * Keyed by element rather than carried on the handle so a patch applied while
164
+ * another is somehow still live restores the SITE's value, never the previous
165
+ * patch's `var(...)`. React unmounts the outgoing bar's effect before it runs
166
+ * the incoming one's, so browse→edit already swaps cleanly; this is the belt to
167
+ * that braces, and it is a WeakMap so a detached element is not retained.
168
+ */
169
+ const originalTops = new WeakMap();
170
+ const NOOP_PATCH = {
171
+ elements: [],
172
+ bandPx: 0,
173
+ revert() { },
174
+ };
175
+ /** True for an element the editor's own chrome owns (or lives inside). */
176
+ function isEditorChrome(el) {
177
+ return el.closest(EDITOR_CHROME_SELECTOR) !== null;
178
+ }
179
+ /**
180
+ * The inline `top` the editor writes for one tier of a theme's sticky stack.
181
+ *
182
+ * The token, not its resolved px: `var()` in an inline style resolves at
183
+ * used-value time, so a bar height that changes (a theme override, a media
184
+ * query) moves everything this patched without a re-scan. The fallback is
185
+ * mandatory — see {@link applyStickyOffset}'s `fallbackPx`.
186
+ *
187
+ * A pure function so the exact declaration can be pinned by a test: happy-dom's
188
+ * CSS value parser understands neither `calc(var(…))` nor a `var()` fallback
189
+ * written with a space after the comma, and silently DROPS the whole
190
+ * declaration in both cases, so a DOM read-back cannot see this string. Which is
191
+ * also why the comma is written tight — browsers treat `var(--x,4px)` and
192
+ * `var(--x, 4px)` identically, and the tight form is the one that survives the
193
+ * test environment, keeping the first-tier DOM assertions (and the integration
194
+ * ones in inline-editing / overlay-editor) real rather than vacuous.
195
+ *
196
+ * @param shift how far down the stack this tier sits — 0 for the first.
197
+ */
198
+ export function stickyTopValue(barHeightVar, fallbackPx, shift) {
199
+ const bar = `var(${barHeightVar},${fallbackPx}px)`;
200
+ return shift === 0 ? bar : `calc(${bar} + ${shift}px)`;
201
+ }
202
+ /**
203
+ * True when a sticky element pins against the VIEWPORT rather than inside some
204
+ * ancestor's scroll pane.
205
+ *
206
+ * Only ever asked of a second-tier candidate, and it is what keeps a sticky
207
+ * table head at `top: 40px` inside an `overflow: auto` pane out of the patch:
208
+ * that element's `top` is measured from its scrollport, not from the top of the
209
+ * window, so the editor's bar never covers it and shifting it by a bar height
210
+ * would push it down inside its own pane for no reason. The overflow test is
211
+ * the same one the browser applies — any `overflow-y` other than `visible` or
212
+ * `clip` makes that ancestor the scrollport (which is also why `overflow-x:
213
+ * hidden`, whose `overflow-y` computes to `auto`, famously "breaks" sticky).
214
+ */
215
+ function pinsToViewport(el, view) {
216
+ const doc = el.ownerDocument;
217
+ for (let parent = el.parentElement; parent && parent !== doc.body && parent !== doc.documentElement; parent = parent.parentElement) {
218
+ const overflowY = view.getComputedStyle(parent).overflowY;
219
+ if (overflowY && overflowY !== 'visible' && overflowY !== 'clip')
220
+ return false;
221
+ }
222
+ return true;
223
+ }
224
+ /**
225
+ * How far down the top stack this element is anchored — 0 for the first tier,
226
+ * its own `top` in px for a second tier — or null when it is not part of the
227
+ * stack at all and must be left exactly as the theme wrote it.
228
+ */
229
+ function topAnchorOf(el, view) {
230
+ const style = view.getComputedStyle(el);
231
+ const position = style.position;
232
+ // `-webkit-sticky` still shows up in older Safari's computed style.
233
+ const sticky = position === 'sticky' || position === '-webkit-sticky';
234
+ if (!sticky && position !== 'fixed')
235
+ return null;
236
+ // A px length and nothing else. `top: auto` — a sticky element with no
237
+ // vertical anchor, which never pins to the top at all — is the obvious
238
+ // rejection, but the one that matters is a PERCENTAGE: `.kywi-selfid-modal`
239
+ // is `position: fixed; top: 50%` (centred, then pulled back by a transform),
240
+ // and a bare `parseFloat` reads that as "50", i.e. a second tier to shift by
241
+ // a bar height. A resolved `top` is normally px, but it is a percentage for
242
+ // an element the box tree has no used value for, and half a viewport is a
243
+ // second tier on any short window. Anchoring on the UNIT rather than the
244
+ // number rejects both without a special case.
245
+ const match = /^(-?[0-9]*\.?[0-9]+)px$/.exec(style.top.trim());
246
+ if (!match)
247
+ return null;
248
+ const top = Number.parseFloat(match[1]);
249
+ if (!Number.isFinite(top))
250
+ return null;
251
+ if (Math.abs(top) <= STICKY_TOP_THRESHOLD_PX)
252
+ return 0;
253
+ // Below the top, but within one header stack of it: a second tier, shifted by
254
+ // the same delta so the theme's own gap survives (see
255
+ // {@link STICKY_SECOND_TIER_MAX_PX}). Negative values beyond the threshold
256
+ // are scroll tricks, not tiers, and are left alone.
257
+ if (top < 0 || top > STICKY_SECOND_TIER_MAX_PX)
258
+ return null;
259
+ if (sticky && !pinsToViewport(el, view))
260
+ return null;
261
+ return top;
262
+ }
263
+ /**
264
+ * The height this element contributes to the clamp band, or 0 if it is not a
265
+ * band: it has to cross the viewport's horizontal midline (a left sidebar or a
266
+ * corner badge does not) and read as a header rather than a drawer (see
267
+ * {@link ALWAYS_A_BAND_MAX_PX}).
268
+ *
269
+ * The band a tier is responsible for is measured from the TOP of the stack —
270
+ * `shift + height`, not the element's own height — because that is the strip
271
+ * the chrome has to clear: a sub-nav at `top: 64px` and 40px tall leaves the
272
+ * chrome nothing until 104.
273
+ */
274
+ function bandHeightOf(el, view, shift) {
275
+ const { height, left, right } = el.getBoundingClientRect();
276
+ const midX = view.innerWidth / 2;
277
+ if (left > midX || right < midX)
278
+ return 0;
279
+ const band = shift + height;
280
+ if (band > ALWAYS_A_BAND_MAX_PX && band > view.innerHeight * MAX_BAND_VIEWPORT_FRACTION)
281
+ return 0;
282
+ return band;
283
+ }
284
+ /**
285
+ * Offset every site-authored top-anchored element by one bar height, for as
286
+ * long as the returned patch is held.
287
+ *
288
+ * ONE scan, at bar mount. A `MutationObserver` is deliberately not part of
289
+ * this: it would have to re-run `getComputedStyle` on every subtree the site
290
+ * (or the editor's own canvas) inserts for the whole session, and the elements
291
+ * this exists for — the site's header — are in the document before the editor
292
+ * mounts. **Accepted limitation:** a sticky bar that MOUNTS mid-session (a
293
+ * cookie banner that appears on a timer, a mobile menu that is `display: none`
294
+ * at editor open, a site header rendered by a client-only component that has
295
+ * not hydrated yet) is not offset, and pins under the bar as it does today.
296
+ * Re-entering edit mode re-scans.
297
+ *
298
+ * The measured band IS re-published when a patched element resizes (a header
299
+ * whose nav wraps to two rows on a narrower viewport, or the responsive-preview
300
+ * switch), via a `ResizeObserver` — the same reason `useChromeFlip` grew one in
301
+ * kywi-cms#132's fix round: no window event fires for that.
302
+ *
303
+ * @param barHeightVar the CSS custom property holding the mounted bar's height
304
+ * — {@link EDIT_BAR_HEIGHT_VAR} or {@link BROWSE_BAR_HEIGHT_VAR}. Passed as a
305
+ * token rather than a number so a theme that re-declares the token moves the
306
+ * offset with the bar, and so nothing here has to re-parse px.
307
+ * @param fallbackPx that token's literal — {@link EDIT_BAR_HEIGHT_PX} or
308
+ * {@link BROWSE_BAR_HEIGHT_PX} — emitted as the `var()` fallback. NOT
309
+ * optional, because the case it covers is the default one on a generated app:
310
+ * both tokens are declared in `editor.css`, which a scaffold loads LAZILY
311
+ * (behind `next/dynamic`, only once the overlay editor opens — see
312
+ * `create-kywi-app/lib/templates.mjs`), so the BROWSE bar renders with
313
+ * neither token defined. A fallback-less `var()` there resolves to `top`'s
314
+ * initial value, `auto`, and a sticky header with `top: auto` stops sticking
315
+ * at all — strictly worse than the overlap this fixes, and for the whole
316
+ * session. `site/styles.css` declares both on `:root` for the same reason
317
+ * (belt to this brace); the fallback is what holds if a host ships neither
318
+ * stylesheet.
319
+ */
320
+ export function applyStickyOffset(barHeightVar, fallbackPx) {
321
+ if (typeof document === 'undefined' || typeof window === 'undefined')
322
+ return NOOP_PATCH;
323
+ const body = document.body;
324
+ if (!body)
325
+ return NOOP_PATCH;
326
+ const patched = [];
327
+ /** Each patched element's tier depth, for the band it is responsible for. */
328
+ const shifts = new Map();
329
+ for (const el of Array.from(body.querySelectorAll('*'))) {
330
+ if (el.hasAttribute(STICKY_PATCH_ATTR))
331
+ continue;
332
+ if (isEditorChrome(el))
333
+ continue;
334
+ const shift = topAnchorOf(el, window);
335
+ if (shift === null)
336
+ continue;
337
+ const { width, height } = el.getBoundingClientRect();
338
+ if (width < MIN_PATCHABLE_PX || height < MIN_PATCHABLE_PX)
339
+ continue;
340
+ originalTops.set(el, {
341
+ value: el.style.getPropertyValue('top'),
342
+ priority: el.style.getPropertyPriority('top'),
343
+ });
344
+ el.setAttribute(STICKY_PATCH_ATTR, '');
345
+ // A second tier keeps its own offset ON TOP of the bar, so the theme's
346
+ // stack arrives intact one bar lower rather than collapsing onto itself.
347
+ el.style.setProperty('top', stickyTopValue(barHeightVar, fallbackPx, shift));
348
+ patched.push(el);
349
+ shifts.set(el, shift);
350
+ }
351
+ const root = document.documentElement;
352
+ const publishBand = () => {
353
+ let band = 0;
354
+ for (const el of patched)
355
+ band = Math.max(band, bandHeightOf(el, window, shifts.get(el) ?? 0));
356
+ // Rounded up: a fractional band rounded DOWN would clamp chrome half a
357
+ // pixel INTO the nav, which is the collision this exists to end.
358
+ const px = Math.ceil(band);
359
+ if (px > 0)
360
+ root.style.setProperty(STICKY_OFFSET_VAR, `${px}px`);
361
+ // Same gate as `revert` below, for the same reason: a scan that patched
362
+ // NOTHING has no band of its own and must not clear the live patch's. Two
363
+ // bars overlapping by a frame is the ordinary case (browse → edit), and
364
+ // without this the second one's empty scan wipes the first one's band and
365
+ // the chrome clamps back into the nav.
366
+ else if (patched.length > 0)
367
+ root.style.removeProperty(STICKY_OFFSET_VAR);
368
+ return px;
369
+ };
370
+ const bandPx = publishBand();
371
+ // Feature-detected, exactly as in use-chrome-flip.ts: happy-dom ships an
372
+ // inert stub and older hosts have none, so a missing observer degrades to
373
+ // "the band is what it measured at mount" instead of throwing.
374
+ const ResizeObserverCtor = window
375
+ .ResizeObserver;
376
+ let observer = null;
377
+ if (patched.length > 0 && typeof ResizeObserverCtor === 'function') {
378
+ observer = new ResizeObserverCtor(() => { publishBand(); });
379
+ for (const el of patched)
380
+ observer.observe(el);
381
+ }
382
+ let reverted = false;
383
+ return {
384
+ elements: patched,
385
+ bandPx,
386
+ revert() {
387
+ if (reverted)
388
+ return;
389
+ reverted = true;
390
+ observer?.disconnect();
391
+ for (const el of patched) {
392
+ el.removeAttribute(STICKY_PATCH_ATTR);
393
+ const original = originalTops.get(el);
394
+ originalTops.delete(el);
395
+ if (original && original.value !== '') {
396
+ el.style.setProperty('top', original.value, original.priority);
397
+ }
398
+ else {
399
+ el.style.removeProperty('top');
400
+ }
401
+ // An element that carried no `style` attribute before must carry none
402
+ // after — "restored exactly" has to include leaving no empty husk
403
+ // behind for the next reader of the DOM to wonder about.
404
+ if (el.getAttribute('style') === '')
405
+ el.removeAttribute('style');
406
+ }
407
+ // Only a patch that could have published the band may clear it — the
408
+ // same reasoning as restoring `top` from the recorded original rather
409
+ // than blindly: a revert must never undo something it did not do.
410
+ if (patched.length > 0) {
411
+ root.style.removeProperty(STICKY_OFFSET_VAR);
412
+ if (root.getAttribute('style') === '')
413
+ root.removeAttribute('style');
414
+ }
415
+ },
416
+ };
417
+ }
418
+ /**
419
+ * The published sticky band, in px — 0 when the page has no offset top band.
420
+ *
421
+ * Cheap enough for a scroll handler: this reads the inline declaration
422
+ * {@link applyStickyOffset} wrote, so unlike `getComputedStyle` it cannot force
423
+ * a style recalculation.
424
+ */
425
+ export function readStickyOffsetPx() {
426
+ if (typeof document === 'undefined')
427
+ return 0;
428
+ const raw = document.documentElement.style.getPropertyValue(STICKY_OFFSET_VAR);
429
+ const parsed = Number.parseFloat(raw);
430
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
431
+ }
432
+ //# sourceMappingURL=sticky-offset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sticky-offset.js","sourceRoot":"","sources":["../../src/scope/sticky-offset.ts"],"names":[],"mappings":"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"}