@wix/origami-site-runtime 1.0.13 → 1.0.14

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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/package.json +8 -8
  3. package/src/index.js +2 -2
  4. package/src/routing/siteLayout.js +1 -9
  5. package/src/siteViteConfig.js +4 -4
  6. package/src/cssPlugins/customMedia.js +0 -52
  7. package/src/cssPlugins/fonts.js +0 -93
  8. package/src/cssPlugins/index.js +0 -32
  9. package/src/cssPlugins/layout/README.md +0 -77
  10. package/src/cssPlugins/layout/compiler.js +0 -116
  11. package/src/cssPlugins/layout/contract.js +0 -35
  12. package/src/cssPlugins/layout/index.js +0 -12
  13. package/src/cssPlugins/layout/parser.js +0 -81
  14. package/src/cssPlugins/mixins.js +0 -63
  15. package/src/cssPlugins/presets.js +0 -73
  16. package/src/cssPlugins/spx/README.md +0 -73
  17. package/src/cssPlugins/spx/calls.js +0 -12
  18. package/src/cssPlugins/spx/cascade.js +0 -41
  19. package/src/cssPlugins/spx/index.js +0 -13
  20. package/src/cssPlugins/stackChildren.js +0 -205
  21. package/src/jsxPlugins/index.js +0 -10
  22. package/src/jsxPlugins/mediaValidation/index.js +0 -49
  23. package/src/jsxPlugins/origamiCompatibility/ast.js +0 -30
  24. package/src/jsxPlugins/origamiCompatibility/componentBindings.js +0 -40
  25. package/src/jsxPlugins/origamiCompatibility/index.js +0 -48
  26. package/src/jsxPlugins/origamiCompatibility/rules/containerProps.js +0 -15
  27. package/src/jsxPlugins/origamiCompatibility/rules/richTextHtml.js +0 -22
  28. package/src/jsxPlugins/validation/checks.js +0 -105
  29. package/src/jsxPlugins/validation/index.js +0 -65
  30. package/src/jsxPlugins/validation/rules.js +0 -24
  31. package/src/jsxPlugins/validation/tree.js +0 -15
  32. package/src/transform/context.js +0 -13
  33. package/src/transform/css/postcssPlugin.js +0 -60
  34. package/src/transform/css/runPlugins.js +0 -40
  35. package/src/transform/jsx/babelAdapter.js +0 -37
  36. package/src/transform/jsx/runPlugins.js +0 -23
  37. package/src/transform/layoutError.js +0 -19
  38. package/src/transform/pipeline.js +0 -60
  39. package/src/transform/registry.js +0 -40
  40. package/src/transform/types.js +0 -47
package/README.md CHANGED
@@ -58,7 +58,7 @@ and semantic container wrappers. Settings-only breakpoint overrides therefore af
58
58
  without repeating the size declarations. The layout compiler does not reset SPX settings;
59
59
  wrappers and nested components follow normal CSS inheritance. Scale retains the signed
60
60
  half-pixel floor; fixed values retain fractional pixels; zero is zero. See the
61
- [SPX plugin README](src/cssPlugins/spx/README.md) for a simple example and implementation details.
61
+ [SPX plugin README](../origami-transforms/src/cssPlugins/spx/README.md) for a simple example and implementation details.
62
62
 
63
63
  ## Release and limitations
64
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/origami-site-runtime",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Vite development and build helpers for wix-import static sites",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -14,23 +14,23 @@
14
14
  "test:browser": "node test/spx.browser.mjs && node test/semanticContainer.browser.mjs"
15
15
  },
16
16
  "dependencies": {
17
- "@babel/core": "^7.26.0",
18
17
  "@babel/parser": "^7.25.0",
19
18
  "@babel/types": "^7.25.2",
20
19
  "@wix/builder-error-pages": "^1.1.6",
21
20
  "@wix/builder-piler-components": "^1.6.7",
22
- "@wix/origami-fonts": "1.0.0",
23
- "@wix/origami-system-components": "1.6.0",
24
- "postcss": "^8.5.3",
25
- "postcss-custom-media": "^12.0.1",
21
+ "@wix/origami-transforms": "0.1.0",
26
22
  "react": "^18.3.1",
27
23
  "react-dom": "^18.3.1"
28
24
  },
