* Register a functional utility class like `max-w-*` that maps to tokens in the * user's theme.
(classRoot: string, desc: UtilityDescription)
| 393 | * user's theme. |
| 394 | */ |
| 395 | function functionalUtility(classRoot: string, desc: UtilityDescription) { |
| 396 | if (desc.staticValues) desc.staticValues = Object.assign(Object.create(null), desc.staticValues) |
| 397 | |
| 398 | function handleFunctionalUtility({ negative }: { negative: boolean }) { |
| 399 | return (candidate: Extract<Candidate, { kind: 'functional' }>) => { |
| 400 | let value: string | null = null |
| 401 | let dataType: string | null = null |
| 402 | |
| 403 | if (!candidate.value) { |
| 404 | if (candidate.modifier) return |
| 405 | |
| 406 | // If the candidate has no value segment (like `rounded`), use the |
| 407 | // `defaultValue` (for candidates like `grow` that have no theme |
| 408 | // values) or a bare theme value (like `--radius` for `rounded`). No |
| 409 | // utility will ever support both of these. |
| 410 | value = |
| 411 | desc.defaultValue !== undefined |
| 412 | ? desc.defaultValue |
| 413 | : theme.resolve(null, desc.themeKeys ?? []) |
| 414 | } else if (candidate.value.kind === 'arbitrary') { |
| 415 | if (candidate.modifier) return |
| 416 | value = candidate.value.value |
| 417 | dataType = candidate.value.dataType |
| 418 | } else { |
| 419 | value = theme.resolve( |
| 420 | candidate.value.fraction ?? candidate.value.value, |
| 421 | desc.themeKeys ?? [], |
| 422 | ) |
| 423 | |
| 424 | // Automatically handle things like `w-1/2` without requiring `1/2` to |
| 425 | // exist as a theme value. |
| 426 | if (value === null && desc.supportsFractions && candidate.value.fraction) { |
| 427 | let [lhs, rhs] = segment(candidate.value.fraction, '/') |
| 428 | if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return |
| 429 | value = `calc(${lhs} / ${rhs} * 100%)` |
| 430 | } |
| 431 | |
| 432 | // If there is still no value but the utility supports bare values, |
| 433 | // then use the bare candidate value as the value. |
| 434 | if (value === null && negative && desc.handleNegativeBareValue) { |
| 435 | value = desc.handleNegativeBareValue(candidate.value) |
| 436 | if (!value?.includes('/') && candidate.modifier) return |
| 437 | if (value !== null) return desc.handle(value, null) |
| 438 | } |
| 439 | |
| 440 | if (value === null && desc.handleBareValue) { |
| 441 | value = desc.handleBareValue(candidate.value) |
| 442 | if (!value?.includes('/') && candidate.modifier) return |
| 443 | } |
| 444 | |
| 445 | if (value === null && !negative && desc.staticValues && !candidate.modifier) { |
| 446 | let fallback = desc.staticValues[candidate.value.value] |
| 447 | if (fallback) return fallback.map(cloneAstNode) |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | // If there is no value, don't generate any rules. |
| 452 | if (value === null) return |
no test coverage detected