@kywi-software/core 0.9.0 → 0.11.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.
- package/AGENT-PATTERNS.md +15 -13
- package/README.md +67 -0
- package/dist/admin/layout-editor/column-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/column-overlay.js +7 -1
- package/dist/admin/layout-editor/column-overlay.js.map +1 -1
- package/dist/admin/layout-editor/drop-indicator.d.ts +18 -1
- package/dist/admin/layout-editor/drop-indicator.d.ts.map +1 -1
- package/dist/admin/layout-editor/drop-indicator.js +23 -3
- package/dist/admin/layout-editor/drop-indicator.js.map +1 -1
- package/dist/admin/layout-editor/editor-canvas.d.ts +31 -8
- package/dist/admin/layout-editor/editor-canvas.d.ts.map +1 -1
- package/dist/admin/layout-editor/editor-canvas.js +131 -48
- package/dist/admin/layout-editor/editor-canvas.js.map +1 -1
- package/dist/admin/layout-editor/editor-state.d.ts.map +1 -1
- package/dist/admin/layout-editor/editor-state.js +19 -3
- package/dist/admin/layout-editor/editor-state.js.map +1 -1
- package/dist/admin/layout-editor/editor.css +106 -8
- package/dist/admin/layout-editor/inline-text.d.ts +56 -4
- package/dist/admin/layout-editor/inline-text.d.ts.map +1 -1
- package/dist/admin/layout-editor/inline-text.js +134 -11
- package/dist/admin/layout-editor/inline-text.js.map +1 -1
- package/dist/admin/layout-editor/layout-editor.d.ts.map +1 -1
- package/dist/admin/layout-editor/layout-editor.js +1 -1
- package/dist/admin/layout-editor/layout-editor.js.map +1 -1
- package/dist/admin/layout-editor/module-overlay.d.ts +26 -13
- package/dist/admin/layout-editor/module-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/module-overlay.js +206 -22
- package/dist/admin/layout-editor/module-overlay.js.map +1 -1
- package/dist/admin/layout-editor/overlay-shell.d.ts.map +1 -1
- package/dist/admin/layout-editor/overlay-shell.js +39 -12
- package/dist/admin/layout-editor/overlay-shell.js.map +1 -1
- package/dist/admin/layout-editor/props-panel.js +1 -1
- package/dist/admin/layout-editor/props-panel.js.map +1 -1
- package/dist/admin/layout-editor/section-overlay.d.ts +8 -1
- package/dist/admin/layout-editor/section-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/section-overlay.js +8 -8
- package/dist/admin/layout-editor/section-overlay.js.map +1 -1
- package/dist/admin/layout-editor/section-variant-container-config.js +5 -5
- package/dist/admin/layout-editor/section-variant-container-config.js.map +1 -1
- package/dist/admin/layout-editor/use-chrome-flip.d.ts +111 -11
- package/dist/admin/layout-editor/use-chrome-flip.d.ts.map +1 -1
- package/dist/admin/layout-editor/use-chrome-flip.js +233 -33
- package/dist/admin/layout-editor/use-chrome-flip.js.map +1 -1
- package/dist/admin/layout-editor/use-editor-dnd.d.ts +44 -1
- package/dist/admin/layout-editor/use-editor-dnd.d.ts.map +1 -1
- package/dist/admin/layout-editor/use-editor-dnd.js +191 -3
- package/dist/admin/layout-editor/use-editor-dnd.js.map +1 -1
- package/dist/admin/styles/admin.css +22 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.d.ts.map +1 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.js +12 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.js.map +1 -1
- package/dist/api/content-routes.d.ts.map +1 -1
- package/dist/api/content-routes.js +34 -7
- package/dist/api/content-routes.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +7 -73
- package/dist/api/index.js.map +1 -1
- package/dist/cache/content-invalidation.d.ts +28 -0
- package/dist/cache/content-invalidation.d.ts.map +1 -0
- package/dist/cache/content-invalidation.js +72 -0
- package/dist/cache/content-invalidation.js.map +1 -0
- package/dist/db/content-restore.d.ts +5 -1
- package/dist/db/content-restore.d.ts.map +1 -1
- package/dist/db/content-restore.js +9 -2
- package/dist/db/content-restore.js.map +1 -1
- package/dist/layout/comments-module.d.ts.map +1 -1
- package/dist/layout/comments-module.js +6 -2
- package/dist/layout/comments-module.js.map +1 -1
- package/dist/layout/module-render-context.d.ts +32 -0
- package/dist/layout/module-render-context.d.ts.map +1 -1
- package/dist/layout/module-render-context.js +32 -1
- package/dist/layout/module-render-context.js.map +1 -1
- package/dist/layout/modules-extended.d.ts.map +1 -1
- package/dist/layout/modules-extended.js +21 -11
- package/dist/layout/modules-extended.js.map +1 -1
- package/dist/layout/modules.d.ts.map +1 -1
- package/dist/layout/modules.js +15 -10
- package/dist/layout/modules.js.map +1 -1
- package/dist/scope/inline-editing.d.ts.map +1 -1
- package/dist/scope/inline-editing.js +10 -0
- package/dist/scope/inline-editing.js.map +1 -1
- package/dist/scope/sticky-offset.d.ts +185 -0
- package/dist/scope/sticky-offset.d.ts.map +1 -0
- package/dist/scope/sticky-offset.js +432 -0
- package/dist/scope/sticky-offset.js.map +1 -0
- package/dist/site/styles.css +41 -3
- package/package.json +1 -1
|
@@ -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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"}
|
package/dist/site/styles.css
CHANGED
|
@@ -701,6 +701,32 @@
|
|
|
701
701
|
* (renders nothing for non-editors), so these rules only ever apply for an
|
|
702
702
|
* authenticated admin who opted into edit mode. */
|
|
703
703
|
|
|
704
|
+
/* Both front-of-site bar heights, declared HERE as well as in editor.css.
|
|
705
|
+
*
|
|
706
|
+
* They are an ecosystem contract — documented for theme authors in
|
|
707
|
+
* packages/core/README.md ("Sticky site headers and the editor's bars"), read
|
|
708
|
+
* by `scope/sticky-offset.ts` to offset a theme's sticky header for an editing
|
|
709
|
+
* session (kywi-cms#146), and offered to a theme's own `.kywi-frontend-edit--*`
|
|
710
|
+
* rules — so a page that can show a bar has to have them defined.
|
|
711
|
+
*
|
|
712
|
+
* editor.css alone does not deliver that. A generated app pulls the admin
|
|
713
|
+
* design system in behind `next/dynamic`, so it is loaded only once someone
|
|
714
|
+
* OPENS the overlay editor and is deliberately absent from the public browse
|
|
715
|
+
* bundle (create-kywi-app/lib/templates.mjs) — the exact lazy-load trap
|
|
716
|
+
* kywi-cms#144 hit from the other side. The BROWSE bar ships in this
|
|
717
|
+
* stylesheet's own bundle and renders with editor.css nowhere in the document,
|
|
718
|
+
* so anything resolving these tokens then would get nothing at all. Every
|
|
719
|
+
* public page loads THIS sheet, so declaring them here closes that window.
|
|
720
|
+
*
|
|
721
|
+
* The values are identical to editor.css's (44/46), which is what makes two
|
|
722
|
+
* declaration sites safe: whichever sheet a host loads last, `:root` resolves
|
|
723
|
+
* to the same number. A theme that overrides either token still wins by loading
|
|
724
|
+
* after core's stylesheet, as it already had to. */
|
|
725
|
+
:root {
|
|
726
|
+
--kywi-browse-bar-height: 44px;
|
|
727
|
+
--kywi-overlay-bar-height: 46px;
|
|
728
|
+
}
|
|
729
|
+
|
|
704
730
|
.kywi-edit-toolbar {
|
|
705
731
|
position: fixed;
|
|
706
732
|
top: 0;
|
|
@@ -710,7 +736,7 @@
|
|
|
710
736
|
display: flex;
|
|
711
737
|
align-items: center;
|
|
712
738
|
gap: var(--_kywi-sm, 0.5rem);
|
|
713
|
-
height: 44px;
|
|
739
|
+
height: var(--kywi-browse-bar-height, 44px);
|
|
714
740
|
padding: 0 var(--kywi-spacing-md, 1rem);
|
|
715
741
|
background: #0f172a;
|
|
716
742
|
color: #f8fafc;
|
|
@@ -718,8 +744,20 @@
|
|
|
718
744
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
|
|
719
745
|
}
|
|
720
746
|
/* Push the page down while the browse-mode bar is visible so it never overlaps
|
|
721
|
-
* the site's own header (class toggled by KywiEditToolbar
|
|
722
|
-
|
|
747
|
+
* the site's own header (class toggled by KywiEditToolbar, which puts it on
|
|
748
|
+
* `documentElement`).
|
|
749
|
+
*
|
|
750
|
+
* This rule targets the CLASS-CARRYING element and uses the bar's own token,
|
|
751
|
+
* so it is byte-for-byte the same declaration editor.css makes — deliberately,
|
|
752
|
+
* because a site that mounts the overlay editor loads BOTH sheets. It used to
|
|
753
|
+
* read `.kywi-frontend-edit--browsing body`, a different element, and two
|
|
754
|
+
* different elements each padded 44px pushed the page down 88px for a 44px
|
|
755
|
+
* bar: the seam of body background above the site's nav in kywi-cms#146(1)
|
|
756
|
+
* (reproduced on the reference app — header at y=88, bar 0–44). Two identical
|
|
757
|
+
* declarations collapse; two different ones stack. The `44px` fallback keeps
|
|
758
|
+
* this correct for a host that ships the browse toolbar without ever importing
|
|
759
|
+
* the editor, which is the only reason this copy exists at all. */
|
|
760
|
+
.kywi-frontend-edit--browsing { padding-top: var(--kywi-browse-bar-height, 44px); }
|
|
723
761
|
.kywi-edit-toolbar-spacer { flex: 1; }
|
|
724
762
|
.kywi-edit-toolbar-title { font-weight: 600; }
|
|
725
763
|
.kywi-edit-toolbar-status {
|