|
1 | 1 | /** |
2 | | - * @typedef {import('css-selector-parser').RuleAttr} RuleAttr |
3 | | - * @typedef {import('css-selector-parser').RulePseudo} RulePseudo |
4 | | - * @typedef {import('css-selector-parser').Rule} Rule |
5 | | - * |
6 | | - * @typedef {import('hast').Element} HastElement |
7 | | - * @typedef {import('hast').Properties} HastProperties |
8 | | - * |
9 | | - * @typedef {'html' | 'svg'} Space |
10 | | - * Name of namespace. |
11 | | - * |
12 | | - * @typedef Options |
13 | | - * Configuration. |
14 | | - * @property {Space} [space] |
15 | | - * Which space first element in the selector is in. |
16 | | - * |
17 | | - * When an `svg` element is created in HTML, the space is automatically |
18 | | - * switched to SVG. |
19 | | - * |
20 | | - * @typedef State |
21 | | - * Info on current context. |
22 | | - * @property {Space} space |
23 | | - * Current space. |
| 2 | + * @typedef {import('./lib/index.js').Space} Space |
| 3 | + * @typedef {import('./lib/index.js').Options} Options |
24 | 4 | */ |
25 | 5 |
|
26 | | -import {h, s} from 'hastscript' |
27 | | -import {CssSelectorParser} from 'css-selector-parser' |
28 | | - |
29 | | -const parser = new CssSelectorParser() |
30 | | - |
31 | | -parser.registerNestingOperators('>', '+', '~') |
32 | | -// Register these so we can throw nicer errors. |
33 | | -parser.registerAttrEqualityMods('~', '|', '^', '$', '*') |
34 | | - |
35 | | -/** |
36 | | - * Turn a selector into a tree. |
37 | | - * |
38 | | - * @param {string | null | undefined} [selector=''] |
39 | | - * CSS selector. |
40 | | - * @param {Space | Options | null | undefined} [space='html'] |
41 | | - * Space or configuration. |
42 | | - * @returns {HastElement} |
43 | | - * Built tree. |
44 | | - */ |
45 | | -export function fromSelector(selector, space) { |
46 | | - /** @type {State} */ |
47 | | - const state = { |
48 | | - space: |
49 | | - (space && typeof space === 'object' && space.space) || |
50 | | - (typeof space === 'string' && space) || |
51 | | - 'html' |
52 | | - } |
53 | | - |
54 | | - const query = parser.parse(selector || '') |
55 | | - |
56 | | - if (query && query.type === 'selectors') { |
57 | | - throw new Error('Cannot handle selector list') |
58 | | - } |
59 | | - |
60 | | - const result = query ? rule(query.rule, state) : [] |
61 | | - |
62 | | - if ( |
63 | | - query && |
64 | | - query.rule.rule && |
65 | | - (query.rule.rule.nestingOperator === '+' || |
66 | | - query.rule.rule.nestingOperator === '~') |
67 | | - ) { |
68 | | - throw new Error( |
69 | | - 'Cannot handle sibling combinator `' + |
70 | | - query.rule.rule.nestingOperator + |
71 | | - '` at root' |
72 | | - ) |
73 | | - } |
74 | | - |
75 | | - return result[0] || build(state.space)('') |
76 | | -} |
77 | | - |
78 | | -/** |
79 | | - * Turn a rule into one or more elements. |
80 | | - * |
81 | | - * @param {Rule} query |
82 | | - * Selector. |
83 | | - * @param {State} state |
84 | | - * Info on current context. |
85 | | - * @returns {Array<HastElement>} |
86 | | - * One or more elements. |
87 | | - */ |
88 | | -function rule(query, state) { |
89 | | - const space = |
90 | | - state.space === 'html' && query.tagName === 'svg' ? 'svg' : state.space |
91 | | - |
92 | | - checkPseudos(query.pseudos || []) |
93 | | - |
94 | | - const node = build(space)(query.tagName === '*' ? '' : query.tagName || '', { |
95 | | - id: query.id, |
96 | | - className: query.classNames, |
97 | | - ...attrsToHast(query.attrs || []) |
98 | | - }) |
99 | | - const results = [node] |
100 | | - |
101 | | - if (query.rule) { |
102 | | - // Sibling. |
103 | | - if ( |
104 | | - query.rule.nestingOperator === '+' || |
105 | | - query.rule.nestingOperator === '~' |
106 | | - ) { |
107 | | - results.push(...rule(query.rule, state)) |
108 | | - } |
109 | | - // Descendant. |
110 | | - else { |
111 | | - node.children.push(...rule(query.rule, {space})) |
112 | | - } |
113 | | - } |
114 | | - |
115 | | - return results |
116 | | -} |
117 | | - |
118 | | -/** |
119 | | - * Check pseudo selectors. |
120 | | - * |
121 | | - * @param {Array<RulePseudo>} pseudos |
122 | | - * Pseudo selectors. |
123 | | - * @returns {void} |
124 | | - * Nothing. |
125 | | - * @throws {Error} |
126 | | - * When a pseudo is defined. |
127 | | - */ |
128 | | -function checkPseudos(pseudos) { |
129 | | - const pseudo = pseudos[0] |
130 | | - |
131 | | - if (pseudo) { |
132 | | - if (pseudo.name) { |
133 | | - throw new Error('Cannot handle pseudo-selector `' + pseudo.name + '`') |
134 | | - } |
135 | | - |
136 | | - throw new Error('Cannot handle pseudo-element or empty pseudo-class') |
137 | | - } |
138 | | -} |
139 | | - |
140 | | -/** |
141 | | - * Turn attribute selectors into properties. |
142 | | - * |
143 | | - * @param {Array<RuleAttr>} attrs |
144 | | - * Attribute selectors. |
145 | | - * @returns {HastProperties} |
146 | | - * Properties. |
147 | | - */ |
148 | | -function attrsToHast(attrs) { |
149 | | - /** @type {HastProperties} */ |
150 | | - const props = {} |
151 | | - let index = -1 |
152 | | - |
153 | | - while (++index < attrs.length) { |
154 | | - const attr = attrs[index] |
155 | | - |
156 | | - if ('operator' in attr) { |
157 | | - if (attr.operator === '=') { |
158 | | - props[attr.name] = attr.value |
159 | | - } else { |
160 | | - throw new Error( |
161 | | - 'Cannot handle attribute equality modifier `' + attr.operator + '`' |
162 | | - ) |
163 | | - } |
164 | | - } else { |
165 | | - props[attr.name] = true |
166 | | - } |
167 | | - } |
168 | | - |
169 | | - return props |
170 | | -} |
171 | | - |
172 | | -/** |
173 | | - * @param {Space} space |
174 | | - * Space. |
175 | | - * @returns {typeof h} |
176 | | - * `h`. |
177 | | - */ |
178 | | -function build(space) { |
179 | | - return space === 'html' ? h : s |
180 | | -} |
| 6 | +export {fromSelector} from './lib/index.js' |
0 commit comments