@uniweb/build 1.6.2 → 1.7.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,15 +56,15 @@
56
56
  "js-yaml": "^4.1.0",
57
57
  "sharp": "^0.35.3",
58
58
  "yaml": "^2.5.0",
59
- "@uniweb/semantic-parser": "~1.12.2",
60
59
  "@uniweb/content-reader": "~1.5.4",
61
- "@uniweb/theming": "~1.2.0",
62
- "@uniweb/schemas": "~1.5.2",
63
- "@uniweb/content-writer": "~1.3.0",
64
- "@uniweb/projections": "~1.1.5"
60
+ "@uniweb/semantic-parser": "~1.12.2",
61
+ "@uniweb/theming": "~1.2.1",
62
+ "@uniweb/projections": "~1.1.6",
63
+ "@uniweb/schemas": "~1.6.0",
64
+ "@uniweb/content-writer": "~1.3.0"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@uniweb/runtime": "~1.1.12"
67
+ "@uniweb/runtime": "~1.1.14"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -73,8 +73,8 @@
73
73
  "@tailwindcss/vite": "^4.0.0",
74
74
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
75
75
  "vite-plugin-svgr": "^4.0.0",
76
- "@uniweb/core": "~1.3.2",
77
- "@uniweb/runtime": "~1.1.12"
76
+ "@uniweb/core": "~1.4.0",
77
+ "@uniweb/runtime": "~1.1.14"
78
78
  },
