fix(dom-css): parse inline styles with CSSOM
This commit is contained in:
parent
346d13c405
commit
4aa41fbf97
|
|
@ -5,7 +5,7 @@ OpenPencil's DOM/CSS compatibility layer should not grow hand-rolled CSS parsing
|
|||
## Current dependency-backed pieces
|
||||
|
||||
- HTML parsing: `parse5` in the headless runtime, native `DOMParser` in the browser runtime.
|
||||
- Stylesheet parsing: `@acemir/cssom` in the headless runtime, native CSSOM in the browser runtime.
|
||||
- Stylesheet parsing and headless inline style declaration parsing: `@acemir/cssom` in the headless runtime, native CSSOM in the browser runtime.
|
||||
- Tailwind generation: Tailwind v4 `compile()` / `build()`.
|
||||
- Color parsing: `@open-pencil/core/color` (`culori`-backed).
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ These are intentionally limited and should not be expanded without replacing the
|
|||
| Selector matching/specificity | `packages/dom-css/src/headless-css.ts` | Supports simple tag/id/class selectors plus descendant and child combinators. Rejects pseudo/classes and attributes. | Replace with a selector engine over DesignDOM or run browser/runtime oracle for complex CSS. |
|
||||
| Shorthand expansion | `packages/dom-css/src/headless-css.ts` | Expands simple margin/padding boxes, border color/width, and background color. | Use parsed declarations from CSSOM/native computed style; avoid adding new shorthand parsers. |
|
||||
| `calc()` / custom properties | `packages/dom-css/src/headless-css.ts` | Resolves variables by direct lookup and only handles `calc(<number><unit> * <number>)`. | Browser runtime for real computed values; do not add arithmetic or fallback parsing manually. |
|
||||
| Inline style strings | `packages/dom-css/src/style-attribute.ts`, `packages/dom-css/src/jsx/runtime.ts` | Splits simple `style="a: b;"` text into declarations. | Use native `CSSStyleDeclaration` in browser paths; keep only for simple authored test data or replace with a CSS declaration parser. |
|
||||
| JSX object style serialization | `packages/dom-css/src/jsx/runtime.ts` | Serializes object style props to inline CSS strings with simple camelCase to kebab-case conversion. | Keep as JSX authoring serialization only; use CSSOM/native parsing after HTML parsing. |
|
||||
| Shadow values | `packages/dom-css/src/css-values.ts` | Extracts one color and numeric offsets for simple shadows. | Replace with a CSS value parser before supporting multiple shadows, inset, color functions, or spread edge cases. |
|
||||
| Numeric lengths | `packages/dom-css/src/css-values.ts` | Parses px/rem-ish numbers with `Number.parseFloat`. | Consume browser-computed pixel values where available; keep headless numeric parsing narrow. |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
import { parse } from '@acemir/cssom'
|
||||
import type { CSSStyleRuleLike } from '@acemir/cssom'
|
||||
|
||||
import type { DesignStyleDeclaration } from './types'
|
||||
|
||||
function firstStyleRule(cssText: string): CSSStyleRuleLike | null {
|
||||
const [rule] = parse(cssText).cssRules
|
||||
if (!rule || typeof rule !== 'object' || !('style' in rule)) return null
|
||||
return rule as CSSStyleRuleLike
|
||||
}
|
||||
|
||||
export function parseStyleAttribute(value: string | undefined): DesignStyleDeclaration | undefined {
|
||||
if (!value) return undefined
|
||||
|
||||
const style: DesignStyleDeclaration = {}
|
||||
for (const declaration of value.split(';')) {
|
||||
const separatorIndex = declaration.indexOf(':')
|
||||
if (separatorIndex <= 0) continue
|
||||
const rule = firstStyleRule(`*{${value}}`)
|
||||
if (!rule) return undefined
|
||||
|
||||
const property = declaration.slice(0, separatorIndex).trim().toLowerCase()
|
||||
const propertyValue = declaration.slice(separatorIndex + 1).trim()
|
||||
if (property.length > 0 && propertyValue.length > 0) style[property] = propertyValue
|
||||
const style: DesignStyleDeclaration = {}
|
||||
for (const property of Array.from(rule.style)) {
|
||||
const propertyValue = rule.style.getPropertyValue(property)
|
||||
if (propertyValue) style[property] = propertyValue
|
||||
}
|
||||
|
||||
return Object.keys(style).length > 0 ? style : undefined
|
||||
|
|
|
|||
|
|
@ -32,6 +32,21 @@ describe('@open-pencil/dom-css runtime', () => {
|
|||
expect(section.children[0]).toEqual({ type: 'text', text: 'OpenPencil' })
|
||||
})
|
||||
|
||||
it('parses inline style values with embedded semicolons', () => {
|
||||
const runtime = createHeadlessCSSRuntime()
|
||||
const document = runtime.parseHTML(
|
||||
'<section style="background-image: url(\'data:image/svg+xml;utf8,<svg></svg>\'); width: 320px">OpenPencil</section>'
|
||||
)
|
||||
const section = document.children[0]
|
||||
|
||||
expect(section?.type).toBe('element')
|
||||
if (section?.type !== 'element') return
|
||||
expect(section.inlineStyle?.['background-image']).toBe(
|
||||
"url('data:image/svg+xml;utf8,<svg></svg>')"
|
||||
)
|
||||
expect(section.inlineStyle?.width).toBe('320px')
|
||||
})
|
||||
|
||||
it('computes selector specificity, inheritance, and shorthands', async () => {
|
||||
const runtime = createHeadlessCSSRuntime()
|
||||
const parsed = runtime.parseHTML(`
|
||||
|
|
|
|||
Loading…
Reference in a new issue