|
| 1 | +import * as AttributeSelectorParser from './attribute-selector-parser' |
1 | 2 | import {
|
2 | 3 | cloneCandidate,
|
3 | 4 | cloneVariant,
|
@@ -1090,139 +1091,6 @@ function isAttributeSelector(node: SelectorParser.SelectorAstNode): boolean {
|
1090 | 1091 | return node.kind === 'selector' && value[0] === '[' && value[value.length - 1] === ']'
|
1091 | 1092 | }
|
1092 | 1093 |
|
1093 |
| -function isAsciiWhitespace(char: string) { |
1094 |
| - return char === ' ' || char === '\t' || char === '\n' || char === '\r' || char === '\f' |
1095 |
| -} |
1096 |
| - |
1097 |
| -enum AttributePart { |
1098 |
| - Start, |
1099 |
| - Attribute, |
1100 |
| - Value, |
1101 |
| - Modifier, |
1102 |
| - End, |
1103 |
| -} |
1104 |
| - |
1105 |
| -function parseAttributeSelector(value: string) { |
1106 |
| - let attribute = { |
1107 |
| - key: '', |
1108 |
| - operator: null as '=' | '~=' | '|=' | '^=' | '$=' | '*=' | null, |
1109 |
| - quote: '', |
1110 |
| - value: null as string | null, |
1111 |
| - modifier: null as 'i' | 's' | null, |
1112 |
| - } |
1113 |
| - |
1114 |
| - let state = AttributePart.Start |
1115 |
| - outer: for (let i = 0; i < value.length; i++) { |
1116 |
| - // Skip whitespace |
1117 |
| - if (isAsciiWhitespace(value[i])) { |
1118 |
| - if (attribute.quote === '' && state !== AttributePart.Value) { |
1119 |
| - continue |
1120 |
| - } |
1121 |
| - } |
1122 |
| - |
1123 |
| - switch (state) { |
1124 |
| - case AttributePart.Start: { |
1125 |
| - if (value[i] === '[') { |
1126 |
| - state = AttributePart.Attribute |
1127 |
| - } else { |
1128 |
| - return null |
1129 |
| - } |
1130 |
| - break |
1131 |
| - } |
1132 |
| - |
1133 |
| - case AttributePart.Attribute: { |
1134 |
| - switch (value[i]) { |
1135 |
| - case ']': { |
1136 |
| - return attribute |
1137 |
| - } |
1138 |
| - |
1139 |
| - case '=': { |
1140 |
| - attribute.operator = '=' |
1141 |
| - state = AttributePart.Value |
1142 |
| - continue outer |
1143 |
| - } |
1144 |
| - |
1145 |
| - case '~': |
1146 |
| - case '|': |
1147 |
| - case '^': |
1148 |
| - case '$': |
1149 |
| - case '*': { |
1150 |
| - if (value[i + 1] === '=') { |
1151 |
| - attribute.operator = (value[i] + '=') as '=' | '~=' | '|=' | '^=' | '$=' | '*=' |
1152 |
| - i++ |
1153 |
| - state = AttributePart.Value |
1154 |
| - continue outer |
1155 |
| - } |
1156 |
| - |
1157 |
| - return null |
1158 |
| - } |
1159 |
| - } |
1160 |
| - |
1161 |
| - attribute.key += value[i] |
1162 |
| - break |
1163 |
| - } |
1164 |
| - |
1165 |
| - case AttributePart.Value: { |
1166 |
| - // End of attribute selector |
1167 |
| - if (value[i] === ']') { |
1168 |
| - return attribute |
1169 |
| - } |
1170 |
| - |
1171 |
| - // Quoted value |
1172 |
| - else if (value[i] === "'" || value[i] === '"') { |
1173 |
| - attribute.value ??= '' |
1174 |
| - |
1175 |
| - attribute.quote = value[i] |
1176 |
| - |
1177 |
| - for (let j = i + 1; j < value.length; j++) { |
1178 |
| - if (value[j] === '\\' && j + 1 < value.length) { |
1179 |
| - // Skip the escaped character |
1180 |
| - j++ |
1181 |
| - attribute.value += value[j] |
1182 |
| - } else if (value[j] === attribute.quote) { |
1183 |
| - i = j |
1184 |
| - state = AttributePart.Modifier |
1185 |
| - continue outer |
1186 |
| - } else { |
1187 |
| - attribute.value += value[j] |
1188 |
| - } |
1189 |
| - } |
1190 |
| - } |
1191 |
| - |
1192 |
| - // Unquoted value |
1193 |
| - else { |
1194 |
| - if (isAsciiWhitespace(value[i])) { |
1195 |
| - state = AttributePart.Modifier |
1196 |
| - } else { |
1197 |
| - attribute.value ??= '' |
1198 |
| - attribute.value += value[i] |
1199 |
| - } |
1200 |
| - } |
1201 |
| - break |
1202 |
| - } |
1203 |
| - |
1204 |
| - case AttributePart.Modifier: { |
1205 |
| - if (value[i] === 'i' || value[i] === 's') { |
1206 |
| - attribute.modifier = value[i] as 'i' | 's' |
1207 |
| - state = AttributePart.End |
1208 |
| - } else if (value[i] == ']') { |
1209 |
| - return attribute |
1210 |
| - } |
1211 |
| - break |
1212 |
| - } |
1213 |
| - |
1214 |
| - case AttributePart.End: { |
1215 |
| - if (value[i] === ']') { |
1216 |
| - return attribute |
1217 |
| - } |
1218 |
| - break |
1219 |
| - } |
1220 |
| - } |
1221 |
| - } |
1222 |
| - |
1223 |
| - return attribute |
1224 |
| -} |
1225 |
| - |
1226 | 1094 | function modernizeArbitraryValuesVariant(
|
1227 | 1095 | designSystem: DesignSystem,
|
1228 | 1096 | variant: Variant,
|
@@ -1521,44 +1389,44 @@ function modernizeArbitraryValuesVariant(
|
1521 | 1389 |
|
1522 | 1390 | // Expecting an attribute selector
|
1523 | 1391 | else if (isAttributeSelector(target)) {
|
1524 |
| - let attribute = parseAttributeSelector(target.value) |
1525 |
| - if (attribute === null) continue // Invalid attribute selector |
| 1392 | + let attributeSelector = AttributeSelectorParser.parse(target.value) |
| 1393 | + if (attributeSelector === null) continue // Invalid attribute selector |
1526 | 1394 |
|
1527 | 1395 | // Migrate `data-*`
|
1528 |
| - if (attribute.key.startsWith('data-')) { |
1529 |
| - let name = attribute.key.slice(5) // Remove `data-` |
| 1396 | + if (attributeSelector.attribute.startsWith('data-')) { |
| 1397 | + let name = attributeSelector.attribute.slice(5) // Remove `data-` |
1530 | 1398 |
|
1531 | 1399 | replaceObject(variant, {
|
1532 | 1400 | kind: 'functional',
|
1533 | 1401 | root: 'data',
|
1534 | 1402 | modifier: null,
|
1535 | 1403 | value:
|
1536 |
| - attribute.value === null |
| 1404 | + attributeSelector.value === null |
1537 | 1405 | ? { kind: 'named', value: name }
|
1538 | 1406 | : {
|
1539 | 1407 | kind: 'arbitrary',
|
1540 |
| - value: `${name}${attribute.operator}${attribute.quote}${attribute.value}${attribute.quote}${attribute.modifier ? ` ${attribute.modifier}` : ''}`, |
| 1408 | + value: `${name}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`, |
1541 | 1409 | },
|
1542 | 1410 | } satisfies Variant)
|
1543 | 1411 | }
|
1544 | 1412 |
|
1545 | 1413 | // Migrate `aria-*`
|
1546 |
| - else if (attribute.key.startsWith('aria-')) { |
1547 |
| - let name = attribute.key.slice(5) // Remove `aria-` |
| 1414 | + else if (attributeSelector.attribute.startsWith('aria-')) { |
| 1415 | + let name = attributeSelector.attribute.slice(5) // Remove `aria-` |
1548 | 1416 | replaceObject(variant, {
|
1549 | 1417 | kind: 'functional',
|
1550 | 1418 | root: 'aria',
|
1551 | 1419 | modifier: null,
|
1552 | 1420 | value:
|
1553 |
| - attribute.value === null |
| 1421 | + attributeSelector.value === null |
1554 | 1422 | ? { kind: 'arbitrary', value: name } // aria-[foo]
|
1555 |
| - : attribute.operator === '=' && |
1556 |
| - attribute.value === 'true' && |
1557 |
| - attribute.modifier === null |
| 1423 | + : attributeSelector.operator === '=' && |
| 1424 | + attributeSelector.value === 'true' && |
| 1425 | + attributeSelector.sensitivity === null |
1558 | 1426 | ? { kind: 'named', value: name } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true]
|
1559 | 1427 | : {
|
1560 | 1428 | kind: 'arbitrary',
|
1561 |
| - value: `${attribute.key}${attribute.operator}${attribute.quote}${attribute.value}${attribute.quote}${attribute.modifier ? ` ${attribute.modifier}` : ''}`, |
| 1429 | + value: `${attributeSelector.attribute}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`, |
1562 | 1430 | }, // aria-[foo~="true"], aria-[foo|="true"], …
|
1563 | 1431 | } satisfies Variant)
|
1564 | 1432 | }
|
|
0 commit comments