@uniweb/theming 0.1.14 → 0.1.16
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/package.json +1 -1
- package/src/css-generator.js +19 -0
- package/src/index.js +1 -0
- package/src/section-overrides.js +73 -43
package/package.json
CHANGED
package/src/css-generator.js
CHANGED
|
@@ -321,6 +321,25 @@ function isGoogleFontsUrl(url) {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Marker delimiting the font `<link>` block in a rendered `<head>`.
|
|
326
|
+
*
|
|
327
|
+
* More than one stage can write that block — a build's `transformIndexHtml`, a
|
|
328
|
+
* build's per-page post-processing, and the runtime's shared prerender seam —
|
|
329
|
+
* so each has to tell "already injected" from "never injected" rather than
|
|
330
|
+
* guess, and a page passing through several must still end up with exactly one
|
|
331
|
+
* copy. The theme `<style>` hangs that check on its `id="uniweb-theme"`;
|
|
332
|
+
* `<link>` tags have no natural id, hence a comment.
|
|
333
|
+
*
|
|
334
|
+
* It lives HERE, beside the code producing the block it marks, because its
|
|
335
|
+
* consumers sit in packages that cannot import one another: `@uniweb/build`
|
|
336
|
+
* (both its Vite plugin and its prerenderer) and `@uniweb/runtime`. Both
|
|
337
|
+
* already depend on `@uniweb/theming`, so this is the one home neither has to
|
|
338
|
+
* reach across a dependency boundary to read — and duplicating the literal is
|
|
339
|
+
* exactly how the two halves of a dedupe check drift apart.
|
|
340
|
+
*/
|
|
341
|
+
export const FONT_LINKS_MARKER = '<!--uniweb-fonts-->'
|
|
342
|
+
|
|
324
343
|
/**
|
|
325
344
|
* Generate font CSS and external link tags.
|
|
326
345
|
*
|
package/src/index.js
CHANGED
package/src/section-overrides.js
CHANGED
|
@@ -145,8 +145,19 @@ function buildRule(selector, vars) {
|
|
|
145
145
|
* Works with both Block instances and plain data objects — only reads:
|
|
146
146
|
* - block.stableId || block.id — for CSS selector
|
|
147
147
|
* - block.themeName — '' for Auto, 'light'/'medium'/'dark' for Pinned
|
|
148
|
-
* - block.
|
|
148
|
+
* - block.themeOverrides — the section's `theme:`, as @uniweb/core normalizes it:
|
|
149
|
+
* `{ colors, contexts, vars, tokens }` — a palette, tokens per color context, the
|
|
150
|
+
* foundation's variables, and tokens for the section in any context
|
|
149
151
|
* - block.componentVars — merged meta.js defaults + frontmatter overrides
|
|
152
|
+
* - block.standardOptions — ⚠️ the older editor envelope, `{ colors, foundationStyles }`,
|
|
153
|
+
* read by its own rules until an editor writes the section's `theme` instead
|
|
154
|
+
* - block.childBlocks — child sections get the same, at any depth: rendered as sections,
|
|
155
|
+
* they carry the `#section-{id}` these rules select [2026-09-28]
|
|
156
|
+
*
|
|
157
|
+
* ⭐ Which context's values apply, for the section's `theme`: a pinned section uses its own
|
|
158
|
+
* context's; a section that follows the site (Auto) uses `light` under a light scheme and
|
|
159
|
+
* `dark` under `.scheme-dark` — two rules, so no knowledge of whether the site can go dark
|
|
160
|
+
* is needed. A context's own token beats one written beside `mode`, being more specific.
|
|
150
161
|
*
|
|
151
162
|
* @param {Array<Object>} blocks - Block data objects
|
|
152
163
|
* @param {Object} appearance - Theme appearance config
|
|
@@ -158,59 +169,78 @@ export function buildSectionOverrides(blocks, appearance = {}) {
|
|
|
158
169
|
if (!blocks || blocks.length === 0) return ''
|
|
159
170
|
|
|
160
171
|
let css = ''
|
|
161
|
-
|
|
162
172
|
for (const block of blocks) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const selector = `#section-${sectionId}`
|
|
173
|
+
css += buildBlockRules(block, appearance || {})
|
|
174
|
+
if (Array.isArray(block.childBlocks) && block.childBlocks.length > 0) {
|
|
175
|
+
css += buildSectionOverrides(block.childBlocks, appearance)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return css
|
|
179
|
+
}
|
|
171
180
|
|
|
172
|
-
|
|
181
|
+
/**
|
|
182
|
+
* The rules for one section: declarations for any scheme, and for an Auto section under a
|
|
183
|
+
* light and under a dark scheme.
|
|
184
|
+
*/
|
|
185
|
+
function buildBlockRules(block, appearance) {
|
|
186
|
+
const selector = `#section-${block.stableId || block.id}`
|
|
187
|
+
const isAuto = !block.themeName
|
|
188
|
+
const always = []
|
|
189
|
+
const light = []
|
|
190
|
+
const dark = []
|
|
191
|
+
|
|
192
|
+
// ⚠️ The older editor envelope — its rules unchanged, the `light` bucket without a toggle
|
|
193
|
+
// included, so an editor sending it renders as before.
|
|
194
|
+
const legacy = block.standardOptions
|
|
195
|
+
const compVars = buildFoundationVars(block.componentVars)
|
|
196
|
+
if (hasOverrides(legacy)) {
|
|
197
|
+
const { colors, foundationStyles } = legacy
|
|
173
198
|
const hasToggle = appearance.allowToggle
|
|
174
|
-
|
|
175
|
-
// Base palette is context-independent (always under 'light' key)
|
|
176
199
|
const paletteVars = buildPaletteVars(colors?.colors?.light)
|
|
177
|
-
|
|
178
|
-
// Split foundation styles: flat (context-independent) + context-keyed (color/gradient)
|
|
179
200
|
const { flat: flatFoundation, contexts: ctxFoundation } = splitFoundationStyles(foundationStyles)
|
|
180
201
|
const foundationVars = buildFoundationVars(flatFoundation)
|
|
181
|
-
|
|
182
|
-
// Component-level CSS vars are context-independent
|
|
183
|
-
const compVars = buildFoundationVars(block.componentVars)
|
|
184
|
-
|
|
185
202
|
if (isAuto && hasToggle) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Context-aware foundation vars (color/gradient types)
|
|
191
|
-
const lightFoundationCtx = buildFoundationVars(ctxFoundation.light)
|
|
192
|
-
const darkFoundationCtx = buildFoundationVars(ctxFoundation.dark)
|
|
193
|
-
|
|
194
|
-
// Context-independent rule: palette + flat foundation + component vars (apply in both schemes)
|
|
195
|
-
const sharedVars = [...paletteVars, ...foundationVars, ...compVars]
|
|
196
|
-
css += buildRule(selector, sharedVars)
|
|
197
|
-
|
|
198
|
-
// Light-only overrides (elements + context-aware foundation vars)
|
|
199
|
-
css += buildRule(`:root:not(.scheme-dark) ${selector}`, [...lightElementVars, ...lightFoundationCtx])
|
|
200
|
-
|
|
201
|
-
// Dark-only overrides (elements + context-aware foundation vars)
|
|
202
|
-
css += buildRule(`.scheme-dark ${selector}`, [...darkElementVars, ...darkFoundationCtx])
|
|
203
|
+
always.push(...paletteVars, ...foundationVars, ...compVars)
|
|
204
|
+
light.push(...buildElementVars(colors?.elements?.light), ...buildFoundationVars(ctxFoundation.light))
|
|
205
|
+
dark.push(...buildElementVars(colors?.elements?.dark), ...buildFoundationVars(ctxFoundation.dark))
|
|
203
206
|
} else {
|
|
204
|
-
// Single context: when toggle is off, always use 'light' bucket
|
|
205
|
-
// (overrides are context-independent); when toggle is on, use pinned context
|
|
206
207
|
const ctx = hasToggle ? (block.themeName || 'light') : 'light'
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
always.push(
|
|
209
|
+
...paletteVars,
|
|
210
|
+
...buildElementVars(colors?.elements?.[ctx]),
|
|
211
|
+
...foundationVars,
|
|
212
|
+
...compVars,
|
|
213
|
+
...buildFoundationVars(ctxFoundation[ctx])
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
always.push(...compVars)
|
|
218
|
+
}
|
|
209
219
|
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
// The section's `theme:` — theme.yml's keys, scoped to the section.
|
|
221
|
+
const theme = block.themeOverrides
|
|
222
|
+
const themeDark = []
|
|
223
|
+
if (theme) {
|
|
224
|
+
const { flat, contexts: ctxVars } = splitFoundationStyles(theme.vars)
|
|
225
|
+
always.push(...buildPaletteVars(theme.colors), ...buildFoundationVars(flat), ...buildElementVars(theme.tokens))
|
|
226
|
+
if (isAuto) {
|
|
227
|
+
light.push(...buildElementVars(theme.contexts?.light), ...buildFoundationVars(ctxVars.light))
|
|
228
|
+
themeDark.push(...buildElementVars(theme.contexts?.dark), ...buildFoundationVars(ctxVars.dark))
|
|
229
|
+
dark.push(...themeDark)
|
|
230
|
+
} else {
|
|
231
|
+
always.push(...buildElementVars(theme.contexts?.[block.themeName]), ...buildFoundationVars(ctxVars[block.themeName]))
|
|
212
232
|
}
|
|
213
233
|
}
|
|
214
234
|
|
|
215
|
-
return
|
|
235
|
+
return (
|
|
236
|
+
buildRule(selector, always) +
|
|
237
|
+
buildRule(`:root:not(.scheme-dark) ${selector}`, light) +
|
|
238
|
+
buildRule(`.scheme-dark ${selector}`, dark) +
|
|
239
|
+
// A site that follows the visitor's system goes dark through a media query before any
|
|
240
|
+
// class is set — as the theme's own dark tokens do (`generateDarkSchemeCSS`) — so the
|
|
241
|
+
// section's dark values follow it there too. Same specificity as the light rule, later.
|
|
242
|
+
(appearance.default === 'system' && themeDark.length > 0
|
|
243
|
+
? `@media (prefers-color-scheme: dark) {\n${buildRule(`:root:not(.scheme-light) ${selector}`, themeDark)}}\n`
|
|
244
|
+
: '')
|
|
245
|
+
)
|
|
216
246
|
}
|