Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,28 @@ describe('CSSNode', () => {

expect(media.type).toBe(AT_RULE)
expect(media.has_prelude).toBe(true)
expect(media.prelude).toBe('(min-width: 768px)')
expect(media.prelude).not.toBeNull()
expect(media.prelude?.text).toBe('(min-width: 768px)')
})

test('prelude node can be walked to access structured children', () => {
const source = '@media screen and (min-width: 768px) { }'
const root = parse(source)
const media = root.first_child!
const prelude = media.prelude!

// Prelude is a wrapper node containing the structured children
expect(prelude).not.toBeNull()
expect(prelude.has_children).toBe(true)

// Can iterate over prelude children (media queries)
const children = [...prelude]
expect(children.length).toBeGreaterThan(0)

// Can access structured nodes within prelude
const mediaQuery = prelude.first_child!
expect(mediaQuery.type_name).toBe('MediaQuery')
expect(mediaQuery.text).toBe('screen and (min-width: 768px)')
})

test('should return true for @supports with prelude', () => {
Expand All @@ -86,7 +107,8 @@ describe('CSSNode', () => {

expect(supports.type).toBe(AT_RULE)
expect(supports.has_prelude).toBe(true)
expect(supports.prelude).toBe('(display: grid)')
expect(supports.prelude).not.toBeNull()
expect(supports.prelude?.text).toBe('(display: grid)')
})

test('should return true for @layer with name', () => {
Expand All @@ -96,7 +118,8 @@ describe('CSSNode', () => {

expect(layer.type).toBe(AT_RULE)
expect(layer.has_prelude).toBe(true)
expect(layer.prelude).toBe('utilities')
expect(layer.prelude).not.toBeNull()
expect(layer.prelude?.text).toBe('utilities')
})

test('should return false for @layer without name', () => {
Expand All @@ -116,7 +139,8 @@ describe('CSSNode', () => {

expect(keyframes.type).toBe(AT_RULE)
expect(keyframes.has_prelude).toBe(true)
expect(keyframes.prelude).toBe('fadeIn')
expect(keyframes.prelude).not.toBeNull()
expect(keyframes.prelude?.text).toBe('fadeIn')
})

test('should return false for @font-face without prelude', () => {
Expand Down Expand Up @@ -573,9 +597,10 @@ describe('CSSNode', () => {
const source = '@media screen and (min-width: 768px) { body { color: red; } }'
const root = parse(source)
const media = root.first_child!
const prelude = media.first_child!
const preludeWrapper = media.prelude!
const mediaQuery = preludeWrapper.first_child!

expect(prelude.type_name).toBe('MediaQuery')
expect(mediaQuery.type_name).toBe('MediaQuery')
})
})

Expand Down Expand Up @@ -1082,15 +1107,16 @@ describe('CSSNode', () => {
})

test('extracts at-rule properties', () => {
const ast = parse('@media screen { }', { parse_atrule_preludes: false })
const ast = parse('@media screen { }')
const atrule = ast.first_child!

const clone = atrule.clone({ deep: false })

expect(clone.type).toBe(AT_RULE)
expect(clone.type_name).toBe('Atrule')
expect(clone.name).toBe('media')
expect(clone.prelude).toBe('screen')
// Prelude is now a child node, not extracted as property
expect(clone.children).toEqual([])
})

test('extracts dimension value with unit', () => {
Expand Down
1 change: 1 addition & 0 deletions src/arena.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const SUPPORTS_QUERY = 36 // supports query: (display: flex)
export const LAYER_NAME = 37 // layer name: base, components
export const PRELUDE_OPERATOR = 38 // logical operator: and, or, not
export const FEATURE_RANGE = 39 // Range syntax: (50px <= width <= 100px)
export const AT_RULE_PRELUDE = 40 // Wrapper for at-rule prelude children

// Flag constants (bit-packed in 1 byte)
export const FLAG_IMPORTANT = 1 << 0 // Has !important
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
LAYER_NAME,
PRELUDE_OPERATOR,
FEATURE_RANGE,
AT_RULE_PRELUDE,
FLAG_IMPORTANT,
ATTR_OPERATOR_NONE,
ATTR_OPERATOR_EQUAL,
Expand Down Expand Up @@ -92,6 +93,7 @@ export {
LAYER_NAME,
PRELUDE_OPERATOR,
FEATURE_RANGE,
AT_RULE_PRELUDE,
FLAG_IMPORTANT,
ATTR_OPERATOR_NONE,
ATTR_OPERATOR_EQUAL,
Expand Down Expand Up @@ -149,4 +151,5 @@ export const NODE_TYPES = {
LAYER_NAME,
PRELUDE_OPERATOR,
FEATURE_RANGE,
AT_RULE_PRELUDE,
} as const
24 changes: 15 additions & 9 deletions src/css-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
LAYER_NAME,
PRELUDE_OPERATOR,
FEATURE_RANGE,
AT_RULE_PRELUDE,
FLAG_IMPORTANT,
FLAG_HAS_ERROR,
FLAG_HAS_BLOCK,
Expand Down Expand Up @@ -90,6 +91,7 @@ export const TYPE_NAMES = {
[LAYER_NAME]: 'Layer',
[PRELUDE_OPERATOR]: 'Operator',
[FEATURE_RANGE]: 'MediaFeatureRange',
[AT_RULE_PRELUDE]: 'AtrulePrelude',
} as const

export type TypeName = (typeof TYPE_NAMES)[keyof typeof TYPE_NAMES] | 'unknown'
Expand Down Expand Up @@ -134,6 +136,7 @@ export type CSSNodeType =
| typeof LAYER_NAME
| typeof PRELUDE_OPERATOR
| typeof FEATURE_RANGE
| typeof AT_RULE_PRELUDE

// Options for cloning nodes
export interface CloneOptions {
Expand Down Expand Up @@ -304,12 +307,16 @@ export class CSSNode {
}

/**
* Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
* This is an alias for `value` to make at-rule usage more semantic
* Get the prelude node (for at-rules: structured prelude with media queries, layer names, etc.)
* Returns the AT_RULE_PRELUDE wrapper node containing prelude children, or null if no prelude
*/
get prelude(): string | null {
let val = this.value
return typeof val === 'string' ? val : null
get prelude(): CSSNode | null {
if (this.type !== AT_RULE) return null
let first = this.first_child
if (first && first.type === AT_RULE_PRELUDE) {
return first
}
return null
}

/**
Expand Down Expand Up @@ -734,10 +741,9 @@ export class CSSNode {
if (this.unit) plain.unit = this.unit
}

// 4. Extract prelude for at-rules
if (this.type === AT_RULE && this.prelude) {
plain.prelude = this.prelude
}
// 4. At-rule preludes are now child nodes (AT_RULE_PRELUDE wrapper)
// They will be cloned as part of children in deep clones
// No special extraction needed - breaking change from string to CSSNode

// 5. Extract flags
if (this.type === DECLARATION) {
Expand Down
Loading