@homepages/template-cli 0.1.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/dist/canvas-core/chrome-css.d.ts +24 -0
- package/dist/canvas-core/chrome-css.js +403 -0
- package/dist/canvas-core/chrome.d.ts +49 -0
- package/dist/canvas-core/chrome.js +147 -0
- package/dist/canvas-core/frame.d.ts +20 -0
- package/dist/canvas-core/frame.js +23 -0
- package/dist/canvas-core/hidden-tabs.d.ts +16 -0
- package/dist/canvas-core/hidden-tabs.js +84 -0
- package/dist/canvas-core/resolve-target.js +55 -0
- package/dist/canvas-core/scroll.d.ts +10 -0
- package/dist/canvas-core/scroll.js +15 -0
- package/dist/canvas-core/section-dom.d.ts +6 -0
- package/dist/canvas-core/section-dom.js +43 -0
- package/dist/canvas-core/srcdoc.d.ts +8 -0
- package/dist/canvas-core/srcdoc.js +77 -0
- package/dist/canvas-core/types.d.ts +76 -0
- package/dist/canvas-core.d.ts +9 -0
- package/dist/canvas-core.js +9 -0
- package/dist/cli/check/config.js +41 -0
- package/dist/cli/check/css.js +131 -0
- package/dist/cli/check/diagnostics.js +37 -0
- package/dist/cli/check/index.js +94 -0
- package/dist/cli/check/loader.js +149 -0
- package/dist/cli/check/paths.js +15 -0
- package/dist/cli/check/relativize.js +18 -0
- package/dist/cli/check/render-invariants.js +24 -0
- package/dist/cli/check/resolve-tool.js +38 -0
- package/dist/cli/check/section-assets.js +58 -0
- package/dist/cli/check/stages/deps.js +337 -0
- package/dist/cli/check/stages/lint.js +46 -0
- package/dist/cli/check/stages/render.js +96 -0
- package/dist/cli/check/stages/size.js +274 -0
- package/dist/cli/check/stages/tree.js +206 -0
- package/dist/cli/check/stages/typecheck.js +46 -0
- package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +17 -0
- package/dist/cli/check/stages/validate/facts.js +19 -0
- package/dist/cli/check/stages/validate/formatter-contract.js +14 -0
- package/dist/cli/check/stages/validate/group-contract.js +21 -0
- package/dist/cli/check/stages/validate/index.js +66 -0
- package/dist/cli/check/stages/validate/list-scalar-contract.js +22 -0
- package/dist/cli/check/stages/validate/nav-contract.js +89 -0
- package/dist/cli/check/stages/validate/nested-slot-contract.js +12 -0
- package/dist/cli/check/stages/validate/object-list-contract.js +31 -0
- package/dist/cli/check/stages/validate/orchestrator.js +236 -0
- package/dist/cli/check/stages/validate/produced-by-contract.js +18 -0
- package/dist/cli/check/stages/validate/row-contract.js +31 -0
- package/dist/cli/check/stages/validate/select-contract.js +13 -0
- package/dist/cli/check/stages/validate/source-contract.js +20 -0
- package/dist/cli/check/stages/validate/variant-contract.js +15 -0
- package/dist/cli/check/workspace.js +99 -0
- package/dist/cli/dev/build-css.js +97 -0
- package/dist/cli/dev/catalog.js +35 -0
- package/dist/cli/dev/compose-template.js +39 -0
- package/dist/cli/dev/content-override.js +58 -0
- package/dist/cli/dev/diagnostics.js +68 -0
- package/dist/cli/dev/discover.js +35 -0
- package/dist/cli/dev/fill-state.js +77 -0
- package/dist/cli/dev/index.js +42 -0
- package/dist/cli/dev/inspect.js +38 -0
- package/dist/cli/dev/island-bootstrap.js +38 -0
- package/dist/cli/dev/island-canvas-bootstrap.js +204 -0
- package/dist/cli/dev/island-map.js +34 -0
- package/dist/cli/dev/island-transform.js +31 -0
- package/dist/cli/dev/manifest-instances.js +31 -0
- package/dist/cli/dev/render-section.js +24 -0
- package/dist/cli/dev/screenshot-target.js +24 -0
- package/dist/cli/dev/section-page.js +91 -0
- package/dist/cli/dev/server.js +510 -0
- package/dist/cli/dev/slot-schema.js +12 -0
- package/dist/cli/dev/structure-summary.js +119 -0
- package/dist/cli/dev/tailwind.js +32 -0
- package/dist/cli/dev/tsconfig-paths-plugin.js +90 -0
- package/dist/cli/dev/vite-server.js +39 -0
- package/dist/cli/dev/workspace.js +63 -0
- package/dist/cli/link/index.js +74 -0
- package/dist/cli/link/overlay.js +59 -0
- package/dist/cli/link/watch.js +25 -0
- package/dist/cli/new/emit.js +48 -0
- package/dist/cli/new/index.js +69 -0
- package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
- package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
- package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
- package/dist/cli/new/scaffold/section/_schema.ts +24 -0
- package/dist/cli/new/scaffold/template/_manifest.json +9 -0
- package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
- package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
- package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
- package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
- package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
- package/dist/cli/new/scaffold/template/theme.css +24 -0
- package/dist/cli/new/scaffold/template/theme.ts +27 -0
- package/dist/cli/new/scaffold-assets.js +20 -0
- package/dist/cli/pack/collect.js +44 -0
- package/dist/cli/pack/guards.js +57 -0
- package/dist/cli/pack/index.js +66 -0
- package/dist/cli/pack/manifest.js +15 -0
- package/dist/cli/pack/zip.js +86 -0
- package/dist/cli/theme/generate.js +43 -0
- package/dist/cli/theme/index.js +33 -0
- package/dist/cli/theme/load-theme.js +69 -0
- package/dist/cli/version.js +20 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +139 -0
- package/dist/dev-client/assets/index-CJa9G1Na.css +1 -0
- package/dist/dev-client/assets/index-C_1atZs4.js +450 -0
- package/dist/dev-client/index.html +13 -0
- package/dist/package.js +5 -0
- package/dist/tokens.css +21 -0
- package/package.json +75 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MarkerAttrs } from "./types.js";
|
|
2
|
+
//#region src/canvas-core/chrome-css.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Custom properties the chrome CSS reads but deliberately does not declare —
|
|
5
|
+
* the host must set them on the iframe document's root element. This is the
|
|
6
|
+
* host's obligation, not a suggestion: an adapter that skips one leaves the
|
|
7
|
+
* declared inline fallback in force, which renders plausibly and silently
|
|
8
|
+
* wrong rather than failing.
|
|
9
|
+
*
|
|
10
|
+
* - `--we-panel-w`: the floating panel's measured width, mirrored onto the
|
|
11
|
+
* iframe root so the page card's right padding clears it. Unset, the card
|
|
12
|
+
* lays out against the 436px base-width fallback and drifts out of alignment
|
|
13
|
+
* whenever the real panel is any other width.
|
|
14
|
+
* - `--we-content`: the host's interior scaling unit, mirrored in so hidden-tab
|
|
15
|
+
* chrome scales with the rest of the UI. Unset, it pins to the 1px fallback
|
|
16
|
+
* and the tabs stay laptop-sized while every surrounding surface scales.
|
|
17
|
+
*
|
|
18
|
+
* Anything added here must actually be read by the kernel; the token test
|
|
19
|
+
* fails on a stale entry.
|
|
20
|
+
*/
|
|
21
|
+
declare const HOST_INJECTED_PROPERTIES: readonly ["--we-panel-w", "--we-content"];
|
|
22
|
+
declare function buildChromeCss(markers: MarkerAttrs): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { HOST_INJECTED_PROPERTIES, buildChromeCss };
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { TAB_TUCK } from "./hidden-tabs.js";
|
|
2
|
+
|
|
3
|
+
//#region src/canvas-core/chrome-css.ts
|
|
4
|
+
/**
|
|
5
|
+
* Custom properties the chrome CSS reads but deliberately does not declare —
|
|
6
|
+
* the host must set them on the iframe document's root element. This is the
|
|
7
|
+
* host's obligation, not a suggestion: an adapter that skips one leaves the
|
|
8
|
+
* declared inline fallback in force, which renders plausibly and silently
|
|
9
|
+
* wrong rather than failing.
|
|
10
|
+
*
|
|
11
|
+
* - `--we-panel-w`: the floating panel's measured width, mirrored onto the
|
|
12
|
+
* iframe root so the page card's right padding clears it. Unset, the card
|
|
13
|
+
* lays out against the 436px base-width fallback and drifts out of alignment
|
|
14
|
+
* whenever the real panel is any other width.
|
|
15
|
+
* - `--we-content`: the host's interior scaling unit, mirrored in so hidden-tab
|
|
16
|
+
* chrome scales with the rest of the UI. Unset, it pins to the 1px fallback
|
|
17
|
+
* and the tabs stay laptop-sized while every surrounding surface scales.
|
|
18
|
+
*
|
|
19
|
+
* Anything added here must actually be read by the kernel; the token test
|
|
20
|
+
* fails on a stale entry.
|
|
21
|
+
*/
|
|
22
|
+
const HOST_INJECTED_PROPERTIES = ["--we-panel-w", "--we-content"];
|
|
23
|
+
function buildChromeCss(markers) {
|
|
24
|
+
return `
|
|
25
|
+
html, body { background: transparent; }
|
|
26
|
+
body {
|
|
27
|
+
--we-page-radius: 14px;
|
|
28
|
+
margin: 0;
|
|
29
|
+
/* Padding aligns the page card with the floating panel and centers
|
|
30
|
+
it in the visible canvas area. Top/bottom match the panel's
|
|
31
|
+
top-5/bottom-5 inset (20px). Left is 20px. The right constant
|
|
32
|
+
(40px) covers the panel's right-5 inset (20px) plus an equal
|
|
33
|
+
20px gap, giving symmetric breathing room on both sides of the
|
|
34
|
+
card between the canvas's left edge and the panel's left edge.
|
|
35
|
+
Fallback 436px matches the panel's base CSS width. */
|
|
36
|
+
padding: 20px calc(var(--we-panel-w, 436px) + 40px) 20px 20px;
|
|
37
|
+
/* Body is the container for the auto-fit calc on #we-zoom. With
|
|
38
|
+
container-type: inline-size, descendants resolve 100cqw to the
|
|
39
|
+
body's content-box width — exactly the width we want the card
|
|
40
|
+
to scale into. */
|
|
41
|
+
container-type: inline-size;
|
|
42
|
+
position: relative;
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
align-items: flex-start;
|
|
46
|
+
min-height: 100%;
|
|
47
|
+
overflow: auto;
|
|
48
|
+
color: #14180F;
|
|
49
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
50
|
+
/* Dotted canvas surface. background-attachment: local makes the grid
|
|
51
|
+
scroll together with the page card so the canvas + website read as
|
|
52
|
+
one scene being panned, not a card sliding over a fixed grid. */
|
|
53
|
+
background-image: radial-gradient(rgba(20, 18, 13, 0.13) 1px, transparent 1.2px);
|
|
54
|
+
background-size: 20px 20px;
|
|
55
|
+
background-position: 10px 10px;
|
|
56
|
+
background-attachment: local;
|
|
57
|
+
}
|
|
58
|
+
/* Hide the native scrollbar on whichever element ends up being the
|
|
59
|
+
scroll container. Some browsers route the scroll to the root
|
|
60
|
+
element (html) rather than body, so cover both. Wheel / trackpad /
|
|
61
|
+
keyboard and programmatic scrollTo still work. */
|
|
62
|
+
html, body { scrollbar-width: none; -ms-overflow-style: none; }
|
|
63
|
+
html::-webkit-scrollbar,
|
|
64
|
+
body::-webkit-scrollbar { width: 0; height: 0; display: none; }
|
|
65
|
+
#we-zoom {
|
|
66
|
+
/* Auto-fit + manual zoom, declarative.
|
|
67
|
+
Two-slope formula: below ~1472px canvas (≤1536px viewport) slope 1
|
|
68
|
+
(0.8×) keeps the card at 80% of canvas width — the original
|
|
69
|
+
breathing-room factor. Above that breakpoint slope 2 (0.214×+0.600)
|
|
70
|
+
takes over, growing sub-linearly so the percentage of canvas the
|
|
71
|
+
card occupies shrinks on wider monitors while absolute pixel width
|
|
72
|
+
keeps growing. At a 1920px monitor this yields ~12.5% smaller than
|
|
73
|
+
the old native-1440px cap, restoring the birds-eye view. The outer
|
|
74
|
+
min(1,...) still prevents upscaling beyond native width.
|
|
75
|
+
|
|
76
|
+
--user-zoom is the manual multiplier the toolbar writes (1.0 = at
|
|
77
|
+
fit / "100%"). The browser recomputes on every resize — no host JS
|
|
78
|
+
needed.
|
|
79
|
+
|
|
80
|
+
Scale via the zoom property, NOT transform: scale(). A transform
|
|
81
|
+
only shrinks the PAINT — the layout box keeps the full unscaled
|
|
82
|
+
1440px footprint, so the body (scroll container) reserves space the
|
|
83
|
+
shrunken page no longer fills and the canvas scrolls far past the
|
|
84
|
+
bottom. zoom scales the layout box too, so scrollHeight tracks the
|
|
85
|
+
visible page exactly. getBoundingClientRect() reports visual coords
|
|
86
|
+
under either, so overlay-ring + scroll-into-view math in
|
|
87
|
+
iframe-canvas.tsx is unaffected; flex justify-content:center
|
|
88
|
+
re-centers the smaller box, so no transform-origin is needed. */
|
|
89
|
+
--user-zoom: 1;
|
|
90
|
+
--fit-zoom: min(0.9, min(
|
|
91
|
+
calc(0.72 * 100cqw / 1440px),
|
|
92
|
+
calc(0.1926 * 100cqw / 1440px + 0.540)
|
|
93
|
+
));
|
|
94
|
+
zoom: calc(var(--fit-zoom) * var(--user-zoom));
|
|
95
|
+
transition: zoom 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
/* Positioned + stacked above #we-htabs (z-index:1) so the page's
|
|
98
|
+
drop-shadow falls over the hidden-tab's inner edge, nestling it against
|
|
99
|
+
the frame. #we-chrome-overlay (z-index:100) stays above. */
|
|
100
|
+
position: relative;
|
|
101
|
+
z-index: 2;
|
|
102
|
+
}
|
|
103
|
+
#we-page {
|
|
104
|
+
width: 1440px;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
border-radius: var(--we-page-radius);
|
|
107
|
+
background: #f5f3ee;
|
|
108
|
+
padding: 0;
|
|
109
|
+
box-shadow:
|
|
110
|
+
0 1px 0 rgba(20,18,13,0.06),
|
|
111
|
+
0 2px 6px -2px rgba(20,18,13,0.10),
|
|
112
|
+
0 12px 28px -8px rgba(20,18,13,0.18),
|
|
113
|
+
0 32px 64px -20px rgba(20,18,13,0.28);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Section wrapper -- structure only. No selection chrome here;
|
|
117
|
+
rings are painted by #we-chrome-overlay (see below). Zero margin
|
|
118
|
+
+ padding means sections butt edge-to-edge, matching publisher. */
|
|
119
|
+
[${markers.sectionInstanceId}] {
|
|
120
|
+
position: relative;
|
|
121
|
+
margin: 0;
|
|
122
|
+
padding: 0;
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-rows: 1fr;
|
|
126
|
+
/* Reveal transition — governs the hidden→visible direction because
|
|
127
|
+
CSS uses the destination state's transition property. Spring easing
|
|
128
|
+
gives a snappy entrance. */
|
|
129
|
+
transition:
|
|
130
|
+
grid-template-rows 200ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
131
|
+
opacity 180ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Section title pill -- lives in #we-chrome-overlay above the rings
|
|
135
|
+
so it straddles the top border without being covered. Sections
|
|
136
|
+
only; slots get the outline ring but no on-canvas label. */
|
|
137
|
+
.we-chrome-label {
|
|
138
|
+
position: absolute;
|
|
139
|
+
pointer-events: none;
|
|
140
|
+
z-index: 1;
|
|
141
|
+
background: rgba(229,184,50,0.95);
|
|
142
|
+
color: #282c32;
|
|
143
|
+
padding: 3px 8px;
|
|
144
|
+
border-radius: 4px;
|
|
145
|
+
font-size: 10.5px;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
line-height: 1;
|
|
148
|
+
letter-spacing: 0.02em;
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
opacity: 0;
|
|
152
|
+
transform: translateY(calc(-50% - 4px)) scale(0.96);
|
|
153
|
+
transition:
|
|
154
|
+
opacity 60ms ease-in,
|
|
155
|
+
transform 60ms ease-in;
|
|
156
|
+
}
|
|
157
|
+
.we-chrome-label[data-active="true"] {
|
|
158
|
+
opacity: 1;
|
|
159
|
+
transform: translateY(-50%) scale(1);
|
|
160
|
+
transition:
|
|
161
|
+
opacity 130ms cubic-bezier(0.16, 1, 0.3, 1) 50ms,
|
|
162
|
+
transform 130ms cubic-bezier(0.16, 1, 0.3, 1) 50ms;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Hidden state -- collapse the section out of the page so it reflows shut.
|
|
166
|
+
The 0-height wrapper stays in flow as the anchor for the section's
|
|
167
|
+
floating edge tab (built in #we-htabs by iframe-canvas.tsx). See
|
|
168
|
+
docs/superpowers/specs/2026-06-02-hidden-section-edge-tabs-design.md. */
|
|
169
|
+
[${markers.sectionInstanceId}][data-hidden="true"] {
|
|
170
|
+
grid-template-rows: 0fr;
|
|
171
|
+
opacity: 0;
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
/* Collapse transition — governs the visible→hidden direction.
|
|
174
|
+
Faster + accelerating so the hide feels decisive. */
|
|
175
|
+
transition:
|
|
176
|
+
grid-template-rows 150ms cubic-bezier(0.4, 0, 1, 1),
|
|
177
|
+
opacity 120ms cubic-bezier(0.4, 0, 1, 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Slot wrapper -- structure only. No outline here; the overlay
|
|
181
|
+
ring handles slot hover + selection too. */
|
|
182
|
+
[${markers.slotId}] {
|
|
183
|
+
position: relative;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Slot-group container -- the gaps between grouped members (dividers,
|
|
188
|
+
padding) carry no slot id, so give the group container itself
|
|
189
|
+
the pointer cursor: the whole group reads as one clickable unit, and
|
|
190
|
+
the overlay ring outlines this container (see iframe-canvas.tsx). */
|
|
191
|
+
[${markers.slotGroup}],
|
|
192
|
+
[${markers.slotItem}] {
|
|
193
|
+
position: relative;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Per-section body holds the API HTML.
|
|
198
|
+
1. position:relative gives the body a positioning context for any
|
|
199
|
+
body-scoped chrome or future body-only effects.
|
|
200
|
+
2. isolation:isolate SEALS the section's z-index hierarchy
|
|
201
|
+
inside this stacking context. Without it, any positioned
|
|
202
|
+
descendant with z-index > 0 (the header's sticky+z-50, a
|
|
203
|
+
dropdown's z-40, a sticky CTA) creates a stacking context
|
|
204
|
+
that escapes upward. Sections never need to know the
|
|
205
|
+
editor chrome exists -- internal z-index is unconstrained. */
|
|
206
|
+
/* overflow:hidden clips section content as the grid row collapses;
|
|
207
|
+
min-height:0 lets the grid child shrink below its content size.
|
|
208
|
+
Constraint: overflow:hidden is permanent (not just during animation), so
|
|
209
|
+
section content must not overflow its section-body boundary — sticky
|
|
210
|
+
elements, absolutely-positioned dropdowns, or shadows that bleed outside
|
|
211
|
+
the section edge will be silently clipped. Template sections are
|
|
212
|
+
self-contained blocks; this is safe in practice. */
|
|
213
|
+
.we-section-body { position: relative; isolation: isolate; overflow: hidden; min-height: 0; }
|
|
214
|
+
|
|
215
|
+
/* Chrome overlay -- sibling of #we-zoom inside the iframe body.
|
|
216
|
+
Sized to body's padding box; rings inside are absolutely
|
|
217
|
+
positioned by JS using getBoundingClientRect + window.scrollY/X.
|
|
218
|
+
Lives OUTSIDE #we-page so rings ride above the page's rounded
|
|
219
|
+
clip and any section's overflow:hidden. pointer-events:none so
|
|
220
|
+
clicks pass through to the section/slot underneath -- the
|
|
221
|
+
click resolver remains on doc.body. */
|
|
222
|
+
#we-chrome-overlay {
|
|
223
|
+
position: absolute;
|
|
224
|
+
inset: 0;
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
z-index: 100;
|
|
227
|
+
}
|
|
228
|
+
.we-chrome-ring {
|
|
229
|
+
position: absolute;
|
|
230
|
+
pointer-events: none;
|
|
231
|
+
border: 2px solid transparent;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
opacity: 0;
|
|
234
|
+
box-sizing: border-box;
|
|
235
|
+
}
|
|
236
|
+
/* Hover ring -- instant on cursor arrival, fades out on leave.
|
|
237
|
+
Position updates instantly (no top/left transition) so dragging
|
|
238
|
+
across elements never flickers. Ink stroke + white drop-shadow
|
|
239
|
+
halo (same trick as crop handles in globals.css) keeps the ring
|
|
240
|
+
readable on both light sections and dark ones like footer. */
|
|
241
|
+
.we-chrome-ring--hover {
|
|
242
|
+
border-color: rgba(15,42,34,0.85);
|
|
243
|
+
filter: drop-shadow(0 0 1px rgba(255,255,255,0.95));
|
|
244
|
+
transition: opacity 70ms ease-in;
|
|
245
|
+
}
|
|
246
|
+
.we-chrome-ring--hover[data-active="true"] {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
transition: opacity 100ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
249
|
+
}
|
|
250
|
+
/* Selection ring -- appears instantly (continuous with hover),
|
|
251
|
+
then morphs ink → gold. No position transition so resize
|
|
252
|
+
tracking stays tight. Same ink + white halo as hover. */
|
|
253
|
+
.we-chrome-ring--selection {
|
|
254
|
+
border-color: rgba(15,42,34,0.85);
|
|
255
|
+
filter: drop-shadow(0 0 1px rgba(255,255,255,0.95));
|
|
256
|
+
transform: scale(1);
|
|
257
|
+
box-shadow: none;
|
|
258
|
+
transition:
|
|
259
|
+
opacity 50ms ease-in,
|
|
260
|
+
border-color 160ms cubic-bezier(0.2, 0, 0, 1),
|
|
261
|
+
box-shadow 160ms cubic-bezier(0.2, 0, 0, 1);
|
|
262
|
+
}
|
|
263
|
+
.we-chrome-ring--selection[data-active="true"] {
|
|
264
|
+
opacity: 1;
|
|
265
|
+
/* Instant show on select; only border + glow animate */
|
|
266
|
+
transition:
|
|
267
|
+
opacity 0ms,
|
|
268
|
+
border-color 180ms cubic-bezier(0.2, 0, 0, 1),
|
|
269
|
+
box-shadow 180ms cubic-bezier(0.2, 0, 0, 1);
|
|
270
|
+
}
|
|
271
|
+
.we-chrome-ring--selection[data-kind="section"],
|
|
272
|
+
.we-chrome-ring--selection[data-kind="slot"] {
|
|
273
|
+
border-color: #E5B832;
|
|
274
|
+
filter: drop-shadow(0 0 1px rgba(255,255,255,0.95));
|
|
275
|
+
box-shadow: 0 0 0 1px rgba(229,184,50,0.25), 0 8px 24px -10px rgba(229,184,50,0.35);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@media (prefers-reduced-motion: reduce) {
|
|
279
|
+
.we-chrome-ring,
|
|
280
|
+
.we-chrome-label,
|
|
281
|
+
[${markers.sectionInstanceId}] {
|
|
282
|
+
transition: none !important;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Hidden-section edge tabs -------------------------------------
|
|
287
|
+
One dim chip-tab per hidden section, pinned to the page's left edge at
|
|
288
|
+
the collapsed wrapper's reflow seam. Built + positioned by
|
|
289
|
+
iframe-canvas.tsx (positionHiddenTabs); de-collided via placeTabs. Lives
|
|
290
|
+
in its own layer (sibling of #we-zoom) so it rides with page scroll and
|
|
291
|
+
stays a constant size at any zoom — like the rings. Colors are literals
|
|
292
|
+
(the iframe doesn't inherit dashboard Tailwind tokens). */
|
|
293
|
+
#we-htabs {
|
|
294
|
+
position: absolute;
|
|
295
|
+
inset: 0;
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
z-index: 1;
|
|
298
|
+
}
|
|
299
|
+
.we-htab {
|
|
300
|
+
position: absolute;
|
|
301
|
+
transform: translateY(-50%);
|
|
302
|
+
display: flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: calc(9 * var(--we-content, 1px));
|
|
305
|
+
height: calc(34 * var(--we-content, 1px));
|
|
306
|
+
padding: 0 calc(${12 + 16} * var(--we-content, 1px)) 0 calc(13 * var(--we-content, 1px));
|
|
307
|
+
background: #ffffff;
|
|
308
|
+
border: 1px solid #ebebe6;
|
|
309
|
+
border-radius: calc(9 * var(--we-content, 1px)) 0 0 calc(9 * var(--we-content, 1px));
|
|
310
|
+
box-shadow: 0 10px 24px -12px rgba(20,18,13,0.34);
|
|
311
|
+
color: #4a4842;
|
|
312
|
+
font: inherit;
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
opacity: 1;
|
|
315
|
+
pointer-events: auto;
|
|
316
|
+
transition: transform 0.2s cubic-bezier(0.2,0.7,0.3,1),
|
|
317
|
+
border-color 0.18s, box-shadow 0.18s;
|
|
318
|
+
}
|
|
319
|
+
.we-htab-eye {
|
|
320
|
+
display: grid;
|
|
321
|
+
place-items: center;
|
|
322
|
+
flex: 0 0 auto;
|
|
323
|
+
color: #8a877e;
|
|
324
|
+
transition: color 0.18s;
|
|
325
|
+
}
|
|
326
|
+
.we-htab-eye svg { width: calc(14 * var(--we-content, 1px)); height: calc(14 * var(--we-content, 1px)); }
|
|
327
|
+
.we-htab-name {
|
|
328
|
+
font-size: calc(13 * var(--we-content, 1px));
|
|
329
|
+
font-weight: 600;
|
|
330
|
+
letter-spacing: -0.01em;
|
|
331
|
+
color: #14180F;
|
|
332
|
+
white-space: nowrap;
|
|
333
|
+
}
|
|
334
|
+
.we-htab:hover {
|
|
335
|
+
transform: translateY(-50%) scale(1.07);
|
|
336
|
+
border-color: rgba(15,42,34,0.5);
|
|
337
|
+
box-shadow: 0 13px 30px -12px rgba(15,42,34,0.45);
|
|
338
|
+
}
|
|
339
|
+
.we-htab:hover .we-htab-eye { color: #0F2A22; }
|
|
340
|
+
.we-htab:focus-visible {
|
|
341
|
+
outline: none;
|
|
342
|
+
transform: translateY(-50%) scale(1.07);
|
|
343
|
+
border-color: #0F2A22;
|
|
344
|
+
box-shadow: 0 0 0 3px #F3F6EF, 0 13px 30px -12px rgba(15,42,34,0.45);
|
|
345
|
+
}
|
|
346
|
+
/* Hover/focus swaps the eye-off glyph to the eye glyph in place — no width
|
|
347
|
+
change, no sliding label. Only one glyph is in flow at a time, so the eye
|
|
348
|
+
slot's grid keeps the shown glyph centered. */
|
|
349
|
+
.we-htab-eye-on { display: none; }
|
|
350
|
+
.we-htab:hover .we-htab-eye-off,
|
|
351
|
+
.we-htab:focus-visible .we-htab-eye-off { display: none; }
|
|
352
|
+
.we-htab:hover .we-htab-eye-on,
|
|
353
|
+
.we-htab:focus-visible .we-htab-eye-on { display: block; }
|
|
354
|
+
|
|
355
|
+
/* Render-error placeholder ------------------------------------- */
|
|
356
|
+
.we-render-error {
|
|
357
|
+
margin: 8px 0;
|
|
358
|
+
padding: 24px 16px;
|
|
359
|
+
border-radius: 6px;
|
|
360
|
+
border: 1px dashed #ebebe6;
|
|
361
|
+
background: #ffffff;
|
|
362
|
+
color: #8a877e;
|
|
363
|
+
font-size: 12.5px;
|
|
364
|
+
}
|
|
365
|
+
.we-render-error__title {
|
|
366
|
+
margin: 0;
|
|
367
|
+
color: #4a4842;
|
|
368
|
+
font-weight: 500;
|
|
369
|
+
}
|
|
370
|
+
.we-render-error__detail {
|
|
371
|
+
margin: 4px 0 0 0;
|
|
372
|
+
color: #b9b6ab;
|
|
373
|
+
font-size: 11.5px;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Island interaction shield: a transparent click layer over an
|
|
377
|
+
island's live surface (editor.liveSurface) (e.g. the neighborhood map canvas). Present until
|
|
378
|
+
the surface's owner (slot/section) is the live selection; a click on it
|
|
379
|
+
falls through to the editor's resolver, which selects the owner. Once the
|
|
380
|
+
owner carries data-we-live the shield is removed, so the live surface (map
|
|
381
|
+
pan/zoom/markers) is interactive. z-index 1 sits on the map canvas's plane
|
|
382
|
+
(later in DOM than the canvas -> above it) but below the floating
|
|
383
|
+
.tr-nbhd__card (z-index 2), so the card's slots stay clickable. */
|
|
384
|
+
.we-enh-shield {
|
|
385
|
+
position: absolute;
|
|
386
|
+
inset: 0;
|
|
387
|
+
z-index: 1;
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
background: transparent;
|
|
390
|
+
}
|
|
391
|
+
[data-we-live] .we-enh-shield { display: none; }
|
|
392
|
+
|
|
393
|
+
/* Image slider: until the slider's \`images\` slot is the live selection, its
|
|
394
|
+
z-index:2 nav arrows would paint above the z-index:1 shield and stay
|
|
395
|
+
clickable, escaping the select-first gate. Disable them so the shield
|
|
396
|
+
catches the first click; restored once .tr-slider carries data-we-live.
|
|
397
|
+
Editor-only — deliberately not in published tokens.css. */
|
|
398
|
+
.tr-slider:not([data-we-live]) .tr-slider__nav { pointer-events: none; }
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
export { HOST_INJECTED_PROPERTIES, buildChromeCss };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/canvas-core/chrome.d.ts
|
|
2
|
+
/** How far outside the target the ring sits, in CSS px. With a 2px border-box
|
|
3
|
+
* border, offset = 1 centers the stroke on the element's edge (1px outside,
|
|
4
|
+
* 1px overlapping inside) so the ring reads as flush. */
|
|
5
|
+
declare const RING_OFFSET = 1;
|
|
6
|
+
/** Inset of the section title pill from the target's left edge, in CSS px. */
|
|
7
|
+
declare const LABEL_LEFT_INDENT = 12;
|
|
8
|
+
interface RingRect {
|
|
9
|
+
top: number;
|
|
10
|
+
left: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Ring geometry in document space: shift by scroll, inflate by `offset`.
|
|
16
|
+
*
|
|
17
|
+
* `containerWidth` clamps the horizontal edges. A section flush with both page
|
|
18
|
+
* edges would otherwise inflate `offset` px past each one and grow the
|
|
19
|
+
* document's horizontal scroll extent. Omitting it means unclamped: the app's
|
|
20
|
+
* overlay is wider than its page card and never overflows, so clamping it would
|
|
21
|
+
* silently shift every full-bleed ring. A consumer whose overlay is page-width
|
|
22
|
+
* opts in by passing one.
|
|
23
|
+
*
|
|
24
|
+
* Absence — not a sentinel value — is what selects the unclamped branch. A
|
|
25
|
+
* numeric stand-in for "unbounded" would make NaN (plausible out of a detached
|
|
26
|
+
* document's clientWidth arithmetic) take the clamped branch and yield NaN
|
|
27
|
+
* geometry, and would let Number.MAX_SAFE_INTEGER quietly enable the left floor.
|
|
28
|
+
*/
|
|
29
|
+
declare function ringBox(rect: RingRect, scroll: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
}, offset: number, containerWidth?: number): RingRect;
|
|
33
|
+
/** Whether an element has a box a ring can actually wrap. */
|
|
34
|
+
declare function hasMeasurableRingBox(el: HTMLElement): boolean;
|
|
35
|
+
declare function measureRingRect(el: HTMLElement): DOMRect;
|
|
36
|
+
/**
|
|
37
|
+
* Move a ring in #we-chrome-overlay to sit `offset` px outside `target`.
|
|
38
|
+
*
|
|
39
|
+
* `kind` flips a data attribute so CSS can pick a color (gold for sections and
|
|
40
|
+
* slots). The hover ring leaves it unset and uses its own selector.
|
|
41
|
+
*/
|
|
42
|
+
declare function positionRing(ring: HTMLElement | null, target: HTMLElement | null, offset: number, kind?: "section" | "slot", containerWidth?: number): void;
|
|
43
|
+
declare function applySelectionKind(ring: HTMLElement, kind: "section" | "slot"): void;
|
|
44
|
+
declare function clearRing(ring: HTMLElement | null): void;
|
|
45
|
+
declare function positionLabel(label: HTMLElement | null, target: HTMLElement | null, text: string): void;
|
|
46
|
+
declare function clearLabel(label: HTMLElement | null): void;
|
|
47
|
+
declare function cssEscape(value: string): string;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { LABEL_LEFT_INDENT, RING_OFFSET, RingRect, applySelectionKind, clearLabel, clearRing, cssEscape, hasMeasurableRingBox, measureRingRect, positionLabel, positionRing, ringBox };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
//#region src/canvas-core/chrome.ts
|
|
2
|
+
/** How far outside the target the ring sits, in CSS px. With a 2px border-box
|
|
3
|
+
* border, offset = 1 centers the stroke on the element's edge (1px outside,
|
|
4
|
+
* 1px overlapping inside) so the ring reads as flush. */
|
|
5
|
+
const RING_OFFSET = 1;
|
|
6
|
+
/** Inset of the section title pill from the target's left edge, in CSS px. */
|
|
7
|
+
const LABEL_LEFT_INDENT = 12;
|
|
8
|
+
/**
|
|
9
|
+
* Ring geometry in document space: shift by scroll, inflate by `offset`.
|
|
10
|
+
*
|
|
11
|
+
* `containerWidth` clamps the horizontal edges. A section flush with both page
|
|
12
|
+
* edges would otherwise inflate `offset` px past each one and grow the
|
|
13
|
+
* document's horizontal scroll extent. Omitting it means unclamped: the app's
|
|
14
|
+
* overlay is wider than its page card and never overflows, so clamping it would
|
|
15
|
+
* silently shift every full-bleed ring. A consumer whose overlay is page-width
|
|
16
|
+
* opts in by passing one.
|
|
17
|
+
*
|
|
18
|
+
* Absence — not a sentinel value — is what selects the unclamped branch. A
|
|
19
|
+
* numeric stand-in for "unbounded" would make NaN (plausible out of a detached
|
|
20
|
+
* document's clientWidth arithmetic) take the clamped branch and yield NaN
|
|
21
|
+
* geometry, and would let Number.MAX_SAFE_INTEGER quietly enable the left floor.
|
|
22
|
+
*/
|
|
23
|
+
function ringBox(rect, scroll, offset, containerWidth) {
|
|
24
|
+
const rawLeft = rect.left + scroll.x - offset;
|
|
25
|
+
const rawRight = rawLeft + rect.width + 2 * offset;
|
|
26
|
+
const left = containerWidth === void 0 ? rawLeft : Math.max(0, rawLeft);
|
|
27
|
+
const right = containerWidth === void 0 ? rawRight : Math.min(containerWidth, rawRight);
|
|
28
|
+
return {
|
|
29
|
+
top: rect.top + scroll.y - offset,
|
|
30
|
+
left,
|
|
31
|
+
width: Math.max(0, right - left),
|
|
32
|
+
height: rect.height + 2 * offset
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Whether an element has a box a ring can actually wrap. */
|
|
36
|
+
function hasMeasurableRingBox(el) {
|
|
37
|
+
const rect = measureRingRect(el);
|
|
38
|
+
return rect.width > 0 && rect.height > 0;
|
|
39
|
+
}
|
|
40
|
+
function measureRingRect(el) {
|
|
41
|
+
const rect = el.getBoundingClientRect();
|
|
42
|
+
if (rect.width > 0 && rect.height > 0) return rect;
|
|
43
|
+
let top = Infinity;
|
|
44
|
+
let left = Infinity;
|
|
45
|
+
let right = -Infinity;
|
|
46
|
+
let bottom = -Infinity;
|
|
47
|
+
let found = false;
|
|
48
|
+
for (const child of Array.from(el.children)) {
|
|
49
|
+
const childRect = measureRingRect(child);
|
|
50
|
+
if (childRect.width <= 0 || childRect.height <= 0) continue;
|
|
51
|
+
found = true;
|
|
52
|
+
top = Math.min(top, childRect.top);
|
|
53
|
+
left = Math.min(left, childRect.left);
|
|
54
|
+
right = Math.max(right, childRect.right);
|
|
55
|
+
bottom = Math.max(bottom, childRect.bottom);
|
|
56
|
+
}
|
|
57
|
+
if (!found) return rect;
|
|
58
|
+
return new DOMRect(left, top, right - left, bottom - top);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Move a ring in #we-chrome-overlay to sit `offset` px outside `target`.
|
|
62
|
+
*
|
|
63
|
+
* `kind` flips a data attribute so CSS can pick a color (gold for sections and
|
|
64
|
+
* slots). The hover ring leaves it unset and uses its own selector.
|
|
65
|
+
*/
|
|
66
|
+
function positionRing(ring, target, offset, kind, containerWidth) {
|
|
67
|
+
if (!ring) return;
|
|
68
|
+
if (!target) {
|
|
69
|
+
ring.removeAttribute("data-active");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const win = target.ownerDocument.defaultView;
|
|
73
|
+
if (!win) return;
|
|
74
|
+
const box = ringBox(measureRingRect(target), {
|
|
75
|
+
x: win.scrollX,
|
|
76
|
+
y: win.scrollY
|
|
77
|
+
}, offset, containerWidth);
|
|
78
|
+
ring.style.top = `${box.top}px`;
|
|
79
|
+
ring.style.left = `${box.left}px`;
|
|
80
|
+
ring.style.width = `${box.width}px`;
|
|
81
|
+
ring.style.height = `${box.height}px`;
|
|
82
|
+
if (kind) ring.setAttribute("data-kind", kind);
|
|
83
|
+
ring.setAttribute("data-active", "true");
|
|
84
|
+
}
|
|
85
|
+
function applySelectionKind(ring, kind) {
|
|
86
|
+
ring.removeAttribute("data-kind");
|
|
87
|
+
requestAnimationFrame(() => {
|
|
88
|
+
requestAnimationFrame(() => {
|
|
89
|
+
ring.setAttribute("data-kind", kind);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function clearRing(ring) {
|
|
94
|
+
if (!ring) return;
|
|
95
|
+
ring.removeAttribute("data-active");
|
|
96
|
+
ring.removeAttribute("data-kind");
|
|
97
|
+
}
|
|
98
|
+
function positionLabel(label, target, text) {
|
|
99
|
+
if (!label) return;
|
|
100
|
+
if (!target || !text) {
|
|
101
|
+
clearLabel(label);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const win = target.ownerDocument.defaultView;
|
|
105
|
+
if (!win) return;
|
|
106
|
+
const rect = measureRingRect(target);
|
|
107
|
+
const wasActive = label.getAttribute("data-active") === "true";
|
|
108
|
+
const textChanged = label.textContent !== text;
|
|
109
|
+
label.textContent = text;
|
|
110
|
+
label.style.top = `${rect.top + win.scrollY}px`;
|
|
111
|
+
label.style.left = `${rect.left + win.scrollX + 12}px`;
|
|
112
|
+
if (wasActive && textChanged) {
|
|
113
|
+
label.removeAttribute("data-active");
|
|
114
|
+
label.offsetWidth;
|
|
115
|
+
}
|
|
116
|
+
label.setAttribute("data-active", "true");
|
|
117
|
+
}
|
|
118
|
+
function clearLabel(label) {
|
|
119
|
+
if (!label) return;
|
|
120
|
+
label.removeAttribute("data-active");
|
|
121
|
+
label.textContent = "";
|
|
122
|
+
}
|
|
123
|
+
function cssEscape(value) {
|
|
124
|
+
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(value);
|
|
125
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, (c) => `\\${c}`);
|
|
126
|
+
}
|
|
127
|
+
function resolveVisibleSlotEl(scope, slotId, markers) {
|
|
128
|
+
const nodes = scope.querySelectorAll(`[${markers.slotId}="${cssEscape(slotId)}"]`);
|
|
129
|
+
for (const el of Array.from(nodes)) if (hasMeasurableRingBox(el)) return el;
|
|
130
|
+
return nodes[0] ?? null;
|
|
131
|
+
}
|
|
132
|
+
function resolveItemEl(slotEl, selection, markers) {
|
|
133
|
+
if (!selection || selection.kind !== "slot" || selection.item_index == null) return null;
|
|
134
|
+
return slotEl?.querySelector(`[${markers.slotItem}="${cssEscape(String(selection.item_index))}"]`) ?? null;
|
|
135
|
+
}
|
|
136
|
+
function resolveSelectionEl(doc, selection, markers) {
|
|
137
|
+
if (!selection) return null;
|
|
138
|
+
const sec = doc.querySelector(`[${markers.sectionInstanceId}="${cssEscape(selection.instance_id)}"]`);
|
|
139
|
+
if (!sec) return null;
|
|
140
|
+
if (selection.kind === "section") return sec;
|
|
141
|
+
const node = resolveVisibleSlotEl(sec, selection.slot_id, markers);
|
|
142
|
+
if (!node) return null;
|
|
143
|
+
return resolveItemEl(node, selection, markers) ?? node.closest(`[${markers.slotGroup}]`) ?? node;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
export { LABEL_LEFT_INDENT, RING_OFFSET, applySelectionKind, clearLabel, clearRing, cssEscape, hasMeasurableRingBox, measureRingRect, positionLabel, positionRing, resolveItemEl, resolveSelectionEl, resolveVisibleSlotEl, ringBox };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CanvasFrameDeps, CanvasSectionEntry, CanvasSelection, CanvasTarget } from "./types.js";
|
|
2
|
+
//#region src/canvas-core/frame.d.ts
|
|
3
|
+
interface CanvasFrame {
|
|
4
|
+
srcdoc(): string;
|
|
5
|
+
buildSectionWrapper(entry: CanvasSectionEntry, doc: Document): HTMLElement;
|
|
6
|
+
buildErrorPlaceholder(entry: CanvasSectionEntry, doc: Document): HTMLElement;
|
|
7
|
+
resolveTarget(hit: HTMLElement | null, sections: CanvasSectionEntry[]): CanvasTarget | null;
|
|
8
|
+
localizeSectionAssets(html: string): string;
|
|
9
|
+
/** The DOM node a selection points at: item, then slot group, then slot —
|
|
10
|
+
* the same order the selection ring targets. */
|
|
11
|
+
resolveSelectionEl(doc: Document, selection: CanvasSelection | null): HTMLElement | null;
|
|
12
|
+
/** The painting copy of a slot, for sections with responsive duplicate DOM. */
|
|
13
|
+
resolveVisibleSlotEl(scope: ParentNode, slotId: string): HTMLElement | null;
|
|
14
|
+
/** The selected collection item within an already-resolved slot element. */
|
|
15
|
+
resolveItemEl(slotEl: HTMLElement | null, selection: CanvasSelection | null): HTMLElement | null;
|
|
16
|
+
}
|
|
17
|
+
/** Bind the frame to one consumer's markers, asset URLs and editability rule. */
|
|
18
|
+
declare function createCanvasFrame(deps: CanvasFrameDeps): CanvasFrame;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { CanvasFrame, createCanvasFrame };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolveItemEl, resolveSelectionEl, resolveVisibleSlotEl } from "./chrome.js";
|
|
2
|
+
import { resolveCanvasTarget } from "./resolve-target.js";
|
|
3
|
+
import { buildErrorPlaceholder, buildSectionWrapper } from "./section-dom.js";
|
|
4
|
+
import { buildSrcdoc, localizeSectionAssets } from "./srcdoc.js";
|
|
5
|
+
|
|
6
|
+
//#region src/canvas-core/frame.ts
|
|
7
|
+
/** Bind the frame to one consumer's markers, asset URLs and editability rule. */
|
|
8
|
+
function createCanvasFrame(deps) {
|
|
9
|
+
const localize = (html) => localizeSectionAssets(html, deps.assets.basePath);
|
|
10
|
+
return {
|
|
11
|
+
srcdoc: () => buildSrcdoc(deps.assets, deps.markers),
|
|
12
|
+
buildSectionWrapper: (entry, doc) => buildSectionWrapper(entry, doc, deps.markers, localize),
|
|
13
|
+
buildErrorPlaceholder,
|
|
14
|
+
resolveTarget: (hit, sections) => resolveCanvasTarget(hit, sections, deps),
|
|
15
|
+
localizeSectionAssets: localize,
|
|
16
|
+
resolveSelectionEl: (doc, selection) => resolveSelectionEl(doc, selection, deps.markers),
|
|
17
|
+
resolveVisibleSlotEl: (scope, slotId) => resolveVisibleSlotEl(scope, slotId, deps.markers),
|
|
18
|
+
resolveItemEl: (slotEl, selection) => resolveItemEl(slotEl, selection, deps.markers)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { createCanvasFrame };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/canvas-core/hidden-tabs.d.ts
|
|
2
|
+
declare const WE_TAB_STEP = 42;
|
|
3
|
+
declare const TAB_TUCK = 16;
|
|
4
|
+
interface TabAnchor {
|
|
5
|
+
idx: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
interface HiddenTabSpec {
|
|
9
|
+
instanceId: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
declare function placeTabs(anchors: TabAnchor[], step: number): Record<number, number>;
|
|
13
|
+
declare function buildHiddenTab(doc: Document, instanceId: string, label: string, onReveal: () => void): HTMLButtonElement;
|
|
14
|
+
declare function reconcileHiddenTabs(container: HTMLElement, doc: Document, specs: HiddenTabSpec[], onReveal: (instanceId: string) => void): HTMLButtonElement[];
|
|
15
|
+
//#endregion
|
|
16
|
+
export { HiddenTabSpec, TAB_TUCK, TabAnchor, WE_TAB_STEP, buildHiddenTab, placeTabs, reconcileHiddenTabs };
|