|
| 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