29
25
  "devDependencies": {
30
- "@wix/origami-types": "1.1.0",
26
+ "@babel/core": "^7.26.0",
31
27
  "@types/node": "^22.0.0",
32
28
  "@vitejs/plugin-react": "^4.3.4",
29
+ "@wix/origami-system-components": "1.0.0",
30
+ "@wix/origami-types": "1.1.0",
33
31
  "playwright": "^1.61.1",
32
+ "postcss": "^8.5.3",
33
+ "postcss-custom-media": "^12.0.1",
34
34
  "typescript": "^6.0.3",
35
35
  "vite": "^6.0.0"
36
36
  },
@@ -65,5 +65,5 @@
65
65
  "./default-error-404": "./src/default-error-404.jsx"
66
66
  },
67
67
  "type": "module",
68
- "falconPackageHash": "598623d5aa01cc1cee0cc4ee1db03460f0154a007c7ffe9519a3efa6"
68
+ "falconPackageHash": "8c92f35adaf551b2211960c206b4571d4c8e90ea4d2a28fdddd390de"
69
69
  }
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { createSiteViteConfig } from './siteViteConfig.js'
2
- export { cssPlugins } from './cssPlugins/index.js'
3
- export { jsxPlugins } from './jsxPlugins/index.js'
2
+ export { cssPlugins } from '@wix/origami-transforms'
3
+ export { jsxPlugins } from '@wix/origami-transforms'
4
4
  export { pagesToRoutes } from './routing/pagesToRoutes.js'
@@ -2,7 +2,7 @@
2
2
  // module id this package exposes to a generated site's browser bundle.
3
3
 
4
4
  import { createRequire } from 'node:module'
5
- import { isAbsolute, relative, resolve, sep } from 'node:path'
5
+ import { resolve } from 'node:path'
6
6
 
7
7
  export const runtimeDefaultError404Module = '@wix/origami-site-runtime/default-error-404'
8
8
 
@@ -21,11 +21,3 @@ export const siteLayout = root => {
21
21
  url === undefined ? resolve(rootPath, '404.html') : resolve(rootPath, `.${url}`, 'index.html'),
22
22
  })
23
23
  }
24
-
25
- // Path of `id` relative to the site root in posix form, or undefined when `id` escapes the root.
26
- export const relativeToSite = (root, id) => {
27
- const filePath = id.split('?', 1)[0]
28
- const fromRoot = relative(resolve(root), filePath)
29
- if (!fromRoot || fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) return undefined
30
- return fromRoot.split(sep).join('/')
31
- }
@@ -6,7 +6,7 @@ import { defineConfig, searchForWorkspaceRoot } from 'vite'
6
6
  import { pagesToRoutes } from './routing/pagesToRoutes.js'
7
7
  import { itemFolderImports } from './vitePlugins/itemFolderImports.js'
8
8
  import { runtimeDefaultError404Path, siteLayout } from './routing/siteLayout.js'
9
- import { createTransformPipeline } from './transform/pipeline.js'
9
+ import { createTransformPipeline } from '@wix/origami-transforms'
10
10
  import { reactBabelPlugin } from './vitePlugins/reactBabel.js'
11
11
  import { siteDocumentsPlugin } from './vitePlugins/siteDocumentsPlugin.js'
12
12
 
