@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
package/AGENT-PATTERNS.md
CHANGED
|
@@ -441,20 +441,22 @@ interchangeable.
|
|
|
441
441
|
page is complete with JavaScript off. This is the default for anything the
|
|
442
442
|
server can know — rule-based audiences (UTM, referrer, the `kywi_audience`
|
|
443
443
|
pin) and A/B experiments. Since 0.6.3 it also renders in the layout editor as
|
|
444
|
-
a badged block with
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
444
|
+
a badged block with a **variant switcher** (internal editor code —
|
|
445
|
+
`activeArms`, the `VariantArm` type, `data-arm-id` — still says "arm"; the
|
|
446
|
+
persisted layout field is `variants`), each variant editable with the
|
|
447
|
+
ordinary section/column/module tools, so server-resolution no longer costs
|
|
448
|
+
the owner their editing surface. Since kywi-cms#119, the owner doesn't need
|
|
449
|
+
an agent to hand-author this shape at all: the section chrome — in the
|
|
450
|
+
admin layout editor and in the in-place front-of-site overlay alike —
|
|
451
|
+
offers **Personalize this section** / **A/B test** actions that wrap an
|
|
452
|
+
ordinary section into a section-level variant container on the spot,
|
|
453
|
+
seeding the default variant from the section's current content and
|
|
454
|
+
opening straight into its config (audience/experiment picker included).
|
|
455
|
+
An agent only needs to reach for `update_layout` when scripting bulk
|
|
456
|
+
changes or building a container the UI can't reach.
|
|
455
457
|
- **Module-level** — the `variantContainer` *module*, placed in a column; its
|
|
456
|
-
|
|
457
|
-
content}]`). Every
|
|
458
|
+
variants are HTML strings (`defaultContent`, `variants: [{audienceId, label,
|
|
459
|
+
content}]`). Every variant ships in the HTML (default visible, the rest
|
|
458
460
|
`display:none`) and the browser runtime reveals the matching one. Use it
|
|
459
461
|
**only for signals the server cannot read at first paint** — in practice, a
|
|
460
462
|
self-ID answer held in `localStorage`.
|
package/README.md
CHANGED
|
@@ -166,6 +166,73 @@ text, button …) types straight on the page (committed on blur through the same
|
|
|
166
166
|
deselects, and a persistent **Done** leaves — confirming first if there are
|
|
167
167
|
unsaved changes.
|
|
168
168
|
|
|
169
|
+
### Sticky site headers and the editor's bars
|
|
170
|
+
|
|
171
|
+
Two fixed bars can appear at the top of a public page for a signed-in editor:
|
|
172
|
+
the **browse-mode toolbar** (`.kywi-edit-toolbar`, 44px) and the **edit-mode
|
|
173
|
+
bar** (`.kywi-overlay-toolbar`, 46px). Each publishes its height as a token on
|
|
174
|
+
`:root`, so a theme never has to hard-code either number:
|
|
175
|
+
|
|
176
|
+
| Token | Bar | Default |
|
|
177
|
+
| --- | --- | --- |
|
|
178
|
+
| `--kywi-browse-bar-height` | browse-mode toolbar | `44px` |
|
|
179
|
+
| `--kywi-overlay-bar-height` | edit-mode bar | `46px` |
|
|
180
|
+
|
|
181
|
+
Both are declared on `:root` by **`./site/styles.css`**, which every public page
|
|
182
|
+
loads — and again, identically, by the layout editor's `editor.css`. The second
|
|
183
|
+
sheet alone is not enough to rely on: a generated app pulls the admin design
|
|
184
|
+
system in behind `next/dynamic`, so `editor.css` is absent from the page until
|
|
185
|
+
someone opens the overlay editor, and browse mode would resolve neither token.
|
|
186
|
+
Give every `var()` on these tokens the matching px fallback anyway (`44px` /
|
|
187
|
+
`46px`) — a `var()` whose token is undefined falls back to the property's
|
|
188
|
+
*initial* value, and `top: auto` un-sticks an element outright rather than
|
|
189
|
+
merely mis-offsetting it.
|
|
190
|
+
|
|
191
|
+
While a bar is up, the document is offset by its height, which is enough for a
|
|
192
|
+
header in normal flow. It is **not** enough for `position: sticky; top: 0` (or
|
|
193
|
+
`position: fixed; top: 0`): document padding moves an element's flow position,
|
|
194
|
+
not the line it pins to once it sticks — so a sticky nav would pin to viewport
|
|
195
|
+
y=0 underneath the bar as soon as the reader scrolled. The editor therefore
|
|
196
|
+
detects top-anchored elements when a bar mounts and patches each one's inline
|
|
197
|
+
`top` to the bar's token for the session, restoring the original exactly on the
|
|
198
|
+
way out (`packages/core/src/scope/sticky-offset.ts`). Nothing is required of a
|
|
199
|
+
theme for this to work.
|
|
200
|
+
|
|
201
|
+
Two things are worth knowing if you are writing one:
|
|
202
|
+
|
|
203
|
+
- **A sticky element that mounts after the editor opens is not offset.** The
|
|
204
|
+
scan runs once, when the bar mounts; a cookie bar on a timer or a menu that is
|
|
205
|
+
`display: none` at that moment keeps its own `top`. Anchor those yourself off
|
|
206
|
+
the class the bar puts on `<html>` — `kywi-frontend-edit--browsing` or
|
|
207
|
+
`kywi-frontend-edit--editing`, which are also how you special-case anything
|
|
208
|
+
else for an editing session:
|
|
209
|
+
|
|
210
|
+
```css
|
|
211
|
+
.my-late-sticky { top: 0; }
|
|
212
|
+
.kywi-frontend-edit--browsing .my-late-sticky { top: var(--kywi-browse-bar-height, 44px); }
|
|
213
|
+
.kywi-frontend-edit--editing .my-late-sticky { top: var(--kywi-overlay-bar-height, 46px); }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
A second tier keeps its own offset on top of the bar:
|
|
217
|
+
|
|
218
|
+
```css
|
|
219
|
+
.my-late-subnav { top: 64px; }
|
|
220
|
+
.kywi-frontend-edit--browsing .my-late-subnav { top: calc(var(--kywi-browse-bar-height, 44px) + 64px); }
|
|
221
|
+
.kywi-frontend-edit--editing .my-late-subnav { top: calc(var(--kywi-overlay-bar-height, 46px) + 64px); }
|
|
222
|
+
```
|
|
223
|
+
- **A whole sticky STACK moves, not just its first tier.** Anything with a
|
|
224
|
+
computed `top` within 4px of zero is offset by one bar height; anything
|
|
225
|
+
between there and 200px — a sub-nav parked under the header at `top: 64px`,
|
|
226
|
+
the docs-site pattern — is offset by the bar *plus its own original value*, so
|
|
227
|
+
the gaps a theme built survive the session. Moving only the first tier would
|
|
228
|
+
make the header cover the sub-nav, an overlap the offset itself would have
|
|
229
|
+
created.
|
|
230
|
+
- **Further down than that, and inside a scroll pane, is left alone.** A sticky
|
|
231
|
+
filter panel at `top: 320px` is anchored to page content rather than to the
|
|
232
|
+
top of the viewport, and a sticky table head inside an `overflow: auto` pane
|
|
233
|
+
pins to that pane, not to the window — the bar never covers either, so neither
|
|
234
|
+
moves.
|
|
235
|
+
|
|
169
236
|
A host must therefore NOT wrap the editor in `.kywi-admin-shell`: that class is
|
|
170
237
|
the admin design system's base + reset and would re-typeset the page being
|
|
171
238
|
edited. Pass the page's own body-wrapper class as `pageClassName` instead, so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-overlay.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/column-overlay.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAe,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"column-overlay.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/column-overlay.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAe,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CA+BhH"}
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
export function ColumnOverlay({ columnId, width, inPlace = false, rootRef, dropId, children }) {
|
|
4
4
|
if (inPlace) {
|
|
5
|
-
return (_jsx("div", { ref: rootRef, className: `kywi-column kywi-col-${width} kywi-ip-column`,
|
|
5
|
+
return (_jsx("div", { ref: rootRef, className: `kywi-column kywi-col-${width} kywi-ip-column`,
|
|
6
|
+
// kywi-cms#138: the drop indicator is an absolutely-positioned overlay
|
|
7
|
+
// inside this column, so the column has to be its containing block —
|
|
8
|
+
// the site's own `.kywi-column` carries no positioning. Inline and
|
|
9
|
+
// editor-only: this component never renders outside the editor, so it
|
|
10
|
+
// cannot leak into the published page.
|
|
11
|
+
style: { position: 'relative' }, "data-kywi-column": "", "data-kywi-col-width": width, "data-column-id": columnId, ...(dropId ? { 'data-kywi-column-drop': dropId } : {}), children: children }));
|
|
6
12
|
}
|
|
7
13
|
return (_jsxs("div", { className: "kywi-editor-column", style: { flex: width }, "data-column-id": columnId, children: [_jsxs("div", { className: "kywi-editor-column-label", children: ["col-", width] }), children] }));
|
|
8
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-overlay.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/column-overlay.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AA+BZ,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAsB;IAC/G,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CACL,cACE,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,wBAAwB,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"column-overlay.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/column-overlay.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AA+BZ,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAsB;IAC/G,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CACL,cACE,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,wBAAwB,KAAK,iBAAiB;YACzD,uEAAuE;YACvE,qEAAqE;YACrE,mEAAmE;YACnE,sEAAsE;YACtE,uCAAuC;YACvC,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,sBACd,EAAE,yBACE,KAAK,oBACV,QAAQ,KACpB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,uBAAuB,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAEtD,QAAQ,GACL,CACP,CAAA;IACH,CAAC;IACD,OAAO,CACL,eACE,SAAS,EAAC,oBAAoB,EAC9B,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,oBACN,QAAQ,aAExB,eAAK,SAAS,EAAC,0BAA0B,qBAAM,KAAK,IAAO,EAC1D,QAAQ,IACL,CACP,CAAA;AACH,CAAC"}
|
|
@@ -2,7 +2,24 @@ import type { DropFeedbackState } from './drag-utils.js';
|
|
|
2
2
|
interface DropIndicatorProps {
|
|
3
3
|
state: DropFeedbackState;
|
|
4
4
|
moduleType?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Y of the slot boundary this marks, in the column's own coordinate space
|
|
7
|
+
* (kywi-cms#138).
|
|
8
|
+
*
|
|
9
|
+
* The indicator is OVERLAY chrome: absolutely positioned inside the column,
|
|
10
|
+
* because drawn in the flow it pushed every module below the boundary down by
|
|
11
|
+
* its own height — measured at +19px as a line and +78px as the placeholder —
|
|
12
|
+
* the instant the drag projected a slot. The module the owner was aiming at
|
|
13
|
+
* slid out from under the cursor mid-drag, so aiming at the bottom of a
|
|
14
|
+
* module landed the drop one slot early. Positioned, it costs the column
|
|
15
|
+
* nothing.
|
|
16
|
+
*
|
|
17
|
+
* `null` while the boundary has not been measured yet (the first render of a
|
|
18
|
+
* new projection, before the layout effect that reads the slots runs) — that
|
|
19
|
+
* frame draws at the top of the column and is corrected before paint.
|
|
20
|
+
*/
|
|
21
|
+
top: number | null;
|
|
5
22
|
}
|
|
6
|
-
export declare function DropIndicator({ state, moduleType }: DropIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
23
|
+
export declare function DropIndicator({ state, moduleType, top }: DropIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
7
24
|
export {};
|
|
8
25
|
//# sourceMappingURL=drop-indicator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drop-indicator.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/drop-indicator.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"drop-indicator.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/drop-indicator.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AASxD,UAAU,kBAAkB;IAC1B,KAAK,EAAE,iBAAiB,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AAED,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,kBAAkB,kDAgC3E"}
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Line thickness, mirrored from `.kywi-editor-drop-line`'s `height` in
|
|
5
|
+
* editor.css so the line can be CENTRED on the boundary it marks. The two must
|
|
6
|
+
* agree; a line drawn half a pixel off is not worth a measurement per frame.
|
|
7
|
+
*/
|
|
8
|
+
const LINE_HEIGHT = 3;
|
|
9
|
+
export function DropIndicator({ state, moduleType, top }) {
|
|
4
10
|
if (state === 'idle')
|
|
5
11
|
return null;
|
|
12
|
+
const boundary = top ?? 0;
|
|
13
|
+
const overlay = {
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
left: 0,
|
|
16
|
+
right: 0,
|
|
17
|
+
pointerEvents: 'none',
|
|
18
|
+
// Above the modules and their own chrome (type chip / action row, z-index 3
|
|
19
|
+
// in editor.css) and below the column resize handle (6) — this is canvas
|
|
20
|
+
// chrome, not part of the floating-chrome tiers that start at 998.
|
|
21
|
+
zIndex: 5,
|
|
22
|
+
};
|
|
6
23
|
if (state === 'line') {
|
|
7
|
-
return _jsx("div", { className: "kywi-editor-drop-line" });
|
|
24
|
+
return (_jsx("div", { className: "kywi-editor-drop-line", style: { ...overlay, top: boundary - LINE_HEIGHT / 2 } }));
|
|
8
25
|
}
|
|
9
|
-
|
|
26
|
+
// Top edge ON the boundary, extending downwards: it overlaps the module that
|
|
27
|
+
// would be displaced, which is the honest picture — that is the room the
|
|
28
|
+
// dropped module takes and where the displaced one goes.
|
|
29
|
+
return (_jsxs("div", { className: "kywi-editor-drop-placeholder", style: { ...overlay, top: boundary }, children: [moduleType && _jsxs("span", { children: [moduleType, " will be placed here"] }), _jsx("div", { style: { fontSize: '10px', opacity: 0.7, marginTop: 2, textTransform: 'none', fontWeight: 500 }, children: "Release to drop" })] }));
|
|
10
30
|
}
|
|
11
31
|
//# sourceMappingURL=drop-indicator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drop-indicator.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/drop-indicator.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;
|
|
1
|
+
{"version":3,"file":"drop-indicator.js","sourceRoot":"","sources":["../../../src/admin/layout-editor/drop-indicator.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAIZ;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,CAAA;AAwBrB,MAAM,UAAU,aAAa,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAsB;IAC1E,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAEjC,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,CAAA;IACzB,MAAM,OAAO,GAAwB;QACnC,QAAQ,EAAE,UAAU;QACpB,IAAI,EAAE,CAAC;QACP,KAAK,EAAE,CAAC;QACR,aAAa,EAAE,MAAM;QACrB,4EAA4E;QAC5E,yEAAyE;QACzE,mEAAmE;QACnE,MAAM,EAAE,CAAC;KACV,CAAA;IAED,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,CACL,cAAK,SAAS,EAAC,uBAAuB,EAAC,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,WAAW,GAAG,CAAC,EAAE,GAAI,CAClG,CAAA;IACH,CAAC;IAED,6EAA6E;IAC7E,yEAAyE;IACzE,yDAAyD;IACzD,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,EAAC,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,aAC/E,UAAU,IAAI,2BAAO,UAAU,4BAA4B,EAC5D,cAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,gCAE9F,IACF,CACP,CAAA;AACH,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { EditorState, EditorAction } from './editor-state.js';
|
|
3
|
-
import type { InlineTextProp } from './inline-text.js';
|
|
3
|
+
import type { InlineTextProp, InlineTextBindingTarget } from './inline-text.js';
|
|
4
4
|
/** Resolve a connected component node to its live definition for canvas preview. */
|
|
5
5
|
type ComponentPreviewResolver = (componentId: string) => {
|
|
6
6
|
props: Record<string, unknown>;
|
|
@@ -23,15 +23,19 @@ export interface InlineEditController {
|
|
|
23
23
|
resetId: string | null;
|
|
24
24
|
/** Bumped each time an edit session ends. */
|
|
25
25
|
nonce: number;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The element + prop a gesture inside one module's wrapper binds, or null when
|
|
28
|
+
* there is nothing safe to bind (kywi-cms#145 — see `resolveInlineBinding`).
|
|
29
|
+
*/
|
|
30
|
+
resolveBinding: (moduleType: string, wrapper: HTMLElement, target: Element | null) => InlineTextBindingTarget | null;
|
|
28
31
|
onActivate: (moduleId: string) => void;
|
|
29
|
-
onCommit: (moduleId: string, host: HTMLElement) => void;
|
|
32
|
+
onCommit: (moduleId: string, host: HTMLElement, prop: InlineTextProp) => void;
|
|
30
33
|
onCancel: () => void;
|
|
31
34
|
/**
|
|
32
|
-
* Double-click fallback for a
|
|
33
|
-
* Selecting is the caller's job (via the
|
|
34
|
-
* moves focus into the already-open props
|
|
35
|
+
* Double-click fallback for a gesture with nothing safe to bind
|
|
36
|
+
* (kywi-cms#118, kywi-cms#145). Selecting is the caller's job (via the
|
|
37
|
+
* module's own `onSelect`); this only moves focus into the already-open props
|
|
38
|
+
* rail once that selection commits.
|
|
35
39
|
*/
|
|
36
40
|
focusPropsRail: () => void;
|
|
37
41
|
}
|
|
@@ -55,9 +59,28 @@ interface EditorCanvasProps {
|
|
|
55
59
|
* chips, handles) float above it instead of boxing it into an artboard.
|
|
56
60
|
*/
|
|
57
61
|
inPlace?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether the front editor's left module-palette rail is expanded
|
|
64
|
+
* (kywi-cms#132). The rail is fixed to the viewport edge and floats over the
|
|
65
|
+
* page's left margin, so a full-width section's floating chrome renders
|
|
66
|
+
* underneath it and cannot be clicked — the chrome moves out of its way, and
|
|
67
|
+
* this is the only channel that tells it to. Meaningful in in-place mode
|
|
68
|
+
* only: the admin artboard's palette is an in-flow column.
|
|
69
|
+
*/
|
|
70
|
+
paletteOpen?: boolean;
|
|
58
71
|
/** Inline text editing controller, only meaningful in in-place mode. */
|
|
59
72
|
inlineEdit?: InlineEditController | null;
|
|
60
73
|
}
|
|
61
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Memoized (kywi-cms#142): the canvas re-renders on every editor-state
|
|
76
|
+
* change, including drag feedback published elsewhere in the tree
|
|
77
|
+
* (DropTargetContext, consumed by DroppableColumn below `EditorCanvas`'s own
|
|
78
|
+
* props) that doesn't change any of ITS props. `React.memo`'s default
|
|
79
|
+
* shallow-equal prop comparison skips re-rendering the whole canvas subtree
|
|
80
|
+
* for those, since `state`/`dispatch`/`moduleComponents` etc. are stable
|
|
81
|
+
* references from the shells (LayoutEditor / OverlayShell) across a
|
|
82
|
+
* drop-target update.
|
|
83
|
+
*/
|
|
84
|
+
export declare const EditorCanvas: React.NamedExoticComponent<EditorCanvasProps>;
|
|
62
85
|
export {};
|
|
63
86
|
//# sourceMappingURL=editor-canvas.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor-canvas.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/editor-canvas.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAkBlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"editor-canvas.d.ts","sourceRoot":"","sources":["../../../src/admin/layout-editor/editor-canvas.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAkBlE,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAK/E,oFAAoF;AACpF,KAAK,wBAAwB,GAAG,CAC9B,WAAW,EAAE,MAAM,KAChB;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAEjG;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,cAAc,EAAE,CACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,OAAO,GAAG,IAAI,KACnB,uBAAuB,GAAG,IAAI,CAAA;IACnC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC7E,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,IAAI,CAAA;CAC3B;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;IACtC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC,CAAA;IACzH,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,wBAAwB,CAAA;IAC3C,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,wEAAwE;IACxE,UAAU,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;CACzC;AA8pBD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,+CAqGvB,CAAA"}
|
|
@@ -15,7 +15,7 @@ import { ColumnResizeHandle } from './column-resize-handle.js';
|
|
|
15
15
|
import { ModuleOverlay } from './module-overlay.js';
|
|
16
16
|
import { DropIndicator } from './drop-indicator.js';
|
|
17
17
|
import { DropTargetContext } from './drag-feedback.js';
|
|
18
|
-
import { useChromeFlip } from './use-chrome-flip.js';
|
|
18
|
+
import { useChromeFlip, chromeOffsetClasses, chromeOffsetStyle } from './use-chrome-flip.js';
|
|
19
19
|
import { ModuleEditorContext } from '../../layout/module-render-context.js';
|
|
20
20
|
/**
|
|
21
21
|
* Render a node's live preview in the canvas. A connected component node
|
|
@@ -63,23 +63,68 @@ function getRegions(state) {
|
|
|
63
63
|
return state.layout.regions;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Build the inline-editing binding for one module, or null when the
|
|
67
|
-
*
|
|
66
|
+
* Build the inline-editing binding for one module, or null when the canvas
|
|
67
|
+
* isn't in in-place mode.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately NOT gated on the module having an editable prop any more
|
|
70
|
+
* (kywi-cms#145): what a gesture can edit depends on where in the module it
|
|
71
|
+
* landed, so the decision moved to `resolve`, per gesture. A module with nothing
|
|
72
|
+
* bindable resolves to null every time and its double-click falls through to
|
|
73
|
+
* `onInlineFallback`.
|
|
68
74
|
*/
|
|
69
75
|
function inlineTextBindingFor(nodeId, nodeType, inPlace, inlineEdit) {
|
|
70
76
|
if (!inPlace || !inlineEdit)
|
|
71
77
|
return null;
|
|
72
|
-
const prop = inlineEdit.resolveTextProp(nodeType);
|
|
73
|
-
if (!prop)
|
|
74
|
-
return null;
|
|
75
78
|
return {
|
|
76
79
|
active: inlineEdit.editingId === nodeId,
|
|
77
|
-
|
|
80
|
+
resolve: (wrapper, target) => inlineEdit.resolveBinding(nodeType, wrapper, target),
|
|
78
81
|
onActivate: () => inlineEdit.onActivate(nodeId),
|
|
79
|
-
onCommit: (host) => inlineEdit.onCommit(nodeId, host),
|
|
82
|
+
onCommit: (host, prop) => inlineEdit.onCommit(nodeId, host, prop),
|
|
80
83
|
onCancel: () => inlineEdit.onCancel(),
|
|
81
84
|
};
|
|
82
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Y of the slot boundary `index` marks, in the column's own coordinate space —
|
|
88
|
+
* or null when there is nothing to measure against (kywi-cms#138).
|
|
89
|
+
*
|
|
90
|
+
* `offsetTop`/`offsetHeight` rather than `getBoundingClientRect()`: the column
|
|
91
|
+
* is the slots' offset parent (both shells position it, see `DroppableColumn`
|
|
92
|
+
* below), so these are already expressed in exactly the space an absolutely
|
|
93
|
+
* positioned child of that column is placed in — no scroll or viewport maths in
|
|
94
|
+
* between, and nothing to re-derive when the page scrolls mid-drag.
|
|
95
|
+
*
|
|
96
|
+
* Direct children only — deliberately narrower than `resolveColumnDropIndex`,
|
|
97
|
+
* which reads `querySelectorAll('[data-kywi-module-slot]')` and so counts
|
|
98
|
+
* descendants at any depth. The two agree today because only `ModuleOverlay`
|
|
99
|
+
* emits `data-kywi-module-slot` and a column never nests another column, so
|
|
100
|
+
* every slot inside a column IS a direct child. If a module ever renders its
|
|
101
|
+
* own column (or anything else emits that attribute), the two would diverge —
|
|
102
|
+
* the indicator would measure against one set of slots while the drop resolves
|
|
103
|
+
* against another — and this is the place that has to change with it.
|
|
104
|
+
*
|
|
105
|
+
* Only ever called for a PALETTE drag (`useEditorDnd` projects a drop target
|
|
106
|
+
* for nothing else), which is what makes the measurement trustworthy: during a
|
|
107
|
+
* canvas reorder the dragged slot carries a live dnd-kit transform and reports
|
|
108
|
+
* where it has been dragged to rather than where it sits.
|
|
109
|
+
*/
|
|
110
|
+
function slotBoundaryOffset(columnEl, index) {
|
|
111
|
+
if (!columnEl)
|
|
112
|
+
return null;
|
|
113
|
+
const slots = [];
|
|
114
|
+
// `Array.from`, not `for…of` over the collection: the package's `lib` is
|
|
115
|
+
// ["ES2022", "DOM"] without "DOM.Iterable".
|
|
116
|
+
for (const child of Array.from(columnEl.children)) {
|
|
117
|
+
if (child.hasAttribute('data-kywi-module-slot'))
|
|
118
|
+
slots.push(child);
|
|
119
|
+
}
|
|
120
|
+
// An empty column has one slot boundary and it is the top of the column.
|
|
121
|
+
const last = slots[slots.length - 1];
|
|
122
|
+
if (!last)
|
|
123
|
+
return 0;
|
|
124
|
+
if (index >= slots.length)
|
|
125
|
+
return last.offsetTop + last.offsetHeight;
|
|
126
|
+
return slots[index].offsetTop;
|
|
127
|
+
}
|
|
83
128
|
// ── DroppableColumn ───────────────────────────────────────────────────────────
|
|
84
129
|
function DroppableColumn({ column, state, dispatch, moduleComponents, resolveComponent, inPlace = false, inlineEdit, }) {
|
|
85
130
|
// kywi-cms#20: this droppable represents the column's own (empty) space —
|
|
@@ -104,31 +149,57 @@ function DroppableColumn({ column, state, dispatch, moduleComponents, resolveCom
|
|
|
104
149
|
// draws nothing — which is the point: the owner sees a single destination.
|
|
105
150
|
const dropTarget = React.useContext(DropTargetContext);
|
|
106
151
|
const target = dropTarget?.columnId === column.id ? dropTarget : null;
|
|
152
|
+
// kywi-cms#138: the drop element doubles as the indicator's positioning
|
|
153
|
+
// context, so the indicator has to be measured against the same element it is
|
|
154
|
+
// placed inside. `setNodeRef` is stable (dnd-kit builds it once per hook), so
|
|
155
|
+
// this callback is too — a changing ref callback would hand dnd-kit a `null`
|
|
156
|
+
// node on every render, mid-drag included.
|
|
157
|
+
const columnElRef = React.useRef(null);
|
|
158
|
+
const attachColumnRef = React.useCallback((el) => {
|
|
159
|
+
columnElRef.current = el;
|
|
160
|
+
setNodeRef(el);
|
|
161
|
+
}, [setNodeRef]);
|
|
162
|
+
// Measured, not guessed: the boundary's y in the column's own coordinates.
|
|
163
|
+
// `useLayoutEffect` so the correction lands before the browser paints — the
|
|
164
|
+
// first frame of a new projection renders at the top of the column and is
|
|
165
|
+
// never seen there.
|
|
166
|
+
const [indicatorTop, setIndicatorTop] = React.useState(null);
|
|
167
|
+
const targetIndex = target ? target.index : null;
|
|
168
|
+
React.useLayoutEffect(() => {
|
|
169
|
+
setIndicatorTop(targetIndex === null ? null : slotBoundaryOffset(columnElRef.current, targetIndex));
|
|
170
|
+
}, [targetIndex, column.nodes.length]);
|
|
107
171
|
/**
|
|
108
|
-
* The
|
|
109
|
-
* the
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
172
|
+
* The projected drop boundary, drawn as ONE absolutely-positioned element at
|
|
173
|
+
* the end of the column rather than spliced into the module flow.
|
|
174
|
+
*
|
|
175
|
+
* In the flow it was self-defeating: the indicator's own height pushed
|
|
176
|
+
* everything below the boundary down the moment the drag projected it —
|
|
177
|
+
* measured on the e2e fixture, +19px for the line and +78px once it escalated
|
|
178
|
+
* to the placeholder — so the module the owner was aiming at moved out from
|
|
179
|
+
* under the cursor and the projection re-resolved one slot early. As an
|
|
180
|
+
* overlay it promises the same slot without touching the layout it describes.
|
|
113
181
|
*/
|
|
114
|
-
const
|
|
115
|
-
const content = (_jsxs(_Fragment, { children: [column.nodes.length === 0 && (_jsx("div", { className: inPlace ? 'kywi-ip-empty-column' : 'kywi-editor-column-empty', children: "Drag a module here" })),
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
182
|
+
const indicator = target ? (_jsx(DropIndicator, { state: target.feedback, top: indicatorTop, ...(target.label ? { moduleType: target.label } : {}) })) : null;
|
|
183
|
+
const content = (_jsxs(_Fragment, { children: [column.nodes.length === 0 && (_jsx("div", { className: inPlace ? 'kywi-ip-empty-column' : 'kywi-editor-column-empty', children: "Drag a module here" })), _jsx(SortableContext, { items: moduleIds, strategy: verticalListSortingStrategy, children: column.nodes.map((node, index) => {
|
|
184
|
+
const inlineText = inlineTextBindingFor(node.id, node.type, inPlace, inlineEdit);
|
|
185
|
+
const select = () => dispatch({ type: 'SELECT', id: node.id, selectionType: 'module' });
|
|
186
|
+
// Nothing safe to bind (kywi-cms#118): double-click would otherwise be
|
|
187
|
+
// a silent no-op, so route it to the props rail instead — select the
|
|
188
|
+
// module, then focus its first field once that lands. Now always
|
|
189
|
+
// supplied, because whether a gesture binds is only known when it
|
|
190
|
+
// happens (kywi-cms#145): the overlay calls this when its own
|
|
191
|
+
// resolution comes back empty.
|
|
192
|
+
const onInlineFallback = inPlace && inlineEdit
|
|
193
|
+
? () => {
|
|
194
|
+
select();
|
|
195
|
+
inlineEdit.focusPropsRail();
|
|
196
|
+
}
|
|
197
|
+
: undefined;
|
|
198
|
+
// Re-key the preview after an edit session so contenteditable's DOM
|
|
199
|
+
// leftovers are replaced by a clean render from the stored props.
|
|
200
|
+
const previewKey = inlineEdit && inlineEdit.resetId === node.id ? `${node.id}:${inlineEdit.nonce}` : node.id;
|
|
201
|
+
return (_jsx(React.Fragment, { children: _jsx(ModuleOverlay, { moduleId: node.id, instanceId: node.instanceId, columnId: column.id, index: index, isSelected: state.selectedId === node.id, typeLabel: node.type, inPlace: inPlace, moduleType: node.type, inlineText: inlineText, ...(onInlineFallback ? { onInlineFallback } : {}), onSelect: select, children: _jsx(ModulePreview, { node: node, moduleComponents: moduleComponents, ...(resolveComponent ? { resolveComponent } : {}) }, previewKey) }) }, node.id));
|
|
202
|
+
}) }), indicator] }));
|
|
132
203
|
// In place the column element IS the drop zone: an extra wrapper div would
|
|
133
204
|
// sit between `.kywi-column` and the module wrappers and break the site's own
|
|
134
205
|
// child selectors. In the artboard the wrapper also carries a minimum height
|
|
@@ -136,8 +207,11 @@ function DroppableColumn({ column, state, dispatch, moduleComponents, resolveCom
|
|
|
136
207
|
//
|
|
137
208
|
// Either way the element carrying `setNodeRef` also carries
|
|
138
209
|
// `data-kywi-column-drop` (kywi-cms#134) — in place that's the column element
|
|
139
|
-
// itself, so the id goes through `ColumnOverlay`'s `dropId
|
|
140
|
-
|
|
210
|
+
// itself, so the id goes through `ColumnOverlay`'s `dropId` — and is the drop
|
|
211
|
+
// indicator's positioning context (kywi-cms#138), which is why the artboard
|
|
212
|
+
// wrapper is `position: relative` here and the in-place column is inside
|
|
213
|
+
// `ColumnOverlay`.
|
|
214
|
+
return inPlace ? (_jsx(ColumnOverlay, { columnId: column.id, width: column.width, inPlace: true, rootRef: attachColumnRef, dropId: column.id, children: content })) : (_jsx(ColumnOverlay, { columnId: column.id, width: column.width, children: _jsx("div", { ref: attachColumnRef, "data-kywi-column-drop": column.id, style: { position: 'relative', minHeight: 40 }, children: content }) }));
|
|
141
215
|
}
|
|
142
216
|
// ── SectionBlock ──────────────────────────────────────────────────────────────
|
|
143
217
|
/**
|
|
@@ -146,7 +220,7 @@ function DroppableColumn({ column, state, dispatch, moduleComponents, resolveCom
|
|
|
146
220
|
* container arm (kywi-cms#92) — arm content is edited with exactly the same
|
|
147
221
|
* machinery, because the reducer's lookups are arm-aware.
|
|
148
222
|
*/
|
|
149
|
-
function SectionBlock({ section, state, dispatch, moduleComponents, resolveComponent, inPlace = false, inArm = false, inlineEdit, }) {
|
|
223
|
+
function SectionBlock({ section, state, dispatch, moduleComponents, resolveComponent, inPlace = false, paletteOpen = false, inArm = false, inlineEdit, }) {
|
|
150
224
|
// In place, the section carries its OWN resolved style exactly as the public
|
|
151
225
|
// SectionRenderer applies it (kywi-cms#94) — otherwise a section's background
|
|
152
226
|
// or padding would vanish the moment the owner started editing, which is the
|
|
@@ -172,7 +246,7 @@ function SectionBlock({ section, state, dispatch, moduleComponents, resolveCompo
|
|
|
172
246
|
{ icon: 'personalize', label: 'Personalize', onClick: wrapInContainer('personalization') },
|
|
173
247
|
{ icon: 'variant', label: 'A/B test', onClick: wrapInContainer('ab_test') },
|
|
174
248
|
];
|
|
175
|
-
return (_jsx(SectionOverlay, { sectionId: section.id, isSelected: state.selectedId === section.id, inPlace: inPlace, ...(inArm ? {} : { actions: wrapActions }), ...(resolved ? { inPlaceStyle: resolved.base, responsiveCss: resolved.responsiveCss } : {}), onSelect: () => dispatch({ type: 'SELECT', id: section.id, selectionType: 'section' }), onAddColumn: () => dispatch({ type: 'ADD_COLUMN', sectionId: section.id }), onDelete: () => dispatch({ type: 'REMOVE_SECTION', sectionId: section.id }), children: section.columns.map((column, ci) => (_jsxs(React.Fragment, { children: [_jsx(DroppableColumn, { column: column, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) }), !inPlace && ci < section.columns.length - 1 && (_jsx(ColumnResizeHandle, { leftWidth: column.width, rightWidth: section.columns[ci + 1].width, onResize: (lw, rw) => {
|
|
249
|
+
return (_jsx(SectionOverlay, { sectionId: section.id, isSelected: state.selectedId === section.id, inPlace: inPlace, paletteOpen: paletteOpen, ...(inArm ? {} : { actions: wrapActions }), ...(resolved ? { inPlaceStyle: resolved.base, responsiveCss: resolved.responsiveCss } : {}), onSelect: () => dispatch({ type: 'SELECT', id: section.id, selectionType: 'section' }), onAddColumn: () => dispatch({ type: 'ADD_COLUMN', sectionId: section.id }), onDelete: () => dispatch({ type: 'REMOVE_SECTION', sectionId: section.id }), children: section.columns.map((column, ci) => (_jsxs(React.Fragment, { children: [_jsx(DroppableColumn, { column: column, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) }), !inPlace && ci < section.columns.length - 1 && (_jsx(ColumnResizeHandle, { leftWidth: column.width, rightWidth: section.columns[ci + 1].width, onResize: (lw, rw) => {
|
|
176
250
|
const widths = section.columns.map((c, i) => i === ci ? lw : i === ci + 1 ? rw : c.width);
|
|
177
251
|
dispatch({ type: 'RESIZE_COLUMNS', sectionId: section.id, widths });
|
|
178
252
|
} }))] }, column.id))) }));
|
|
@@ -192,7 +266,7 @@ function formatWeight(weight) {
|
|
|
192
266
|
* way — the canvas is showing whichever arm is selected here.
|
|
193
267
|
*/
|
|
194
268
|
function ArmTabs({ arms, activeArm, isAbTest, onSelect, }) {
|
|
195
|
-
return (_jsx("div", { className: "kywi-editor-vc-arms", role: "tablist", "aria-label": "
|
|
269
|
+
return (_jsx("div", { className: "kywi-editor-vc-arms", role: "tablist", "aria-label": "Variants", children: arms.map(arm => (_jsxs("button", { type: "button", role: "tab", "aria-selected": arm.id === activeArm, "data-arm-id": arm.id, className: `kywi-editor-vc-arm-btn${arm.id === activeArm ? ' kywi-editor-vc-arm-btn--active' : ''}`, onClick: (e) => {
|
|
196
270
|
e.stopPropagation();
|
|
197
271
|
onSelect(arm.id);
|
|
198
272
|
}, children: [arm.label, isAbTest && arm.weight !== null && (_jsx("span", { className: "kywi-editor-vc-arm-weight", children: formatWeight(arm.weight) }))] }, arm.id))) }));
|
|
@@ -222,7 +296,7 @@ function ArmBody({ inPlace, activeArm, children, }) {
|
|
|
222
296
|
* `.kywi-variant-container` and its badge/arm switcher floats above the band,
|
|
223
297
|
* so a personalized hero looks exactly like the hero while staying switchable.
|
|
224
298
|
*/
|
|
225
|
-
function VariantContainerBlock({ container, state, dispatch, moduleComponents, resolveComponent, inPlace = false, inlineEdit, }) {
|
|
299
|
+
function VariantContainerBlock({ container, state, dispatch, moduleComponents, resolveComponent, inPlace = false, paletteOpen = false, inlineEdit, }) {
|
|
226
300
|
// Collapsed/expanded is presentation-only and per-block, so it stays local.
|
|
227
301
|
// Expanded by default: hiding arm content behind a disclosure would re-create
|
|
228
302
|
// the very "invisible in the editor" problem this block exists to fix.
|
|
@@ -244,18 +318,17 @@ function VariantContainerBlock({ container, state, dispatch, moduleComponents, r
|
|
|
244
318
|
// In place the badge/arm switcher floats above this band; for a personalized
|
|
245
319
|
// hero — a band at the very top of the page, the common case — that lands
|
|
246
320
|
// under the editor's fixed top bar, so it is pinned down inside the band
|
|
247
|
-
// instead (kywi-cms#122)
|
|
321
|
+
// instead (kywi-cms#122), and for a full-width band it also lands behind the
|
|
322
|
+
// open module palette, so it is pushed right out of that rail (kywi-cms#132).
|
|
248
323
|
const containerRef = React.useRef(null);
|
|
249
|
-
const
|
|
324
|
+
const chromeOffsets = useChromeFlip(containerRef, inPlace, paletteOpen);
|
|
250
325
|
return (_jsxs("div", { ref: containerRef, className: rootClassName, "data-variant-container-id": container.id, "data-vc-mode": container.mode, ...(inPlace ? { 'data-variant': activeArm } : {}), "data-testid": "editor-variant-container", children: [_jsxs("div", { className: inPlace
|
|
251
|
-
?
|
|
252
|
-
: 'kywi-editor-vc-toolbar', style: inPlace
|
|
253
|
-
? { '--kywi-ip-chrome-clamp': `${chromeClampPx}px` }
|
|
254
|
-
: undefined, ...(inPlace ? { 'data-kywi-editor-chrome': '', contentEditable: false } : {}), onClick: (e) => {
|
|
326
|
+
? ['kywi-ip-chrome', 'kywi-ip-vc-chrome', ...chromeOffsetClasses(chromeOffsets)].join(' ')
|
|
327
|
+
: 'kywi-editor-vc-toolbar', style: inPlace ? chromeOffsetStyle(chromeOffsets) : undefined, ...(inPlace ? { 'data-kywi-editor-chrome': '', contentEditable: false } : {}), onClick: (e) => {
|
|
255
328
|
// Stop here so the canvas root doesn't immediately deselect.
|
|
256
329
|
e.stopPropagation();
|
|
257
330
|
dispatch({ type: 'SELECT', id: container.id, selectionType: 'variantContainer' });
|
|
258
|
-
}, children: [_jsx("span", { className: "kywi-editor-vc-badge", children: isAbTest ? 'A/B test' : 'Personalized' }), _jsx("span", { className: "kywi-editor-vc-title", children: isAbTest ? 'A/B tested section' : 'Personalized section' }), _jsxs("span", { className: "kywi-editor-vc-arm-count", children: [arms.length, "
|
|
331
|
+
}, children: [_jsx("span", { className: "kywi-editor-vc-badge", children: isAbTest ? 'A/B test' : 'Personalized' }), _jsx("span", { className: "kywi-editor-vc-title", children: isAbTest ? 'A/B tested section' : 'Personalized section' }), _jsxs("span", { className: "kywi-editor-vc-arm-count", children: [arms.length, " variant", arms.length === 1 ? '' : 's'] }), inPlace && expanded && (_jsx(ArmTabs, { arms: arms, activeArm: activeArm, isAbTest: isAbTest, onSelect: selectArm })), _jsx("div", { className: "kywi-editor-vc-toolbar-actions", children: _jsx("button", { type: "button", className: "kywi-editor-section-toolbar-btn", "data-action": "toggle-variant-container", "aria-expanded": expanded, onClick: (e) => { e.stopPropagation(); setExpanded(v => !v); }, children: expanded ? 'Collapse' : 'Expand' }) })] }), expanded && (_jsxs(_Fragment, { children: [!inPlace && (_jsx(ArmTabs, { arms: arms, activeArm: activeArm, isAbTest: isAbTest, onSelect: selectArm })), _jsxs(ArmBody, { inPlace: inPlace, activeArm: activeArm, children: [sections.length === 0 && (_jsx("p", { className: "kywi-editor-vc-arm-empty", children: "This variant renders nothing. Add a section so visitors in it see content." })), sections.map(section => (_jsx(SectionBlock, { section: section, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, paletteOpen: paletteOpen, inArm: true, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) }, section.id))), _jsx("button", { type: "button", className: inPlace ? 'kywi-ip-add-arm-section' : 'kywi-editor-vc-add-section', "data-action": "add-arm-section", ...(inPlace ? { 'data-kywi-editor-chrome': '' } : {}), onClick: (e) => {
|
|
259
332
|
e.stopPropagation();
|
|
260
333
|
dispatch({
|
|
261
334
|
type: 'ADD_ARM_SECTION',
|
|
@@ -263,10 +336,20 @@ function VariantContainerBlock({ container, state, dispatch, moduleComponents, r
|
|
|
263
336
|
armId: activeArm,
|
|
264
337
|
columns: [12],
|
|
265
338
|
});
|
|
266
|
-
}, children: "+ Add section to this
|
|
339
|
+
}, children: "+ Add section to this variant" })] })] }))] }));
|
|
267
340
|
}
|
|
268
341
|
// ── EditorCanvas ──────────────────────────────────────────────────────────────
|
|
269
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Memoized (kywi-cms#142): the canvas re-renders on every editor-state
|
|
344
|
+
* change, including drag feedback published elsewhere in the tree
|
|
345
|
+
* (DropTargetContext, consumed by DroppableColumn below `EditorCanvas`'s own
|
|
346
|
+
* props) that doesn't change any of ITS props. `React.memo`'s default
|
|
347
|
+
* shallow-equal prop comparison skips re-rendering the whole canvas subtree
|
|
348
|
+
* for those, since `state`/`dispatch`/`moduleComponents` etc. are stable
|
|
349
|
+
* references from the shells (LayoutEditor / OverlayShell) across a
|
|
350
|
+
* drop-target update.
|
|
351
|
+
*/
|
|
352
|
+
export const EditorCanvas = React.memo(function EditorCanvas({ state, dispatch, moduleComponents, onInsertSection, resolveComponent, overlayHandles = false, inPlace = false, paletteOpen = false, inlineEdit, }) {
|
|
270
353
|
const regions = getRegions(state);
|
|
271
354
|
// In place the canvas is NOT an artboard: it carries no background, padding
|
|
272
355
|
// or max-width of its own, so the page renders at its real width with its own
|
|
@@ -285,6 +368,6 @@ export function EditorCanvas({ state, dispatch, moduleComponents, onInsertSectio
|
|
|
285
368
|
return (_jsx(ModuleEditorContext.Provider, { value: true, children: _jsxs("div", { className: canvasClassName, onClick: () => dispatch({ type: 'DESELECT' }), children: [totalNodes === 0 && (_jsxs("div", { className: "kywi-editor-canvas-empty", "data-testid": "editor-canvas-empty", children: [_jsx("p", { className: "kywi-editor-canvas-empty-title", children: "This page currently renders its body content." }), _jsx("p", { className: "kywi-editor-canvas-empty-copy", children: "Add a section to design it with the layout engine." }), _jsx("button", { type: "button", className: "kywi-editor-canvas-empty-cta", onClick: (event) => {
|
|
286
369
|
event.stopPropagation();
|
|
287
370
|
onInsertSection(Object.keys(regions)[0] ?? 'main');
|
|
288
|
-
}, children: "+ Add Section" })] })), Object.entries(regions).map(([regionName, nodes]) => (_jsxs(RegionOverlay, { name: regionName, inPlace: inPlace, children: [nodes.filter(isRenderableNode).map((node) => (_jsxs(React.Fragment, { children: [!inPlace && _jsx(SectionInsertLine, { onInsert: () => onInsertSection(regionName) }), isLayoutSection(node) ? (_jsx(SectionBlock, { section: node, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) })) : (_jsx(VariantContainerBlock, { container: node, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) }))] }, node.id))), _jsx(SectionInsertLine, { onInsert: () => onInsertSection(regionName), inPlace: inPlace })] }, regionName)))] }) }));
|
|
289
|
-
}
|
|
371
|
+
}, children: "+ Add Section" })] })), Object.entries(regions).map(([regionName, nodes]) => (_jsxs(RegionOverlay, { name: regionName, inPlace: inPlace, children: [nodes.filter(isRenderableNode).map((node) => (_jsxs(React.Fragment, { children: [!inPlace && _jsx(SectionInsertLine, { onInsert: () => onInsertSection(regionName) }), isLayoutSection(node) ? (_jsx(SectionBlock, { section: node, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, paletteOpen: paletteOpen, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) })) : (_jsx(VariantContainerBlock, { container: node, state: state, dispatch: dispatch, moduleComponents: moduleComponents, inPlace: inPlace, paletteOpen: paletteOpen, ...(inlineEdit ? { inlineEdit } : {}), ...(resolveComponent ? { resolveComponent } : {}) }))] }, node.id))), _jsx(SectionInsertLine, { onInsert: () => onInsertSection(regionName), inPlace: inPlace })] }, regionName)))] }) }));
|
|
372
|
+
});
|
|
290
373
|
//# sourceMappingURL=editor-canvas.js.map
|