Skip to content

Commit 9990be8

Browse files
authored
feat: add value_as_number to css-node (#53)
1 parent 4eb87b5 commit 9990be8

File tree

3 files changed

+120
-151
lines changed

3 files changed

+120
-151
lines changed

src/css-node.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,20 +229,29 @@ export class CSSNode {
229229
// For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
230230
// For URL nodes with unquoted URL: returns the URL content without quotes
231231
get value(): string | number | null {
232+
let { type, text } = this
233+
234+
if (type === DIMENSION) {
235+
return parse_dimension(text).value
236+
}
237+
238+
if (type === NUMBER) {
239+
return Number.parseFloat(this.text)
240+
}
241+
232242
// Special handling for URL nodes
233-
if (this.type === URL) {
234-
const firstChild = this.first_child
243+
if (type === URL) {
244+
let firstChild = this.first_child
235245
if (firstChild && firstChild.type === STRING) {
236246
// Return the string as-is (with quotes) - consistent with STRING node
237247
return firstChild.text
238248
}
239249
// For URL nodes without children (e.g., @import url(...)), extract from text
240250
// Handle both url("...") and url('...') and just "..." or '...'
241-
const text = this.text
242251
if (str_starts_with(text, 'url(')) {
243252
// url("...") or url('...') or url(...) - extract content between parens
244-
const openParen = text.indexOf('(')
245-
const closeParen = text.lastIndexOf(')')
253+
let openParen = text.indexOf('(')
254+
let closeParen = text.lastIndexOf(')')
246255
if (openParen !== -1 && closeParen !== -1 && closeParen > openParen) {
247256
let content = text.substring(openParen + 1, closeParen).trim()
248257
return content
@@ -254,18 +263,24 @@ export class CSSNode {
254263
// For unquoted URLs, fall through to value delta logic below
255264
}
256265

257-
// For dimension and number nodes, parse and return as number
258-
if (this.type === DIMENSION || this.type === NUMBER) {
259-
return parse_dimension(this.text).value
260-
}
261-
262266
// For other nodes, return as string
263267
let start = this.arena.get_value_start(this.index)
264268
let length = this.arena.get_value_length(this.index)
265269
if (length === 0) return null
266270
return this.source.substring(start, start + length)
267271
}
268272

273+
get value_as_number(): number | null {
274+
let text = this.text
275+
if (this.type === NUMBER) {
276+
return Number.parseFloat(text)
277+
}
278+
if (this.type === DIMENSION) {
279+
return parse_dimension(text).value
280+
}
281+
return null
282+
}
283+
269284
// Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
270285
// This is an alias for `value` to make at-rule usage more semantic
271286
get prelude(): string | null {

src/parse-value.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,90 @@ describe('Value Node Types', () => {
660660
})
661661
})
662662

663+
describe('value_as_number getter', () => {
664+
it('should return number for NUMBER nodes', () => {
665+
const root = parse('div { opacity: 0.5; }')
666+
const decl = root.first_child?.first_child?.next_sibling?.first_child
667+
const numberNode = decl?.values[0]
668+
669+
expect(numberNode?.type).toBe(NUMBER)
670+
expect(numberNode?.value_as_number).toBe(0.5)
671+
})
672+
673+
it('should return number for DIMENSION nodes', () => {
674+
const root = parse('div { width: 100px; }')
675+
const decl = root.first_child?.first_child?.next_sibling?.first_child
676+
const dimNode = decl?.values[0]
677+
678+
expect(dimNode?.type).toBe(DIMENSION)
679+
expect(dimNode?.value_as_number).toBe(100)
680+
})
681+
682+
it('should handle negative numbers', () => {
683+
const root = parse('div { margin: -10px; }')
684+
const decl = root.first_child?.first_child?.next_sibling?.first_child
685+
const dimNode = decl?.values[0]
686+
687+
expect(dimNode?.type).toBe(DIMENSION)
688+
expect(dimNode?.value_as_number).toBe(-10)
689+
})
690+
691+
it('should handle zero', () => {
692+
const root = parse('div { margin: 0; }')
693+
const decl = root.first_child?.first_child?.next_sibling?.first_child
694+
const numberNode = decl?.values[0]
695+
696+
expect(numberNode?.type).toBe(NUMBER)
697+
expect(numberNode?.value_as_number).toBe(0)
698+
})
699+
700+
it('should handle decimal numbers', () => {
701+
const root = parse('div { line-height: 1.5; }')
702+
const decl = root.first_child?.first_child?.next_sibling?.first_child
703+
const numberNode = decl?.values[0]
704+
705+
expect(numberNode?.type).toBe(NUMBER)
706+
expect(numberNode?.value_as_number).toBe(1.5)
707+
})
708+
709+
it('should handle percentage dimensions', () => {
710+
const root = parse('div { width: 50%; }')
711+
const decl = root.first_child?.first_child?.next_sibling?.first_child
712+
const dimNode = decl?.values[0]
713+
714+
expect(dimNode?.type).toBe(DIMENSION)
715+
expect(dimNode?.value_as_number).toBe(50)
716+
expect(dimNode?.unit).toBe('%')
717+
})
718+
719+
it('should return null for IDENTIFIER nodes', () => {
720+
const root = parse('div { color: red; }')
721+
const decl = root.first_child?.first_child?.next_sibling?.first_child
722+
const identNode = decl?.values[0]
723+
724+
expect(identNode?.type).toBe(IDENTIFIER)
725+
expect(identNode?.value_as_number).toBeNull()
726+
})
727+
728+
it('should return null for STRING nodes', () => {
729+
const root = parse('div { content: "hello"; }')
730+
const decl = root.first_child?.first_child?.next_sibling?.first_child
731+
const stringNode = decl?.values[0]
732+
733+
expect(stringNode?.type).toBe(STRING)
734+
expect(stringNode?.value_as_number).toBeNull()
735+
})
736+
737+
it('should return null for FUNCTION nodes', () => {
738+
const root = parse('div { width: calc(100% - 20px); }')
739+
const decl = root.first_child?.first_child?.next_sibling?.first_child
740+
const funcNode = decl?.values[0]
741+
742+
expect(funcNode?.type).toBe(FUNCTION)
743+
expect(funcNode?.value_as_number).toBeNull()
744+
})
745+
})
746+
663747
describe('Case-insensitive function names', () => {
664748
const getValue = (css: string) => {
665749
const root = parse(css)

0 commit comments

Comments
 (0)