@@ -27,8 +27,8 @@ const isStylesheet = asset => (asset.names?.[0] ?? asset.originalFileNames?.[0]
27
27
  /**
28
28
  * @param {{
29
29
  * root?: string,
30
- * jsxPlugins?: readonly import('./transform/types.js').JsxTransformPlugin[],
31
- * cssPlugins?: readonly import('./transform/types.js').CssTransformPlugin[],
30
+ * jsxPlugins?: readonly import('@wix/origami-transforms').JsxTransformPlugin[],
31
+ * cssPlugins?: readonly import('@wix/origami-transforms').CssTransformPlugin[],
32
32
  * }} [options]
33
33
  */
34
34
  export const createSiteViteConfig = ({ root, jsxPlugins, cssPlugins } = {}) => {
@@ -61,7 +61,7 @@ export const createSiteViteConfig = ({ root, jsxPlugins, cssPlugins } = {}) => {
61
61
  ],
62
62
  // Stating the PostCSS plugins here also opts out of Vite's search for a project postcss
63
63
  // config, which a generated site does not have. The CSS stage has to run as a PostCSS
64
- // plugin rather than a Vite one — see transform/css/postcssPlugin.js.
64
+ // plugin rather than a Vite one — see origami-transforms/src/transform/css/postcssPlugin.js.
65
65
  css: { postcss: { plugins: [pipeline.postcssPlugin] } },
66
66
  server: {
67
67
  port: DEFAULT_DEVELOPMENT_PORT,
@@ -1,52 +0,0 @@
1
- // `@custom-media --name (query);` with `@media (--name) {}` (Media Queries Level 5). No browser ships it, so the
2
- // alias is always expanded. The expansion is postcss-custom-media's, which matches the DM output exactly; this
3
- // descriptor only runs that plugin over the stage's shared Root, so the stage keeps its gating (site-owned files)
4
- // and its ordering.
5
- //
6
- // A definition is only visible within the document that declares it. DM emits the definitions in the theme
7
- // carrier, the generator `@import`s that into every page stylesheet, and Vite's postcss-import inlines it before
8
- // this stage runs — so definition and usage share one Root.
9
-
10
- import postcss from 'postcss'
11
- import postcssCustomMedia from 'postcss-custom-media'
12
-
13
- const processor = postcss([postcssCustomMedia()])
14
-
15
- const CUSTOM_MEDIA_QUERY = /\(\s*--/
16
-
17
- // Whether the document uses the feature at all: a definition to remove, or an alias to expand. Every carrier but
18
- // the theme has neither, and this walk visits only at-rules, where the expansion walks and re-marks every node.
19
- const usesCustomMedia = root => {
20
- let used = false
21
- root.walkAtRules(atRule => {
22
- if (atRule.name === 'custom-media' || (atRule.name === 'media' && CUSTOM_MEDIA_QUERY.test(atRule.params))) {
23
- used = true
24
- return false
25
- }
26
- })
27
- return used
28
- }
29
-
30
- /** @type {import('../transform/types.js').CssTransformPlugin} */
31
- export const customMediaPlugin = {
32
- name: 'custom-media',
33
- order: 0,
34
- transform(root, context) {
35
- if (!usesCustomMedia(root)) return
36
- // A Root handed to `process` is transformed in place. The plugin has no async hooks, so `sync` is safe.
37
- // Its warnings go to the nested Result and are dropped: the only one it raises is a cyclic definition,
38
- // which leaves the alias unexpanded and so fails below anyway. A definition it cannot parse at all is a
39
- // different matter — it emits the unparsed bytes as the query and warns about nothing, which no check
40
- // here can see. DM does not emit one.
41
- processor.process(root, { from: context.filePath }).sync()
42
- // An unexpanded alias is left as authored, and a browser evaluates it to "unknown", which silently
43
- // disables the whole block.
44
- root.walkAtRules('media', atRule => {
45
- if (CUSTOM_MEDIA_QUERY.test(atRule.params)) {
46
- throw new Error(
47
- `@media ${atRule.params} has no usable @custom-media definition: it is missing or cyclic`
48
- )
49
- }
50
- })
51
- },
52
- }
@@ -1,93 +0,0 @@
1
- // Checks the theme's font tokens (`--wst-heading-1-font: normal normal 700 --spx(88)/1em benzin`). It changes
2
- // nothing. The editor reads the family at the end of each one as one Wix font id and fails the import otherwise, so
3
- // the build fails, with every such token listed, unless the family is exactly the id of a font in the Wix font
4
- // catalog bundled in @wix/origami-fonts: no quotes, commas or fallbacks, the same case. A legacy font, which the
5
- // editor no longer offers, counts as not in the catalog. `origami search-font` finds the right id.
6
-
7
- import { basename } from 'node:path'
8
- import { fonts } from '@wix/origami-fonts'
9
- import { LayoutError } from '../transform/layoutError.js'
10
-
11
- const FONT_TOKEN = /^--wst-[\w-]+-font$/
12
- // The size in a `font` shorthand, with its optional line height: the family follows it. The number's quantifiers
13
- // never overlap, so a long run of digits that is not a size fails in linear time.
14
- const SIZE = /^(?:--spx\(.*\)|calc\(.*\)|(?:\d+(?:\.\d*)?|\.\d+)(?:px|em|rem|%|vw|vh|pt)?)(?:\/\S+)?$/i
15
- const FONT_IDS = new Set(fonts.filter(font => font.permissions !== 'legacy').map(font => font.id))
16
-
17
- /** A value split on top-level whitespace, so `--spx(16)/1.3em` stays one token. */
18
- const tokens = value => {
19
- const result = []
20
- let depth = 0
21
- let current = ''
22
- for (const char of value.trim()) {
23
- if (char === '(') depth++
24
- else if (char === ')') depth--
25
- if (depth === 0 && /\s/.test(char)) {
26
- if (current) result.push(current)
27
- current = ''
28
- } else current += char
29
- }
30
- if (current) result.push(current)
31
- return result
32
- }
33
-
34
- /** A font shorthand's family as written; undefined for `var(…)`. */
35
- const familyOf = value => {
36
- if (/var\(/.test(value)) return undefined
37
- const parts = tokens(value)
38
- // The last size: the repo's TS lib (ES2022) has no findLastIndex.
39
- let size = parts.length - 1
40
- while (size >= 0 && !SIZE.test(parts[size])) size--
41
- if (size === -1 || size === parts.length - 1) return undefined
42
- return parts.slice(size + 1).join(' ')
43
- }
44
-
45
- /** ` (theme.css:12)`: the carrier the declaration was written in, which the bundler doesn't name. */
46
- const where = declaration => {
47
- const file = declaration.source?.input.file
48
- const line = declaration.source?.start?.line
49
- return file ? ` (${basename(file)}${line ? `:${line}` : ''})` : ''
50
- }
51
-
52
- /**
53
- * Where `source` starts, as LayoutError's location, leaving out what it doesn't know.
54
- * @param {import('postcss').Declaration['source']} source
55
- */
56
- const locationOf = source => {
57
- /** @type {{ file?: string, line?: number, column?: number }} */
58
- const location = {}
59
- if (source?.input.file !== undefined) location.file = source.input.file
60
- if (source?.start !== undefined) {
61
- location.line = source.start.line
62
- location.column = source.start.column
63
- }
64
- return location
65
- }
66
-
67
- /** @type {import('../transform/types.js').CssTransformPlugin} */
68
- export const fontsPlugin = {
69
- name: 'fonts',
70
- // Only checks; it reads the tokens as authored, before spx rewrites the sizes the family follows.
71
- order: 5,
72
- transform(root) {
73
- /** @type {import('postcss').Declaration[]} */
74
- const declarations = []
75
- root.walkDecls(FONT_TOKEN, declaration => {
76
- declarations.push(declaration)
77
- })
78
- const violations = []
79
- let first
80
- for (const declaration of declarations) {
81
- const family = familyOf(declaration.value)
82
- if (family === undefined || FONT_IDS.has(family)) continue
83
- first ??= declaration.source
84
- violations.push(
85
- `${declaration.prop}${where(declaration)} must name exactly one Wix font id from the catalog; found ` +
86
- `${family}. Run \`origami search-font --font <name>\` for the id.`
87
- )
88
- }
89
- if (violations.length) {
90
- throw new LayoutError(violations, locationOf(first))
91
- }
92
- },
93
- }
@@ -1,32 +0,0 @@
1
- // The CSS stage's plugins, in the order they run. DM authors its CSS in features no browser implements yet, or only
2
- // Chromium does; these compile them away so a generated site renders everywhere. A caller can still pass its own
3
- // list through `createSiteViteConfig({ cssPlugins })`.
4
- //
5
- // A descriptor needs a `name`, an `order`, and `transform(root, context)`, where `root` is the PostCSS Root for the
6
- // stylesheet; see `../transform/types.js` for the contract and `../transform/css/postcssPlugin.js` for what the
7
- // stage guarantees a plugin sees. The order matters once: mixin bodies are copied into their calling rules before
8
- // layout routes declarations to wrappers and SPX converts calls using the browser's active settings. stack-children
9
- // only checks, and runs between mixins and layout: it needs the mixins' declarations and the stack rules as authored,
10
- // before layout compiles them away. presets runs after mixins too, so a `--preset` a mixin body carries is moved
11
- // like an authored one. fonts only checks the theme's font tokens, as authored.
12
- //
13
- // `@layer` needs no plugin: cascade layers have been in every browser since March 2022.
14
-
15
- import { customMediaPlugin } from './customMedia.js'
16
- import { fontsPlugin } from './fonts.js'
17
- import { mixinsPlugin } from './mixins.js'
18
- import { presetsPlugin } from './presets.js'
19
- import { layoutPlugin } from './layout/index.js'
20
- import { spxPlugin } from './spx/index.js'
21
- import { stackChildrenPlugin } from './stackChildren.js'
22
-
23
- /** @type {readonly import('../transform/types.js').CssTransformPlugin[]} */
24
- export const cssPlugins = Object.freeze([
25
- customMediaPlugin,
26
- fontsPlugin,
27
- mixinsPlugin,
28
- stackChildrenPlugin,
29
- presetsPlugin,
30
- layoutPlugin,
31
- spxPlugin,
32
- ])
@@ -1,77 +0,0 @@
1
- # Layout puts container styles on the right elements
2
-
3
- A container has an outer component, an overflow wrapper and a content wrapper.
4
- The outer component controls its place on the page. The overflow wrapper controls
5
- scrolling and clipping. The content wrapper arranges the children.
6
-
7
- In site-owned `layout.css`, `@origami-target` says which wrapper a style belongs to:
8
-
9
- ```css
10
- #container {
11
- --container-layout-type: FlexContainerLayout;
12
-
13
- @origami-target internal-container-content-wrapper {
14
- display: flex;
15
- flex-direction: column;
16
- row-gap: --spx(16);
17
- --content-max-width: 1200px;
18
- --content-max-width-align: center;
19
- }
20
-
21
- @origami-target internal-container-overflow-wrapper {
22
- overflow-y: auto;
23
- }
24
- }
25
- ```
26
-
27
- The plugin replaces these target blocks with ordinary CSS rules that select the
28
- matching wrappers. Grid, flex, gaps, padding and content width go on the content
29
- wrapper; overflow goes on the overflow wrapper. Other component styles stay on
30
- the outer element.
31
-
32
- ## What the plugin adds
33
-
34
- The outer element and overflow wrapper get a one-cell grid so the wrappers fill
35
- the container. The content wrapper gets sizing and positioning styles, including
36
- a maximum width and left, center or right alignment. It lets pointer events pass
37
- through empty space while its children remain clickable.
38
-
39
- Each container starts with no content width limit and centered alignment. These
40
- defaults live in a separate CSS layer so authored settings can override them.
41
- Media queries, scopes and other surrounding conditions still apply to generated
42
- rules. Vertical overflow also sets `--sticky-offset` to `0px` for `scroll` and
43
- resets it for other values.
44
-
45
- ## Why validation runs before transformations
46
-
47
- During development and builds, Vite first inlines CSS imports. Our CSS pipeline
48
- then runs every plugin's optional `validate()` before running any plugin's
49
- `transform()`. Currently, layout is the only plugin with this validation hook.
50
- If validation fails, processing stops before transformations begin.
51
-
52
- Layout needs this early check because targets are not allowed inside mixins.
53
- The mixin plugin runs before layout compilation: it removes mixin definitions
54
- and copies their contents into the rules that use them. After that, an invalid
55
- target could look like a normal component rule, or disappear with an unused
56
- mixin. Early validation checks the original placement before that evidence is lost.
57
-
58
- Previously, plugins only had a transformation step and checked input as they
59
- processed it. The target placement rules introduced the need for a separate
60
- validation phase. Layout calls `parseTargets()` again during transformation to
61
- check and collect the target nodes from the updated stylesheet.
62
-
63
- ## Files and boundaries
64
-
65
- - `index.js` registers validation and transformation.
66
- - `contract.js` lists the supported targets, their properties and layout displays.
67
- - `parser.js` checks target placement, properties and values, and rejects duplicates.
68
- - `compiler.js` builds wrapper selectors, adds base styles and moves declarations.
69
-
70
- Targets must come from site-owned `layout.css` and belong to a single component ID
71
- rule. Unsupported targets or properties fail with an error rather than being ignored.
72
-
73
- This plugin generates CSS; the runtime supplies the wrapper elements. SPX values
74
- are converted afterward by the SPX plugin.
75
-
76
- `test/semanticContainer.browser.mjs` checks container behavior in the browser.
77
- Run it together with the SPX checks using the package's `test:browser` script.
@@ -1,116 +0,0 @@
1
- import postcss from 'postcss'
2
- import { buildContainerClasses } from '@wix/origami-system-components/layout-contract'
3
- import { CONTENT } from './contract.js'
4
- import { isLayoutSource, parseTargets } from './parser.js'
5
-
6
- const grid = { display: 'grid', 'grid-template-rows': '1fr', 'grid-template-columns': 'minmax(0, 1fr)' }
7
-
8
- /** Preserve source locations on every emitted declaration and selector. */
9
- const ruleFor = (owner, selector, declarations) => {
10
- const rule = postcss.rule({ selector, source: owner.source })
11
- for (const [prop, value] of Object.entries(declarations)) {
12
- const declaration = postcss.decl({ prop, value: String(value) })
13
- declaration.source = owner.source
14
- rule.append(declaration)
15
- }
16
- return rule
17
- }
18
-
19
- /** @param {import('postcss').Root} root
20
- * @param {import('../../transform/types.js').TransformContext} context */
21
- export const compileContainers = (root, context) => {
22
- const owners = parseTargets(root, context)
23
- if (!owners.size) return
24
- // Thunderbolt writes max-width and margin-left directly; neither property inherits.
25
- // We use custom properties so width and alignment can change independently at breakpoints.
26
- // Those do inherit, so each container needs defaults to avoid taking its parent's settings.
27
- // This first layer keeps defaults weaker than authored rules, including rules in later layers.
28
- // Clone only conditional/scope envelopes into it so the defaults apply under the same conditions.
29
- const defaultsLayer = postcss.atRule({ name: 'layer', params: 'origami-container-defaults' })
30
- if (root.source) defaultsLayer.source = root.source
31
- root.prepend(defaultsLayer)
32
- // Source identity prevents a matching selector in another carrier from becoming an owner.
33
- const identities = new Set([...owners.keys()].map(rule => `${rule.source?.input.file}\0${rule.selector}`))
34
- const rules = []
35
- root.walkRules(rule => {
36
- if (isLayoutSource(rule, context) && identities.has(`${rule.source?.input.file}\0${rule.selector}`))
37
- rules.push(rule)
38
- })
39
- // Mechanics are emitted before the first rule in each envelope; authored declarations win.
40
- const contexts = new WeakMap()
41
- for (const owner of rules) {
42
- const { contentClass, overflowClass } = buildContainerClasses(owner.selector.slice(1))
43
- // In @scope, an unqualified selector cannot select the scoping root. DM emits
44
- // #id there too; :where(:scope) includes that root without adding specificity.
45
- let selector = owner.selector
46
- for (let ancestor = owner.parent; ancestor && ancestor.type !== 'root'; ancestor = ancestor.parent) {
47
- if (ancestor.type === 'atrule' && ancestor.name === 'scope') {
48
- if (ancestor.params.trim() === `(${selector})`) selector = `:where(:scope)${selector}`
49
- break
50
- }
51
- }
52
- const overflow = `${selector} > .${overflowClass}`
53
- const content = `${overflow} > .${contentClass}`
54
- const emitted = contexts.get(owner.parent) ?? new Set()
55
- if (!emitted.has(owner.selector)) {
56
- let defaultsParent = defaultsLayer
57
- const envelopes = []
58
- for (let ancestor = owner.parent; ancestor && ancestor.type !== 'root'; ancestor = ancestor.parent) {
59
- if (ancestor.type === 'atrule' && ancestor.name !== 'layer') envelopes.unshift(ancestor)
60
- }
61
- for (const envelope of envelopes) {
62
- const clone = envelope.clone({ nodes: [] })
63
- defaultsParent.append(clone)
64
- defaultsParent = clone
65
- }
66
- defaultsParent.append(
67
- ruleFor(owner, `:where(${content})`, {
68
- '--content-max-width': 'none',
69
- '--origami-content-align': '0.5',
70
- })
71
- )
72
- owner.before(ruleFor(owner, selector, grid))
73
- owner.before(ruleFor(owner, overflow, { ...grid, position: 'relative', 'align-self': 'stretch' }))
74
- owner.before(
75
- ruleFor(owner, content, {
76
- 'box-sizing': 'border-box',
77
- position: 'relative',
78
- 'pointer-events': 'none',
79
- 'max-width': 'var(--content-max-width)',
80
- '--section-max-width': 'var(--content-max-width)',
81
- 'margin-left':
82
- 'clamp(0px, (100% - var(--content-max-width, 100%)) * var(--origami-content-align), 100 * var(--one-unit, 1%))',
83
- })
84
- )
85
- owner.before(ruleFor(owner, `${content} > *`, { 'pointer-events': 'auto' }))
86
- emitted.add(owner.selector)
87
- contexts.set(owner.parent, emitted)
88
- }
89
- owner.selector = selector
90
- let previous = owner
91
- for (const target of owners.get(owner) ?? []) {
92
- const generated = ruleFor(owner, target.params.trim() === CONTENT ? content : overflow, {})
93
- for (const node of target.nodes ?? []) {
94
- const clone = node.clone()
95
- if (clone.type === 'decl') {
96
- if (clone.prop === '--content-max-width-align') {
97
- clone.prop = '--origami-content-align'
98
- // Left margin uses none, half or all of the extra space to align the content.
99
- clone.value = { left: '0', center: '0.5', right: '1' }[clone.value]
100
- }
101
- if (clone.prop === 'overflow-y')
102
- generated.append(
103
- clone.clone({
104
- prop: '--sticky-offset',
105
- value: clone.value === 'scroll' ? '0px' : 'initial',
106
- })
107
- )
108
- }
109
- generated.append(clone)
110
- }
111
- previous.after(generated)
112
- previous = generated
113
- target.remove()
114
- }
115
- }
116
- }
@@ -1,35 +0,0 @@
1
- // DM JSX era 0.42, document-management ef124715766d47d0b420bb3ec5a6c54a674a7b9b.
2
- // Consumer routing only: component eligibility is validated by DM against JSX.
3
- export const CONTENT = 'internal-container-content-wrapper'
4
- export const OVERFLOW = 'internal-container-overflow-wrapper'
5
- export const targets = Object.freeze({
6
- [CONTENT]: new Set([
7
- 'position',
8
- 'display',
9
- 'grid-template-columns',
10
- 'grid-template-rows',
11
- 'grid-auto-flow',
12
- 'grid-auto-rows',
13
- 'grid-auto-columns',
14
- 'row-gap',
15
- 'column-gap',
16
- 'flex-direction',
17
- 'flex-wrap',
18
- 'justify-content',
19
- 'align-items',
20
- 'align-content',
21
- 'padding-top',
22
- 'padding-bottom',
23
- 'padding-left',
24
- 'padding-right',
25
- '--content-max-width',
26
- '--content-max-width-align',
27
- ]),
28
- [OVERFLOW]: new Set(['overflow-x', 'overflow-y']),
29
- })
30
- export const displays = Object.freeze({
31
- GridContainerLayout: 'grid',
32
- FlexContainerLayout: 'flex',
33
- StackContainerLayout: 'flex',
34
- OrganizerContainerLayout: 'flex',
35
- })
@@ -1,12 +0,0 @@
1
- import { compileContainers } from './compiler.js'
2
- import { parseTargets } from './parser.js'
3
-
4
- /** @type {import('../../transform/types.js').CssTransformPlugin} */
5
- export const layoutPlugin = {
6
- name: 'layout',
7
- order: 15,
8
- validate: (root, context) => {
9
- parseTargets(root, context)
10
- },
11
- transform: compileContainers,
12
- }
@@ -1,81 +0,0 @@
1
- import { basename } from 'node:path'
2
- import { isValidContainerId } from '@wix/origami-system-components/layout-contract'
3
- import { relativeToSite } from '../../routing/siteLayout.js'
4
- import { CONTENT, OVERFLOW, targets, displays } from './contract.js'
5
-
6
- /** @param {import('postcss').Node} node
7
- * @param {import('../../transform/types.js').TransformContext} context */
8
- export const isLayoutSource = (node, context) => {
9
- const file = node.source?.input.file
10
- if (!file || basename(file) !== 'layout.css') return false
11
- const relative = context.layout ? relativeToSite(context.layout.root, file) : file
12
- return relative !== undefined && !relative.split(/[\\/]/).includes('node_modules')
13
- }
14
-
15
- /** Validate before mixins can erase the original placement/source of a target.
16
- * @param {import('postcss').Root} root
17
- * @param {import('../../transform/types.js').TransformContext} context */
18
- export const parseTargets = (root, context) => {
19
- /** @type {Map<import('postcss').Rule, import('postcss').AtRule[]>} */
20
- const owners = new Map()
21
- root.walkAtRules('origami-target', target => {
22
- const name = target.params.trim()
23
- if (!Object.hasOwn(targets, name)) throw target.error(`Unknown @origami-target ${name}`)
24
- const owner = target.parent
25
- if (owner?.type !== 'rule' || !owner.selector.startsWith('#') || !isValidContainerId(owner.selector.slice(1))) {
26
- throw target.error('@origami-target requires a single component ID rule')
27
- }
28
- for (let ancestor = owner.parent; ancestor && ancestor.type !== 'root'; ancestor = ancestor.parent) {
29
- if (
30
- ancestor.type !== 'atrule' ||
31
- !['scope', 'media', 'layer', 'supports', 'container'].includes(ancestor.name)
32
- ) {
33
- throw target.error('@origami-target is only allowed inside layout rule envelopes')
34
- }
35
- }
36
- if (!isLayoutSource(target, context))
37
- throw target.error('@origami-target must originate in site-owned layout.css')
38
- const siblings = owners.get(owner) ?? []
39
- if (siblings.some(sibling => sibling.params.trim() === name)) throw target.error(`Duplicate target ${name}`)
40
- const seen = new Set()
41
- for (const node of target.nodes ?? []) {
42
- if (node.type === 'comment') continue
43
- if (node.type !== 'decl') throw node.error('Target bodies must contain declarations only')
44
- if (seen.has(node.prop)) throw node.error(`Duplicate target property ${node.prop}`)
45
- seen.add(node.prop)
46
- if (!targets[name].has(node.prop)) throw node.error(`${node.prop} does not belong to ${name}`)
47
- if (node.prop === 'position' && node.value !== 'relative')
48
- throw node.error('Content position must be relative')
49
- if (name === OVERFLOW && !['auto', 'scroll', 'hidden', 'visible', 'clip'].includes(node.value)) {
50
- throw node.error(`Unsupported overflow value ${node.value}`)
51
- }
52
- if (node.prop === '--content-max-width-align' && !['left', 'center', 'right'].includes(node.value)) {
53
- throw node.error(`Unsupported content alignment ${node.value}`)
54
- }
55
- if (node.prop === 'display') {
56
- const type = owner.nodes.find(item => item.type === 'decl' && item.prop === '--container-layout-type')
57
- if (
58
- !['grid', 'flex'].includes(node.value) ||
59
- (type?.type === 'decl' &&
60
- (!Object.hasOwn(displays, type.value) || displays[type.value] !== node.value))
61
- ) {
62
- throw node.error('Container type and content display are inconsistent')
63
- }
64
- }
65
- }
66
- siblings.push(target)
67
- owners.set(owner, siblings)
68
- })
69
- for (const owner of owners.keys()) {
70
- const seen = new Set()
71
- for (const node of owner.nodes) {
72
- if (node.type !== 'decl') continue
73
- if (seen.has(node.prop)) throw node.error(`Duplicate root property ${node.prop}`)
74
- seen.add(node.prop)
75
- if ((targets[CONTENT].has(node.prop) && node.prop !== 'position') || targets[OVERFLOW].has(node.prop)) {
76
- throw node.error(`${node.prop} belongs inside @origami-target`)
77
- }
78
- }
79
- }
80
- return owners
81
- }