79
79
  "peerDependenciesMeta": {
80
80
  "vite": {
@@ -5,6 +5,7 @@
5
5
  * reads to render, and nothing else:
6
6
  *
7
7
  * - background: 'self' when component handles its own background
8
+ * - sticky: { default, top? } — whether the section stays in view unless its author says
8
9
  * - data: { <key>: <schema ref> | { schema, single?, whole? } | null } — every `content.data`
9
10
  * key the component declares, in order, with its schema ref and the flags set on it (null for
10
11
  * an inline shape)
@@ -37,6 +38,35 @@
37
38
  import { lowerData } from '@uniweb/schemas/content'
38
39
  import { schemaDeclarationOf } from '@uniweb/schemas/foundation'
39
40
 
41
+ /**
42
+ * A section type's `sticky`, checked: `true` or `false`, or `{ default, top }`. `top` is how far
43
+ * below the top the section stays — one CSS value, `4rem` or `var(--header-height)` — and is
44
+ * written into the style of the box that sticks, so a declaration (`;`, a brace) is refused.
45
+ *
46
+ * @param {*} declared - `meta.js`'s `sticky`
47
+ * @returns {{ default: boolean, top?: string }}
48
+ * @throws {Error} for any other shape
49
+ */
50
+ export function stickyDeclaration(declared) {
51
+ if (typeof declared === 'boolean') return { default: declared }
52
+ if (!declared || typeof declared !== 'object' || Array.isArray(declared)) {
53
+ throw new Error(`[uniweb] Invalid 'sticky': write true, false or { default, top } — got ${JSON.stringify(declared)}`)
54
+ }
55
+ const unknown = Object.keys(declared).filter((key) => key !== 'default' && key !== 'top')
56
+ if (unknown.length) throw new Error(`[uniweb] Invalid 'sticky': ${unknown.join(', ')} — it takes default and top`)
57
+ if (declared.default !== undefined && typeof declared.default !== 'boolean') {
58
+ throw new Error(`[uniweb] Invalid 'sticky.default': write true or false`)
59
+ }
60
+ const out = { default: declared.default === true }
61
+ if (declared.top !== undefined) {
62
+ if (typeof declared.top !== 'string' || !declared.top.trim() || /[;{}]/.test(declared.top)) {
63
+ throw new Error(`[uniweb] Invalid 'sticky.top': one CSS length, such as '4rem' or 'var(--header-height)'`)
64
+ }
65
+ out.top = declared.top.trim()
66
+ }
67
+ return out
68
+ }
69
+
40
70
  /**
41
71
  * A `data:` entry as the runtime reads it: its schema ref, with the flags set on it — or null for an
42
72
  * inline shape, which names no schema and is filled only under its own key.
@@ -132,6 +162,12 @@ export function extractRuntimeSchema(fullMeta) {
132
162
  runtime.background = fullMeta.background
133
163
  }
134
164
 
165
+ // Whether the section stays in view by default: core reads it, and the author's
166
+ // `sticky:` wins (`Block#initComponent`). The runtime makes sticky the box that can.
167
+ if (fullMeta.sticky !== undefined) {
168
+ runtime.sticky = stickyDeclaration(fullMeta.sticky)
169
+ }
170
+
135
171
  // Data. `data:` is the single declaration surface for a section's structured
136
172
  // data: it maps each `content.data` key to its schema. A value is a named ref
137
173
  // (`'@/member'`), an inline field map, or an inline rich-form (`{ fields: [...] }`,
package/src/schema.js CHANGED
@@ -22,6 +22,7 @@ import { describeContent, lowerData } from '@uniweb/schemas/content'
22
22
  import { normalizeData, schemaDeclarationOf } from '@uniweb/schemas/foundation'
23
23
  import { rootListSection } from '@uniweb/schemas/conform'
24
24
  import { SECTION_PARAMS, SECTION_KEYS } from '@uniweb/schemas/section'
25
+ import { stickyDeclaration } from './runtime-schema.js'
25
26
  import { collectSchemaRefs, buildDataSchemaMap, ownSchemaRefs } from './resolve-data-schema.js'
26
27
  import {
27
28
  composeSupports,
@@ -652,12 +653,29 @@ function buildComponentEntry(name, relativePath, meta) {
652
653
 
653
654
  if (meta.data !== undefined) entry.data = lowerData(meta.data)
654
655
  refuseDataDeclarations(`${name} (meta.js)`, entry.data)
656
+ refuseStickyDeclaration(`${name} (meta.js)`, meta.sticky)
655
657
 
656
658
  const problems = [...content.problems, ...describeChildren(meta).problems]
657
659
  if (problems.length > 0) loweringProblems.set(entry, problems)
658
660
  return entry
659
661
  }
660
662
 
663
+ /**
664
+ * Refuse a `sticky` that is not `true`, `false` or `{ default, top }` (`stickyDeclaration`), naming
665
+ * the component — the lean runtime schema checks it too, without the name.
666
+ *
667
+ * @param {string} owner - who declares it, for the message
668
+ * @param {*} sticky - `meta.js`'s `sticky`
669
+ */
670
+ function refuseStickyDeclaration(owner, sticky) {
671
+ if (sticky === undefined) return
672
+ try {
673
+ stickyDeclaration(sticky)
674
+ } catch (err) {
675
+ throw new Error(`[uniweb] ${owner}: ${err.message.replace(/^\[uniweb\] /, '')}`)
676
+ }
677
+ }
678
+
661
679
  /**
662
680
  * Refuse a `data:` key typed by a schema that is not written as one: the retired `/*` suffix, a
663
681
  * property of the long form other than `schema`, `single` and `whole`, or a flag that is not a
@@ -2622,11 +2622,15 @@ export async function collectSiteContent(sitePath, options = {}) {
2622
2622
  // `base` reaches the theme because self-hosted font faces are authored
2623
2623
  // root-relative (`/fonts/x.woff2`) and the emitted @font-face lives in an
2624
2624
  // inline <style> — under a subdirectory deployment it must carry the base.
2625
- const { config: processedTheme, css: themeCSS, links: themeLinks, warnings } = buildTheme(rawThemeConfig, { foundationVars, base })
2626
-
2627
- // Log theme warnings
2628
- if (warnings?.length > 0) {
2629
- warnings.forEach(w => console.warn(`[theme] ${w}`))
2625
+ const { config: processedTheme, css: themeCSS, links: themeLinks, warnings, errors } = buildTheme(rawThemeConfig, { foundationVars, base })
2626
+
2627
+ // Log what theme.yml gets wrong — the check's findings (`errors`: a colour that
2628
+ // is not one, a key nothing reads) and what processing left out (`warnings`).
2629
+ // Neither stops the build: the theme renders with what could be used. ⛔ Until
2630
+ // 2026-10-19 the findings were dropped here, so a misspelled `code:` key or an
2631
+ // invalid colour reached no one.
2632
+ for (const message of [...(errors || []), ...(warnings || [])]) {
2633
+ console.warn(`[theme] ${message}`)
2630
2634
  }
2631
2635
 
2632
2636
  // Check if pages directory exists