@hjmds/design-contracts 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/dist/alert-dialog.d.ts +102 -0
- package/dist/alert-dialog.d.ts.map +1 -0
- package/dist/alert-dialog.js +136 -0
- package/dist/alert-dialog.js.map +1 -0
- package/dist/base-recipes.d.ts +184 -0
- package/dist/base-recipes.d.ts.map +1 -0
- package/dist/base-recipes.js +129 -0
- package/dist/base-recipes.js.map +1 -0
- package/dist/behaviors.d.ts +1254 -0
- package/dist/behaviors.d.ts.map +1 -0
- package/dist/behaviors.js +972 -0
- package/dist/behaviors.js.map +1 -0
- package/dist/bottom-navigation-defaults.d.ts +13 -0
- package/dist/bottom-navigation-defaults.d.ts.map +1 -0
- package/dist/bottom-navigation-defaults.js +10 -0
- package/dist/bottom-navigation-defaults.js.map +1 -0
- package/dist/bottom-navigation.d.ts +114 -0
- package/dist/bottom-navigation.d.ts.map +1 -0
- package/dist/bottom-navigation.js +223 -0
- package/dist/bottom-navigation.js.map +1 -0
- package/dist/breadcrumb.d.ts +97 -0
- package/dist/breadcrumb.d.ts.map +1 -0
- package/dist/breadcrumb.js +99 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/calendar.d.ts +285 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +297 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +39 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +50 -0
- package/dist/card.js.map +1 -0
- package/dist/carousel.d.ts +180 -0
- package/dist/carousel.d.ts.map +1 -0
- package/dist/carousel.js +172 -0
- package/dist/carousel.js.map +1 -0
- package/dist/catalog.d.ts +7158 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +220 -0
- package/dist/catalog.js.map +1 -0
- package/dist/collection.d.ts +123 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +211 -0
- package/dist/collection.js.map +1 -0
- package/dist/color-references.d.ts +46 -0
- package/dist/color-references.d.ts.map +1 -0
- package/dist/color-references.js +39 -0
- package/dist/color-references.js.map +1 -0
- package/dist/colors.d.ts +56 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +90 -0
- package/dist/colors.js.map +1 -0
- package/dist/command-palette.d.ts +240 -0
- package/dist/command-palette.d.ts.map +1 -0
- package/dist/command-palette.js +97 -0
- package/dist/command-palette.js.map +1 -0
- package/dist/component-contracts.d.ts +137 -0
- package/dist/component-contracts.d.ts.map +1 -0
- package/dist/component-contracts.js +53 -0
- package/dist/component-contracts.js.map +1 -0
- package/dist/component-definitions.d.ts +135 -0
- package/dist/component-definitions.d.ts.map +1 -0
- package/dist/component-definitions.js +142 -0
- package/dist/component-definitions.js.map +1 -0
- package/dist/component-recipes.d.ts +3583 -0
- package/dist/component-recipes.d.ts.map +1 -0
- package/dist/component-recipes.js +1503 -0
- package/dist/component-recipes.js.map +1 -0
- package/dist/component-references.d.ts +416 -0
- package/dist/component-references.d.ts.map +1 -0
- package/dist/component-references.js +140 -0
- package/dist/component-references.js.map +1 -0
- package/dist/content-state.d.ts +104 -0
- package/dist/content-state.d.ts.map +1 -0
- package/dist/content-state.js +116 -0
- package/dist/content-state.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +80 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -0
- package/dist/counter-badge-recipe.js +44 -0
- package/dist/counter-badge-recipe.js.map +1 -0
- package/dist/counter-badge.d.ts +12 -0
- package/dist/counter-badge.d.ts.map +1 -0
- package/dist/counter-badge.js +21 -0
- package/dist/counter-badge.js.map +1 -0
- package/dist/data-table.d.ts +203 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +182 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +268 -0
- package/dist/date-picker.d.ts.map +1 -0
- package/dist/date-picker.js +168 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/description-list.d.ts +68 -0
- package/dist/description-list.d.ts.map +1 -0
- package/dist/description-list.js +85 -0
- package/dist/description-list.js.map +1 -0
- package/dist/design-system-provider.d.ts +80 -0
- package/dist/design-system-provider.d.ts.map +1 -0
- package/dist/design-system-provider.js +147 -0
- package/dist/design-system-provider.js.map +1 -0
- package/dist/evidence.d.ts +49 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +133 -0
- package/dist/evidence.js.map +1 -0
- package/dist/file-picker.d.ts +183 -0
- package/dist/file-picker.d.ts.map +1 -0
- package/dist/file-picker.js +224 -0
- package/dist/file-picker.js.map +1 -0
- package/dist/floating-action-button.d.ts +143 -0
- package/dist/floating-action-button.d.ts.map +1 -0
- package/dist/floating-action-button.js +149 -0
- package/dist/floating-action-button.js.map +1 -0
- package/dist/form.d.ts +143 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +206 -0
- package/dist/form.js.map +1 -0
- package/dist/foundations.d.ts +300 -0
- package/dist/foundations.d.ts.map +1 -0
- package/dist/foundations.js +238 -0
- package/dist/foundations.js.map +1 -0
- package/dist/grid.d.ts +75 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +133 -0
- package/dist/grid.js.map +1 -0
- package/dist/icon-button-recipe.d.ts +99 -0
- package/dist/icon-button-recipe.d.ts.map +1 -0
- package/dist/icon-button-recipe.js +53 -0
- package/dist/icon-button-recipe.js.map +1 -0
- package/dist/icon.d.ts +41 -0
- package/dist/icon.d.ts.map +1 -0
- package/dist/icon.js +147 -0
- package/dist/icon.js.map +1 -0
- package/dist/image.d.ts +82 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +100 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +118 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +118 -0
- package/dist/layout.js.map +1 -0
- package/dist/link.d.ts +57 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +142 -0
- package/dist/link.js.map +1 -0
- package/dist/load-more.d.ts +64 -0
- package/dist/load-more.d.ts.map +1 -0
- package/dist/load-more.js +124 -0
- package/dist/load-more.js.map +1 -0
- package/dist/mentions.d.ts +67 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +107 -0
- package/dist/mentions.js.map +1 -0
- package/dist/number-field.d.ts +208 -0
- package/dist/number-field.d.ts.map +1 -0
- package/dist/number-field.js +247 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +152 -0
- package/dist/otp-field.d.ts.map +1 -0
- package/dist/otp-field.js +117 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/pagination.d.ts +181 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +226 -0
- package/dist/pagination.js.map +1 -0
- package/dist/password-field.d.ts +187 -0
- package/dist/password-field.d.ts.map +1 -0
- package/dist/password-field.js +120 -0
- package/dist/password-field.js.map +1 -0
- package/dist/popover.d.ts +152 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +137 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress-recipe.d.ts +43 -0
- package/dist/progress-recipe.d.ts.map +1 -0
- package/dist/progress-recipe.js +16 -0
- package/dist/progress-recipe.js.map +1 -0
- package/dist/recipes.d.ts +31 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +46 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responsive.d.ts +27 -0
- package/dist/responsive.d.ts.map +1 -0
- package/dist/responsive.js +66 -0
- package/dist/responsive.js.map +1 -0
- package/dist/result.d.ts +111 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +97 -0
- package/dist/result.js.map +1 -0
- package/dist/selection-helpers.d.ts +16 -0
- package/dist/selection-helpers.d.ts.map +1 -0
- package/dist/selection-helpers.js +52 -0
- package/dist/selection-helpers.js.map +1 -0
- package/dist/semantic-colors.d.ts +275 -0
- package/dist/semantic-colors.d.ts.map +1 -0
- package/dist/semantic-colors.js +84 -0
- package/dist/semantic-colors.js.map +1 -0
- package/dist/sheet.d.ts +51 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +69 -0
- package/dist/sheet.js.map +1 -0
- package/dist/showcase.d.ts +153 -0
- package/dist/showcase.d.ts.map +1 -0
- package/dist/showcase.js +210 -0
- package/dist/showcase.js.map +1 -0
- package/dist/side-panel.d.ts +199 -0
- package/dist/side-panel.d.ts.map +1 -0
- package/dist/side-panel.js +111 -0
- package/dist/side-panel.js.map +1 -0
- package/dist/slider.d.ts +138 -0
- package/dist/slider.d.ts.map +1 -0
- package/dist/slider.js +150 -0
- package/dist/slider.js.map +1 -0
- package/dist/splitter.d.ts +113 -0
- package/dist/splitter.d.ts.map +1 -0
- package/dist/splitter.js +99 -0
- package/dist/splitter.js.map +1 -0
- package/dist/statistic.d.ts +41 -0
- package/dist/statistic.d.ts.map +1 -0
- package/dist/statistic.js +76 -0
- package/dist/statistic.js.map +1 -0
- package/dist/steps.d.ts +196 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +160 -0
- package/dist/steps.js.map +1 -0
- package/dist/tag.d.ts +128 -0
- package/dist/tag.d.ts.map +1 -0
- package/dist/tag.js +93 -0
- package/dist/tag.js.map +1 -0
- package/dist/timeline.d.ts +147 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +127 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toast.d.ts +164 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +529 -0
- package/dist/toast.js.map +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +9 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tooltip.d.ts +44 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +88 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tour.d.ts +218 -0
- package/dist/tour.d.ts.map +1 -0
- package/dist/tour.js +211 -0
- package/dist/tour.js.map +1 -0
- package/dist/transfer-list.d.ts +207 -0
- package/dist/transfer-list.d.ts.map +1 -0
- package/dist/transfer-list.js +193 -0
- package/dist/transfer-list.js.map +1 -0
- package/dist/tree-select.d.ts +78 -0
- package/dist/tree-select.d.ts.map +1 -0
- package/dist/tree-select.js +132 -0
- package/dist/tree-select.js.map +1 -0
- package/dist/tree.d.ts +206 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +223 -0
- package/dist/tree.js.map +1 -0
- package/dist/upload-item.d.ts +173 -0
- package/dist/upload-item.d.ts.map +1 -0
- package/dist/upload-item.js +156 -0
- package/dist/upload-item.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/docs/affix.md +63 -0
- package/docs/anchor.md +59 -0
- package/docs/ant-design-coverage.md +119 -0
- package/docs/app-provider.md +50 -0
- package/docs/app-rn-adoption.md +227 -0
- package/docs/architecture.md +320 -0
- package/docs/authoring-brief.md +87 -0
- package/docs/border-beam.md +66 -0
- package/docs/bottom-navigation.md +127 -0
- package/docs/breadcrumb.md +82 -0
- package/docs/calendar.md +154 -0
- package/docs/carousel.md +130 -0
- package/docs/cascader.md +93 -0
- package/docs/catalog-decision-status.md +306 -0
- package/docs/color-picker.md +72 -0
- package/docs/command-palette.md +116 -0
- package/docs/confirm-popover.md +93 -0
- package/docs/consistency-audit.md +383 -0
- package/docs/consumer-release-gate.md +90 -0
- package/docs/content-state.md +156 -0
- package/docs/context-panel.md +88 -0
- package/docs/cross-platform-core-normalization.md +118 -0
- package/docs/data-table.md +79 -0
- package/docs/date-picker.md +84 -0
- package/docs/description-list.md +75 -0
- package/docs/design-system-provider.md +139 -0
- package/docs/dropdown.md +78 -0
- package/docs/expansion-roadmap.md +285 -0
- package/docs/file-picker.md +59 -0
- package/docs/floating-action-button.md +111 -0
- package/docs/form.md +134 -0
- package/docs/generated/component-maturity.md +103 -0
- package/docs/generated/renderer-evidence.json +5597 -0
- package/docs/generated/renderer-evidence.md +132 -0
- package/docs/generated/showcase-manifest.json +3732 -0
- package/docs/icon.md +22 -0
- package/docs/identity.md +121 -0
- package/docs/image.md +84 -0
- package/docs/implementation-0.5.md +85 -0
- package/docs/layout-primitives.md +107 -0
- package/docs/layout.md +83 -0
- package/docs/library-reference-decisions.md +123 -0
- package/docs/link.md +67 -0
- package/docs/load-more.md +31 -0
- package/docs/mentions.md +82 -0
- package/docs/migration-0.2.md +108 -0
- package/docs/migration-0.3.md +72 -0
- package/docs/migration-0.5.md +82 -0
- package/docs/migration-0.6.md +197 -0
- package/docs/notification.md +54 -0
- package/docs/number-field.md +82 -0
- package/docs/otp-field.md +103 -0
- package/docs/pagination.md +139 -0
- package/docs/password-field.md +98 -0
- package/docs/popover.md +124 -0
- package/docs/promotion-candidates.md +169 -0
- package/docs/qr-code.md +69 -0
- package/docs/rating.md +58 -0
- package/docs/responsive-grid.md +90 -0
- package/docs/result.md +96 -0
- package/docs/showcase.md +97 -0
- package/docs/side-panel.md +71 -0
- package/docs/slider.md +79 -0
- package/docs/splitter.md +55 -0
- package/docs/statistic.md +16 -0
- package/docs/steps.md +115 -0
- package/docs/tag.md +57 -0
- package/docs/time-picker.md +88 -0
- package/docs/timeline.md +146 -0
- package/docs/toast.md +127 -0
- package/docs/tooltip.md +58 -0
- package/docs/tour.md +136 -0
- package/docs/transfer-list.md +94 -0
- package/docs/tree-select.md +101 -0
- package/docs/tree.md +123 -0
- package/docs/upload-item.md +68 -0
- package/docs/utility.md +53 -0
- package/docs/virtual-list.md +70 -0
- package/docs/watermark.md +58 -0
- package/package.json +402 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
export const collectionValidationDefaults = {
|
|
2
|
+
requireItemLabel: true,
|
|
3
|
+
requireTextValue: true,
|
|
4
|
+
requireSectionName: true,
|
|
5
|
+
};
|
|
6
|
+
function assertNonEmpty(value, name) {
|
|
7
|
+
if (!value.trim())
|
|
8
|
+
throw new TypeError(`${name} must not be empty`);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates renderer-neutral collection identity before focus or selection state
|
|
12
|
+
* is derived. Item IDs are global across sections because selection APIs expose
|
|
13
|
+
* one key namespace.
|
|
14
|
+
*/
|
|
15
|
+
export function validateCollection(source, options = collectionValidationDefaults) {
|
|
16
|
+
const resolved = { ...collectionValidationDefaults, ...options };
|
|
17
|
+
const itemIds = new Set();
|
|
18
|
+
const sectionIds = new Set();
|
|
19
|
+
const sections = source.sections;
|
|
20
|
+
const items = sections
|
|
21
|
+
? sections.flatMap((section) => {
|
|
22
|
+
assertNonEmpty(section.id, "section id");
|
|
23
|
+
if (sectionIds.has(section.id)) {
|
|
24
|
+
throw new TypeError(`duplicate section id: ${section.id}`);
|
|
25
|
+
}
|
|
26
|
+
sectionIds.add(section.id);
|
|
27
|
+
if (resolved.requireSectionName) {
|
|
28
|
+
const name = section.accessibilityLabel?.trim() || section.label;
|
|
29
|
+
if (!name) {
|
|
30
|
+
throw new TypeError(`section ${section.id} needs an accessible name`);
|
|
31
|
+
}
|
|
32
|
+
assertNonEmpty(name, `section ${section.id} name`);
|
|
33
|
+
}
|
|
34
|
+
return section.items;
|
|
35
|
+
})
|
|
36
|
+
: source.items;
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
assertNonEmpty(item.id, "item id");
|
|
39
|
+
if (itemIds.has(item.id)) {
|
|
40
|
+
throw new TypeError(`duplicate item id: ${item.id}`);
|
|
41
|
+
}
|
|
42
|
+
itemIds.add(item.id);
|
|
43
|
+
if (resolved.requireItemLabel) {
|
|
44
|
+
assertNonEmpty(item.label, `item ${item.id} label`);
|
|
45
|
+
}
|
|
46
|
+
if (resolved.requireTextValue) {
|
|
47
|
+
assertNonEmpty(item.textValue, `item ${item.id} textValue`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function flattenCollectionItems(source) {
|
|
52
|
+
return source.sections
|
|
53
|
+
? source.sections.flatMap((section) => section.items)
|
|
54
|
+
: source.items;
|
|
55
|
+
}
|
|
56
|
+
export function resolveCollectionItem(source, key) {
|
|
57
|
+
if (key == null)
|
|
58
|
+
return null;
|
|
59
|
+
return flattenCollectionItems(source).find((item) => item.id === key) ?? null;
|
|
60
|
+
}
|
|
61
|
+
export function getCollectionNavigationIntent(key) {
|
|
62
|
+
if (key === "ArrowDown")
|
|
63
|
+
return "next";
|
|
64
|
+
if (key === "ArrowUp")
|
|
65
|
+
return "previous";
|
|
66
|
+
if (key === "Home")
|
|
67
|
+
return "first";
|
|
68
|
+
if (key === "End")
|
|
69
|
+
return "last";
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
/** Navigation math shared by Menu, Select, Combobox and future listboxes. */
|
|
73
|
+
export function getCollectionNavigationTarget(source, currentKey, intent, loop = true) {
|
|
74
|
+
validateCollection(source);
|
|
75
|
+
const enabled = flattenCollectionItems(source).filter((item) => !item.disabled);
|
|
76
|
+
if (enabled.length === 0)
|
|
77
|
+
return undefined;
|
|
78
|
+
if (intent === "first")
|
|
79
|
+
return enabled[0]?.id;
|
|
80
|
+
if (intent === "last")
|
|
81
|
+
return enabled.at(-1)?.id;
|
|
82
|
+
const currentIndex = enabled.findIndex((item) => item.id === currentKey);
|
|
83
|
+
if (currentIndex < 0) {
|
|
84
|
+
return intent === "previous" ? enabled.at(-1)?.id : enabled[0]?.id;
|
|
85
|
+
}
|
|
86
|
+
const nextIndex = currentIndex + (intent === "next" ? 1 : -1);
|
|
87
|
+
if (!loop && (nextIndex < 0 || nextIndex >= enabled.length)) {
|
|
88
|
+
return enabled[currentIndex]?.id;
|
|
89
|
+
}
|
|
90
|
+
return enabled[(nextIndex + enabled.length) % enabled.length]?.id;
|
|
91
|
+
}
|
|
92
|
+
function collectionTextStartsWith(textValue, query, locale) {
|
|
93
|
+
const candidate = textValue.normalize("NFC").trimStart();
|
|
94
|
+
const normalizedQuery = query.normalize("NFC").trim();
|
|
95
|
+
if (!normalizedQuery)
|
|
96
|
+
return false;
|
|
97
|
+
const collator = new Intl.Collator(locale, {
|
|
98
|
+
usage: "search",
|
|
99
|
+
sensitivity: "base",
|
|
100
|
+
});
|
|
101
|
+
// Collation-equivalent prefixes do not always have the same UTF-16 length
|
|
102
|
+
// (for example German ß and ss), so compare a small prefix window instead
|
|
103
|
+
// of slicing only by query.length.
|
|
104
|
+
const maxPrefixLength = Math.min(candidate.length, normalizedQuery.length + 4);
|
|
105
|
+
for (let length = 1; length <= maxPrefixLength; length += 1) {
|
|
106
|
+
if (collator.compare(candidate.slice(0, length), normalizedQuery) === 0) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
/** Products own buffering; this helper owns matching order and disabled skip. */
|
|
113
|
+
export function getCollectionTypeaheadMatch(source, query, options = {}) {
|
|
114
|
+
validateCollection(source);
|
|
115
|
+
if (!query.trim())
|
|
116
|
+
return undefined;
|
|
117
|
+
const enabled = flattenCollectionItems(source).filter((item) => !item.disabled);
|
|
118
|
+
if (enabled.length === 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
const currentIndex = enabled.findIndex((item) => item.id === options.startsAfterKey);
|
|
121
|
+
for (let offset = 1; offset <= enabled.length; offset += 1) {
|
|
122
|
+
const item = enabled[(currentIndex + offset + enabled.length) % enabled.length];
|
|
123
|
+
if (item &&
|
|
124
|
+
collectionTextStartsWith(item.textValue, query, options.locale)) {
|
|
125
|
+
return item.id;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/** Missing keys clear; disabled-but-still-present selections are retained. */
|
|
131
|
+
export function reconcileSelectSelection(source, selectedKey, options = {}) {
|
|
132
|
+
validateCollection(source);
|
|
133
|
+
if (options.selectedItem) {
|
|
134
|
+
validateCollection({ items: [options.selectedItem] });
|
|
135
|
+
if (selectedKey == null) {
|
|
136
|
+
throw new RangeError("selectedItem requires a selectedKey");
|
|
137
|
+
}
|
|
138
|
+
if (options.selectedItem.id !== selectedKey) {
|
|
139
|
+
throw new RangeError("selectedItem id must match selectedKey");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const items = flattenCollectionItems(source);
|
|
143
|
+
if (selectedKey != null && items.some((item) => item.id === selectedKey)) {
|
|
144
|
+
return selectedKey;
|
|
145
|
+
}
|
|
146
|
+
if (selectedKey != null &&
|
|
147
|
+
(options.asyncState?.status === "loading" ||
|
|
148
|
+
options.asyncState?.status === "loadingMore" ||
|
|
149
|
+
options.asyncState?.status === "error")) {
|
|
150
|
+
return selectedKey;
|
|
151
|
+
}
|
|
152
|
+
if (options.disallowEmptySelection) {
|
|
153
|
+
return items.find((item) => !item.disabled)?.id ?? null;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/** Resolves visible Select copy independently from a transient result page. */
|
|
158
|
+
export function resolveSelectSelectedItem(source, selectedKey, selectedItem) {
|
|
159
|
+
validateCollection(source);
|
|
160
|
+
if (selectedItem)
|
|
161
|
+
validateCollection({ items: [selectedItem] });
|
|
162
|
+
if (selectedKey == null) {
|
|
163
|
+
if (selectedItem) {
|
|
164
|
+
throw new RangeError("selectedItem requires a selectedKey");
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
if (selectedItem && selectedItem.id !== selectedKey) {
|
|
169
|
+
throw new RangeError("selectedItem id must match selectedKey");
|
|
170
|
+
}
|
|
171
|
+
const itemInSource = source.sections
|
|
172
|
+
? source.sections
|
|
173
|
+
.flatMap((section) => section.items)
|
|
174
|
+
.find((item) => item.id === selectedKey)
|
|
175
|
+
: source.items.find((item) => item.id === selectedKey);
|
|
176
|
+
return selectedItem ?? itemInSource ?? null;
|
|
177
|
+
}
|
|
178
|
+
export function isComboboxResultCurrent(queryValue, resultQuery) {
|
|
179
|
+
return queryValue === resultQuery;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* External result pages are not the source of truth for a committed value.
|
|
183
|
+
* A product may supply the committed descriptor while a different query is
|
|
184
|
+
*/
|
|
185
|
+
export function resolveComboboxSelectedItem(source, selectedKey, selectedItem) {
|
|
186
|
+
validateCollection(source);
|
|
187
|
+
if (selectedItem)
|
|
188
|
+
validateCollection({ items: [selectedItem] });
|
|
189
|
+
if (selectedKey == null) {
|
|
190
|
+
if (selectedItem) {
|
|
191
|
+
throw new RangeError("selectedItem requires a selectedKey");
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (selectedItem && selectedItem.id !== selectedKey) {
|
|
196
|
+
throw new RangeError("selectedItem id must match selectedKey");
|
|
197
|
+
}
|
|
198
|
+
// A supplied descriptor is the committed snapshot. Prefer it even if a
|
|
199
|
+
// stale external page happens to contain the same stable key.
|
|
200
|
+
if (selectedItem)
|
|
201
|
+
return selectedItem;
|
|
202
|
+
const itemInResults = source.sections
|
|
203
|
+
? source.sections
|
|
204
|
+
.flatMap((section) => section.items)
|
|
205
|
+
.find((item) => item.id === selectedKey)
|
|
206
|
+
: source.items.find((item) => item.id === selectedKey);
|
|
207
|
+
if (itemInResults)
|
|
208
|
+
return itemInResults;
|
|
209
|
+
throw new RangeError(`Selected combobox item is unavailable: ${selectedKey}`);
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=collection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AsyncCollectionState,\n ComboboxCommitReason,\n ComboboxFiltering,\n ComboboxInput,\n CollectionItemDescriptor,\n CollectionKey,\n CollectionSectionDescriptor,\n SelectItemDescriptor,\n SelectSelection,\n WebKeyboardKey,\n} from \"./behaviors.js\";\n\nexport type CollectionSource<\n Key extends CollectionKey = CollectionKey,\n SectionKey extends CollectionKey = CollectionKey,\n> =\n | Readonly<{ items: readonly CollectionItemDescriptor<Key>[]; sections?: never }>\n | Readonly<{\n items?: never;\n sections: readonly CollectionSectionDescriptor<Key, SectionKey>[];\n }>;\n\nexport type SelectCollectionSectionDescriptor<\n Key extends CollectionKey = CollectionKey,\n SectionKey extends CollectionKey = CollectionKey,\n> = Readonly<{\n id: SectionKey;\n items: readonly SelectItemDescriptor<Key>[];\n}> &\n (\n | Readonly<{ label: string; accessibilityLabel?: string }>\n | Readonly<{ label?: string; accessibilityLabel: string }>\n );\n\n/** Select/Combobox source excludes Menu-only danger tone and shortcut. */\nexport type SelectCollectionSource<\n Key extends CollectionKey = CollectionKey,\n SectionKey extends CollectionKey = CollectionKey,\n> =\n | Readonly<{ items: readonly SelectItemDescriptor<Key>[]; sections?: never }>\n | Readonly<{\n items?: never;\n sections: readonly SelectCollectionSectionDescriptor<Key, SectionKey>[];\n }>;\n\nexport type CollectionValidationOptions = Readonly<{\n requireItemLabel?: boolean;\n requireTextValue?: boolean;\n requireSectionName?: boolean;\n}>;\n\nexport const collectionValidationDefaults = {\n requireItemLabel: true,\n requireTextValue: true,\n requireSectionName: true,\n} as const satisfies Required<CollectionValidationOptions>;\n\nfunction assertNonEmpty(value: string, name: string): void {\n if (!value.trim()) throw new TypeError(`${name} must not be empty`);\n}\n\n/**\n * Validates renderer-neutral collection identity before focus or selection state\n * is derived. Item IDs are global across sections because selection APIs expose\n * one key namespace.\n */\nexport function validateCollection<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: CollectionSource<Key, SectionKey>,\n options: CollectionValidationOptions = collectionValidationDefaults,\n): void {\n const resolved = { ...collectionValidationDefaults, ...options };\n const itemIds = new Set<Key>();\n const sectionIds = new Set<SectionKey>();\n const sections = source.sections;\n const items = sections\n ? sections.flatMap((section) => {\n assertNonEmpty(section.id, \"section id\");\n if (sectionIds.has(section.id)) {\n throw new TypeError(`duplicate section id: ${section.id}`);\n }\n sectionIds.add(section.id);\n if (resolved.requireSectionName) {\n const name = section.accessibilityLabel?.trim() || section.label;\n if (!name) {\n throw new TypeError(`section ${section.id} needs an accessible name`);\n }\n assertNonEmpty(name, `section ${section.id} name`);\n }\n return section.items;\n })\n : source.items;\n\n for (const item of items) {\n assertNonEmpty(item.id, \"item id\");\n if (itemIds.has(item.id)) {\n throw new TypeError(`duplicate item id: ${item.id}`);\n }\n itemIds.add(item.id);\n if (resolved.requireItemLabel) {\n assertNonEmpty(item.label, `item ${item.id} label`);\n }\n if (resolved.requireTextValue) {\n assertNonEmpty(item.textValue, `item ${item.id} textValue`);\n }\n }\n}\n\nexport function flattenCollectionItems<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: CollectionSource<Key, SectionKey>,\n): readonly CollectionItemDescriptor<Key>[] {\n return source.sections\n ? source.sections.flatMap((section) => section.items)\n : source.items;\n}\n\nexport function resolveCollectionItem<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: CollectionSource<Key, SectionKey>,\n key: Key | null | undefined,\n): CollectionItemDescriptor<Key> | null {\n if (key == null) return null;\n return flattenCollectionItems(source).find((item) => item.id === key) ?? null;\n}\n\nexport type ReconcileSelectOptions<\n Key extends CollectionKey = CollectionKey,\n> = Readonly<{\n disallowEmptySelection?: boolean;\n /** Retains a committed key while an async collection is transient. */\n asyncState?: AsyncCollectionState;\n /** Preserves committed copy when the current result page omits the item. */\n selectedItem?: SelectItemDescriptor<Key>;\n}>;\n\nexport type CollectionNavigationIntent =\n | \"next\"\n | \"previous\"\n | \"first\"\n | \"last\";\n\nexport function getCollectionNavigationIntent(\n key: WebKeyboardKey,\n): CollectionNavigationIntent | undefined {\n if (key === \"ArrowDown\") return \"next\";\n if (key === \"ArrowUp\") return \"previous\";\n if (key === \"Home\") return \"first\";\n if (key === \"End\") return \"last\";\n return undefined;\n}\n\n/** Navigation math shared by Menu, Select, Combobox and future listboxes. */\nexport function getCollectionNavigationTarget<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: CollectionSource<Key, SectionKey>,\n currentKey: Key | null | undefined,\n intent: CollectionNavigationIntent,\n loop = true,\n): Key | undefined {\n validateCollection(source);\n const enabled = flattenCollectionItems(source).filter((item) => !item.disabled);\n if (enabled.length === 0) return undefined;\n if (intent === \"first\") return enabled[0]?.id;\n if (intent === \"last\") return enabled.at(-1)?.id;\n const currentIndex = enabled.findIndex((item) => item.id === currentKey);\n if (currentIndex < 0) {\n return intent === \"previous\" ? enabled.at(-1)?.id : enabled[0]?.id;\n }\n const nextIndex = currentIndex + (intent === \"next\" ? 1 : -1);\n if (!loop && (nextIndex < 0 || nextIndex >= enabled.length)) {\n return enabled[currentIndex]?.id;\n }\n return enabled[(nextIndex + enabled.length) % enabled.length]?.id;\n}\n\nexport type CollectionTypeaheadOptions<\n Key extends CollectionKey = CollectionKey,\n> = Readonly<{\n locale?: string | readonly string[];\n startsAfterKey?: Key | null;\n}>;\n\nfunction collectionTextStartsWith(\n textValue: string,\n query: string,\n locale?: string | readonly string[],\n): boolean {\n const candidate = textValue.normalize(\"NFC\").trimStart();\n const normalizedQuery = query.normalize(\"NFC\").trim();\n if (!normalizedQuery) return false;\n const collator = new Intl.Collator(locale, {\n usage: \"search\",\n sensitivity: \"base\",\n });\n // Collation-equivalent prefixes do not always have the same UTF-16 length\n // (for example German ß and ss), so compare a small prefix window instead\n // of slicing only by query.length.\n const maxPrefixLength = Math.min(\n candidate.length,\n normalizedQuery.length + 4,\n );\n for (let length = 1; length <= maxPrefixLength; length += 1) {\n if (collator.compare(candidate.slice(0, length), normalizedQuery) === 0) {\n return true;\n }\n }\n return false;\n}\n\n/** Products own buffering; this helper owns matching order and disabled skip. */\nexport function getCollectionTypeaheadMatch<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: CollectionSource<Key, SectionKey>,\n query: string,\n options: CollectionTypeaheadOptions<Key> = {},\n): Key | undefined {\n validateCollection(source);\n if (!query.trim()) return undefined;\n const enabled = flattenCollectionItems(source).filter((item) => !item.disabled);\n if (enabled.length === 0) return undefined;\n const currentIndex = enabled.findIndex(\n (item) => item.id === options.startsAfterKey,\n );\n for (let offset = 1; offset <= enabled.length; offset += 1) {\n const item = enabled[(currentIndex + offset + enabled.length) % enabled.length];\n if (\n item &&\n collectionTextStartsWith(item.textValue, query, options.locale)\n ) {\n return item.id;\n }\n }\n return undefined;\n}\n\n/** Missing keys clear; disabled-but-still-present selections are retained. */\nexport function reconcileSelectSelection<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: SelectCollectionSource<Key, SectionKey>,\n selectedKey: Key | null,\n options: ReconcileSelectOptions<Key> = {},\n): Key | null {\n validateCollection(source);\n if (options.selectedItem) {\n validateCollection({ items: [options.selectedItem] });\n if (selectedKey == null) {\n throw new RangeError(\"selectedItem requires a selectedKey\");\n }\n if (options.selectedItem.id !== selectedKey) {\n throw new RangeError(\"selectedItem id must match selectedKey\");\n }\n }\n const items = flattenCollectionItems(source);\n if (selectedKey != null && items.some((item) => item.id === selectedKey)) {\n return selectedKey;\n }\n if (\n selectedKey != null &&\n (options.asyncState?.status === \"loading\" ||\n options.asyncState?.status === \"loadingMore\" ||\n options.asyncState?.status === \"error\")\n ) {\n return selectedKey;\n }\n if (options.disallowEmptySelection) {\n return items.find((item) => !item.disabled)?.id ?? null;\n }\n return null;\n}\n\n/** Resolves visible Select copy independently from a transient result page. */\nexport function resolveSelectSelectedItem<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: SelectCollectionSource<Key, SectionKey>,\n selectedKey: Key | null,\n selectedItem?: SelectItemDescriptor<Key>,\n): SelectItemDescriptor<Key> | null {\n validateCollection(source);\n if (selectedItem) validateCollection({ items: [selectedItem] });\n if (selectedKey == null) {\n if (selectedItem) {\n throw new RangeError(\"selectedItem requires a selectedKey\");\n }\n return null;\n }\n if (selectedItem && selectedItem.id !== selectedKey) {\n throw new RangeError(\"selectedItem id must match selectedKey\");\n }\n const itemInSource = source.sections\n ? source.sections\n .flatMap((section) => section.items)\n .find((item) => item.id === selectedKey)\n : source.items.find((item) => item.id === selectedKey);\n return selectedItem ?? itemInSource ?? null;\n}\n\nexport type SelectOpenChangeReason =\n | \"trigger\"\n | \"keyboard\"\n | \"selection\"\n | \"escape\"\n | \"outside\"\n | \"blur\"\n | \"programmatic\";\n\nexport type SelectOpenState =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange(open: boolean, reason: SelectOpenChangeReason): void;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, reason: SelectOpenChangeReason) => void;\n }>;\n\nexport type SelectLabel =\n | Readonly<{ label: string; accessibilityLabel?: string }>\n | Readonly<{ label?: never; accessibilityLabel: string }>;\n\nexport type SelectConfiguration = Readonly<{\n disallowEmptySelection?: boolean;\n loop?: boolean;\n}>;\n\nexport type SelectCollectionState<\n Key extends CollectionKey = CollectionKey,\n> = Readonly<{\n asyncState?: AsyncCollectionState;\n selectedItem?: SelectItemDescriptor<Key>;\n}>;\n\nexport type SelectState<Key extends CollectionKey = CollectionKey> =\n SelectSelection<Key> &\n SelectOpenState &\n SelectLabel &\n SelectConfiguration &\n SelectCollectionState<Key>;\n\nexport type ComboboxCollectionState =\n | Readonly<{\n filtering?: Extract<ComboboxFiltering, \"local\">;\n asyncState?: AsyncCollectionState;\n queryValue?: never;\n resultQuery?: never;\n }>\n | Readonly<{\n filtering: Extract<ComboboxFiltering, \"external\">;\n asyncState: AsyncCollectionState;\n /** Canonical query currently requested by the product search adapter. */\n queryValue: string;\n /** Exact canonical query that produced the supplied external items. */\n resultQuery: string;\n }>;\n\n/**\n * The future adaptive Combobox keeps selection, editable input and popup state\n * independently controlled. External results carry their originating query so\n * a renderer can reject responses that arrived after the input changed.\n */\nexport type ComboboxState<Key extends CollectionKey = CollectionKey> =\n SelectSelection<Key> &\n ComboboxInput &\n SelectOpenState &\n SelectLabel &\n ComboboxCollectionState &\n Readonly<{ loop?: boolean }> &\n Readonly<{\n /** Preserves committed copy when transient external results omit it. */\n selectedItem?: SelectItemDescriptor<Key>;\n onCommit?: (key: Key | null, reason: ComboboxCommitReason) => void;\n }>;\n\nexport function isComboboxResultCurrent(\n queryValue: string,\n resultQuery: string,\n): boolean {\n return queryValue === resultQuery;\n}\n\n/**\n * External result pages are not the source of truth for a committed value.\n * A product may supply the committed descriptor while a different query is\n */\nexport function resolveComboboxSelectedItem<\n Key extends CollectionKey,\n SectionKey extends CollectionKey,\n>(\n source: SelectCollectionSource<Key, SectionKey>,\n selectedKey: Key | null,\n selectedItem?: SelectItemDescriptor<Key>,\n): SelectItemDescriptor<Key> | null {\n validateCollection(source);\n if (selectedItem) validateCollection({ items: [selectedItem] });\n if (selectedKey == null) {\n if (selectedItem) {\n throw new RangeError(\"selectedItem requires a selectedKey\");\n }\n return null;\n }\n if (selectedItem && selectedItem.id !== selectedKey) {\n throw new RangeError(\"selectedItem id must match selectedKey\");\n }\n // A supplied descriptor is the committed snapshot. Prefer it even if a\n // stale external page happens to contain the same stable key.\n if (selectedItem) return selectedItem;\n const itemInResults = source.sections\n ? source.sections\n .flatMap((section) => section.items)\n .find((item) => item.id === selectedKey)\n : source.items.find((item) => item.id === selectedKey);\n if (itemInResults) return itemInResults;\n throw new RangeError(`Selected combobox item is unavailable: ${selectedKey}`);\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type AccentTone, type ThemeColors } from "./colors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Recipes point at semantic colors instead of copying a palette value.
|
|
4
|
+
* Renderers resolve the reference with the active theme and accent map.
|
|
5
|
+
*/
|
|
6
|
+
export type ThemeColorReference = Readonly<{
|
|
7
|
+
source: "theme";
|
|
8
|
+
key: keyof ThemeColors;
|
|
9
|
+
alpha?: number;
|
|
10
|
+
}>;
|
|
11
|
+
export type AccentColorReference = Readonly<{
|
|
12
|
+
source: "accent";
|
|
13
|
+
key: AccentTone;
|
|
14
|
+
alpha?: number;
|
|
15
|
+
}>;
|
|
16
|
+
export type AccentFillReference = Readonly<{
|
|
17
|
+
source: "accentFill";
|
|
18
|
+
key: AccentTone;
|
|
19
|
+
alpha?: number;
|
|
20
|
+
}>;
|
|
21
|
+
export type ColorReference = ThemeColorReference | AccentColorReference | AccentFillReference;
|
|
22
|
+
export type ColorReferencePalette = Readonly<{
|
|
23
|
+
theme: Readonly<ThemeColors>;
|
|
24
|
+
/** Generic feedback roles. Product aliases belong in a separate product map. */
|
|
25
|
+
statusAccents: Readonly<Record<AccentTone, string>>;
|
|
26
|
+
/** Solid fills stay generic too; products may map their vocabulary separately. */
|
|
27
|
+
statusAccentFills: Readonly<Record<AccentTone, string>>;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function themeColor<K extends keyof ThemeColors>(key: K, alpha?: number): Readonly<{
|
|
30
|
+
source: "theme";
|
|
31
|
+
key: K;
|
|
32
|
+
alpha?: number;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function accentColor<K extends AccentTone>(key: K, alpha?: number): Readonly<{
|
|
35
|
+
source: "accent";
|
|
36
|
+
key: K;
|
|
37
|
+
alpha?: number;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function solidAccentColor<K extends AccentTone>(key: K, alpha?: number): Readonly<{
|
|
40
|
+
source: "accentFill";
|
|
41
|
+
key: K;
|
|
42
|
+
alpha?: number;
|
|
43
|
+
}>;
|
|
44
|
+
/** Resolve a recipe color without coupling the recipe to CSS or React Native. */
|
|
45
|
+
export declare function resolveColorReference(reference: ColorReference, palette: ColorReferencePalette): string;
|
|
46
|
+
//# sourceMappingURL=color-references.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-references.d.ts","sourceRoot":"","sources":["../src/color-references.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,MAAM,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,QAAQ,CAAC;IACjB,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,MAAM,MAAM,cAAc,GACtB,mBAAmB,GACnB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC7B,gFAAgF;IAChF,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,kFAAkF;IAClF,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD,CAAC,CAAC;AAQH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,WAAW,EACpD,GAAG,EAAE,CAAC,EACN,KAAK,CAAC,EAAE,MAAM,GACb,QAAQ,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAIvD;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,EAC9C,GAAG,EAAE,CAAC,EACN,KAAK,CAAC,EAAE,MAAM,GACb,QAAQ,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAIxD;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EACnD,GAAG,EAAE,CAAC,EACN,KAAK,CAAC,EAAE,MAAM,GACb,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5D;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,cAAc,EACzB,OAAO,EAAE,qBAAqB,GAC7B,MAAM,CAiBR"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { withAlpha, } from "./colors.js";
|
|
2
|
+
function assertAlpha(alpha) {
|
|
3
|
+
if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
|
4
|
+
throw new RangeError("Color reference alpha must be between 0 and 1");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function themeColor(key, alpha) {
|
|
8
|
+
if (alpha === undefined)
|
|
9
|
+
return { source: "theme", key };
|
|
10
|
+
assertAlpha(alpha);
|
|
11
|
+
return { source: "theme", key, alpha };
|
|
12
|
+
}
|
|
13
|
+
export function accentColor(key, alpha) {
|
|
14
|
+
if (alpha === undefined)
|
|
15
|
+
return { source: "accent", key };
|
|
16
|
+
assertAlpha(alpha);
|
|
17
|
+
return { source: "accent", key, alpha };
|
|
18
|
+
}
|
|
19
|
+
export function solidAccentColor(key, alpha) {
|
|
20
|
+
if (alpha === undefined)
|
|
21
|
+
return { source: "accentFill", key };
|
|
22
|
+
assertAlpha(alpha);
|
|
23
|
+
return { source: "accentFill", key, alpha };
|
|
24
|
+
}
|
|
25
|
+
/** Resolve a recipe color without coupling the recipe to CSS or React Native. */
|
|
26
|
+
export function resolveColorReference(reference, palette) {
|
|
27
|
+
const value = reference.source === "theme"
|
|
28
|
+
? palette.theme[reference.key]
|
|
29
|
+
: reference.source === "accent"
|
|
30
|
+
? palette.statusAccents[reference.key]
|
|
31
|
+
: palette.statusAccentFills[reference.key];
|
|
32
|
+
if (typeof value !== "string") {
|
|
33
|
+
throw new TypeError(`Missing ${reference.source} color for semantic key \"${reference.key}\"`);
|
|
34
|
+
}
|
|
35
|
+
return reference.alpha === undefined || reference.alpha === 1
|
|
36
|
+
? value
|
|
37
|
+
: withAlpha(value, reference.alpha);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=color-references.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-references.js","sourceRoot":"","sources":["../src/color-references.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAGV,MAAM,aAAa,CAAC;AAqCrB,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,UAAU,CAAC,+CAA+C,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,GAAM,EACN,KAAc;IAEd,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IACzD,WAAW,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAM,EACN,KAAc;IAEd,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;IAC1D,WAAW,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,GAAM,EACN,KAAc;IAEd,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;IAC9D,WAAW,CAAC,KAAK,CAAC,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,qBAAqB,CACnC,SAAyB,EACzB,OAA8B;IAE9B,MAAM,KAAK,GACT,SAAS,CAAC,MAAM,KAAK,OAAO;QAC1B,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC;QAC9B,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,QAAQ;YAC7B,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC;YACtC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAEjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,SAAS,CACjB,WAAW,SAAS,CAAC,MAAM,6BAA6B,SAAS,CAAC,GAAG,IAAI,CAC1E,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,KAAK,CAAC;QAC3D,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC","sourcesContent":["import {\n withAlpha,\n type AccentTone,\n type ThemeColors,\n} from \"./colors.js\";\n\n/**\n * Recipes point at semantic colors instead of copying a palette value.\n * Renderers resolve the reference with the active theme and accent map.\n */\nexport type ThemeColorReference = Readonly<{\n source: \"theme\";\n key: keyof ThemeColors;\n alpha?: number;\n}>;\n\nexport type AccentColorReference = Readonly<{\n source: \"accent\";\n key: AccentTone;\n alpha?: number;\n}>;\n\nexport type AccentFillReference = Readonly<{\n source: \"accentFill\";\n key: AccentTone;\n alpha?: number;\n}>;\n\nexport type ColorReference =\n | ThemeColorReference\n | AccentColorReference\n | AccentFillReference;\n\nexport type ColorReferencePalette = Readonly<{\n theme: Readonly<ThemeColors>;\n /** Generic feedback roles. Product aliases belong in a separate product map. */\n statusAccents: Readonly<Record<AccentTone, string>>;\n /** Solid fills stay generic too; products may map their vocabulary separately. */\n statusAccentFills: Readonly<Record<AccentTone, string>>;\n}>;\n\nfunction assertAlpha(alpha: number): void {\n if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {\n throw new RangeError(\"Color reference alpha must be between 0 and 1\");\n }\n}\n\nexport function themeColor<K extends keyof ThemeColors>(\n key: K,\n alpha?: number,\n): Readonly<{ source: \"theme\"; key: K; alpha?: number }> {\n if (alpha === undefined) return { source: \"theme\", key };\n assertAlpha(alpha);\n return { source: \"theme\", key, alpha };\n}\n\nexport function accentColor<K extends AccentTone>(\n key: K,\n alpha?: number,\n): Readonly<{ source: \"accent\"; key: K; alpha?: number }> {\n if (alpha === undefined) return { source: \"accent\", key };\n assertAlpha(alpha);\n return { source: \"accent\", key, alpha };\n}\n\nexport function solidAccentColor<K extends AccentTone>(\n key: K,\n alpha?: number,\n): Readonly<{ source: \"accentFill\"; key: K; alpha?: number }> {\n if (alpha === undefined) return { source: \"accentFill\", key };\n assertAlpha(alpha);\n return { source: \"accentFill\", key, alpha };\n}\n\n/** Resolve a recipe color without coupling the recipe to CSS or React Native. */\nexport function resolveColorReference(\n reference: ColorReference,\n palette: ColorReferencePalette,\n): string {\n const value =\n reference.source === \"theme\"\n ? palette.theme[reference.key]\n : reference.source === \"accent\"\n ? palette.statusAccents[reference.key]\n : palette.statusAccentFills[reference.key];\n\n if (typeof value !== \"string\") {\n throw new TypeError(\n `Missing ${reference.source} color for semantic key \\\"${reference.key}\\\"`,\n );\n }\n\n return reference.alpha === undefined || reference.alpha === 1\n ? value\n : withAlpha(value, reference.alpha);\n}\n"]}
|
package/dist/colors.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** A persisted preference is an application concern; this package only defines valid values. */
|
|
2
|
+
export type ThemePreference = "system" | "light" | "dark";
|
|
3
|
+
export type ResolvedTheme = Exclude<ThemePreference, "system">;
|
|
4
|
+
export declare function isThemePreference(value: unknown): value is ThemePreference;
|
|
5
|
+
/**
|
|
6
|
+
* Product code chooses colors by meaning, never by a raw palette name.
|
|
7
|
+
* Both themes deliberately expose exactly the same keys.
|
|
8
|
+
*/
|
|
9
|
+
export type ThemeColors = {
|
|
10
|
+
bg: string;
|
|
11
|
+
surface: string;
|
|
12
|
+
surfaceAlt: string;
|
|
13
|
+
surfaceAccent: string;
|
|
14
|
+
border: string;
|
|
15
|
+
text: string;
|
|
16
|
+
textBody: string;
|
|
17
|
+
textMuted: string;
|
|
18
|
+
textSub: string;
|
|
19
|
+
textWeak: string;
|
|
20
|
+
primary: string;
|
|
21
|
+
contentBrand: string;
|
|
22
|
+
danger: string;
|
|
23
|
+
onPrimary: string;
|
|
24
|
+
dangerFill: string;
|
|
25
|
+
onDanger: string;
|
|
26
|
+
};
|
|
27
|
+
export declare const THEMES: Readonly<Record<ResolvedTheme, Readonly<ThemeColors>>>;
|
|
28
|
+
/** Generic emphasis roles. Product-specific meanings are mapped by each consumer. */
|
|
29
|
+
export type AccentTone = "info" | "success" | "warning" | "attention";
|
|
30
|
+
export declare const ACCENTS: Readonly<Record<ResolvedTheme, Readonly<Record<AccentTone, string>>>>;
|
|
31
|
+
/** Solid emphasis fills always pair with `onAccentFill`. */
|
|
32
|
+
export declare const accentFill: Readonly<Record<AccentTone, string>>;
|
|
33
|
+
export declare const onAccentFill = "#ffffff";
|
|
34
|
+
export declare const accentTint: {
|
|
35
|
+
readonly weak: 0.1;
|
|
36
|
+
readonly base: 0.15;
|
|
37
|
+
readonly strong: 0.2;
|
|
38
|
+
readonly border: 0.3;
|
|
39
|
+
};
|
|
40
|
+
/** Shared visual signature. Renderers translate the normalized coordinates. */
|
|
41
|
+
export declare const brandGradient: {
|
|
42
|
+
readonly from: "#0369a1";
|
|
43
|
+
readonly to: "#155dfc";
|
|
44
|
+
readonly start: {
|
|
45
|
+
readonly x: 0;
|
|
46
|
+
readonly y: 0;
|
|
47
|
+
};
|
|
48
|
+
readonly end: {
|
|
49
|
+
readonly x: 1;
|
|
50
|
+
readonly y: 1;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export declare const onBrandGradient = "#ffffff";
|
|
54
|
+
/** Add an alpha channel without coupling callers to a CSS or React Native helper. */
|
|
55
|
+
export declare function withAlpha(hex: string, alpha: number): string;
|
|
56
|
+
//# sourceMappingURL=colors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAC1D,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;AAE/D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAE1E;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAwCF,eAAO,MAAM,MAAM,EAAE,QAAQ,CAC3B,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAC3B,CAAC;AAEpB,qFAAqF;AACrF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAEtE,eAAO,MAAM,OAAO,EAAE,QAAQ,CAC5B,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAc5D,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAK3D,CAAC;AAEF,eAAO,MAAM,YAAY,YAAY,CAAC;AAEtC,eAAO,MAAM,UAAU;;;;;CAKb,CAAC;AAEX,+EAA+E;AAC/E,eAAO,MAAM,aAAa;;;;;;;;;;;CAKhB,CAAC;AAEX,eAAO,MAAM,eAAe,YAAY,CAAC;AAEzC,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAc5D"}
|
package/dist/colors.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export function isThemePreference(value) {
|
|
2
|
+
return value === "system" || value === "light" || value === "dark";
|
|
3
|
+
}
|
|
4
|
+
const light = {
|
|
5
|
+
bg: "#ffffff",
|
|
6
|
+
surface: "#f2f4f6",
|
|
7
|
+
surfaceAlt: "#e5e8eb",
|
|
8
|
+
surfaceAccent: "#c9e2ff",
|
|
9
|
+
border: "#e5e8eb",
|
|
10
|
+
text: "#191f28",
|
|
11
|
+
textBody: "#333d4b",
|
|
12
|
+
textMuted: "#4e5968",
|
|
13
|
+
textSub: "#6b7684",
|
|
14
|
+
textWeak: "#8b95a1",
|
|
15
|
+
primary: "#0369a1",
|
|
16
|
+
contentBrand: "#075985",
|
|
17
|
+
danger: "#b71919",
|
|
18
|
+
onPrimary: "#ffffff",
|
|
19
|
+
dangerFill: "#b91c1c",
|
|
20
|
+
onDanger: "#ffffff",
|
|
21
|
+
};
|
|
22
|
+
const dark = {
|
|
23
|
+
bg: "#0b1026",
|
|
24
|
+
surface: "#131a36",
|
|
25
|
+
surfaceAlt: "#1e293b",
|
|
26
|
+
surfaceAccent: "#1e3a5f",
|
|
27
|
+
border: "#1e293b",
|
|
28
|
+
text: "#f1f5f9",
|
|
29
|
+
textBody: "#e2e8f0",
|
|
30
|
+
textMuted: "#cbd5e1",
|
|
31
|
+
textSub: "#94a3b8",
|
|
32
|
+
textWeak: "#64748b",
|
|
33
|
+
primary: "#075985",
|
|
34
|
+
contentBrand: "#38bdf8",
|
|
35
|
+
danger: "#f87171",
|
|
36
|
+
onPrimary: "#ffffff",
|
|
37
|
+
dangerFill: "#b91c1c",
|
|
38
|
+
onDanger: "#ffffff",
|
|
39
|
+
};
|
|
40
|
+
export const THEMES = { light, dark };
|
|
41
|
+
export const ACCENTS = {
|
|
42
|
+
light: {
|
|
43
|
+
info: "#6d28d9",
|
|
44
|
+
success: "#065f46",
|
|
45
|
+
warning: "#92400e",
|
|
46
|
+
attention: "#9a3412",
|
|
47
|
+
},
|
|
48
|
+
dark: {
|
|
49
|
+
info: "#a78bfa",
|
|
50
|
+
success: "#34d399",
|
|
51
|
+
warning: "#fbbf24",
|
|
52
|
+
attention: "#fb923c",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
/** Solid emphasis fills always pair with `onAccentFill`. */
|
|
56
|
+
export const accentFill = {
|
|
57
|
+
info: "#6d28d9",
|
|
58
|
+
success: "#065f46",
|
|
59
|
+
warning: "#92400e",
|
|
60
|
+
attention: "#9a3412",
|
|
61
|
+
};
|
|
62
|
+
export const onAccentFill = "#ffffff";
|
|
63
|
+
export const accentTint = {
|
|
64
|
+
weak: 0.1,
|
|
65
|
+
base: 0.15,
|
|
66
|
+
strong: 0.2,
|
|
67
|
+
border: 0.3,
|
|
68
|
+
};
|
|
69
|
+
/** Shared visual signature. Renderers translate the normalized coordinates. */
|
|
70
|
+
export const brandGradient = {
|
|
71
|
+
from: "#0369a1",
|
|
72
|
+
to: "#155dfc",
|
|
73
|
+
start: { x: 0, y: 0 },
|
|
74
|
+
end: { x: 1, y: 1 },
|
|
75
|
+
};
|
|
76
|
+
export const onBrandGradient = "#ffffff";
|
|
77
|
+
/** Add an alpha channel without coupling callers to a CSS or React Native helper. */
|
|
78
|
+
export function withAlpha(hex, alpha) {
|
|
79
|
+
const match = /^#([0-9a-fA-F]{6})$/.exec(hex);
|
|
80
|
+
if (!match) {
|
|
81
|
+
throw new TypeError("A six-digit hex color is required");
|
|
82
|
+
}
|
|
83
|
+
if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
|
84
|
+
throw new RangeError("Alpha must be a finite number between 0 and 1");
|
|
85
|
+
}
|
|
86
|
+
const source = match[1];
|
|
87
|
+
const channels = [0, 2, 4].map((offset) => Number.parseInt(source.slice(offset, offset + 2), 16));
|
|
88
|
+
return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${alpha})`;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=colors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.js","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"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","sourcesContent":["/** A persisted preference is an application concern; this package only defines valid values. */\nexport type ThemePreference = \"system\" | \"light\" | \"dark\";\nexport type ResolvedTheme = Exclude<ThemePreference, \"system\">;\n\nexport function isThemePreference(value: unknown): value is ThemePreference {\n return value === \"system\" || value === \"light\" || value === \"dark\";\n}\n\n/**\n * Product code chooses colors by meaning, never by a raw palette name.\n * Both themes deliberately expose exactly the same keys.\n */\nexport type ThemeColors = {\n bg: string;\n surface: string;\n surfaceAlt: string;\n surfaceAccent: string;\n border: string;\n text: string;\n textBody: string;\n textMuted: string;\n textSub: string;\n textWeak: string;\n primary: string;\n contentBrand: string;\n danger: string;\n onPrimary: string;\n dangerFill: string;\n onDanger: string;\n};\n\nconst light = {\n bg: \"#ffffff\",\n surface: \"#f2f4f6\",\n surfaceAlt: \"#e5e8eb\",\n surfaceAccent: \"#c9e2ff\",\n border: \"#e5e8eb\",\n text: \"#191f28\",\n textBody: \"#333d4b\",\n textMuted: \"#4e5968\",\n textSub: \"#6b7684\",\n textWeak: \"#8b95a1\",\n primary: \"#0369a1\",\n contentBrand: \"#075985\",\n danger: \"#b71919\",\n onPrimary: \"#ffffff\",\n dangerFill: \"#b91c1c\",\n onDanger: \"#ffffff\",\n} as const satisfies ThemeColors;\n\nconst dark = {\n bg: \"#0b1026\",\n surface: \"#131a36\",\n surfaceAlt: \"#1e293b\",\n surfaceAccent: \"#1e3a5f\",\n border: \"#1e293b\",\n text: \"#f1f5f9\",\n textBody: \"#e2e8f0\",\n textMuted: \"#cbd5e1\",\n textSub: \"#94a3b8\",\n textWeak: \"#64748b\",\n primary: \"#075985\",\n contentBrand: \"#38bdf8\",\n danger: \"#f87171\",\n onPrimary: \"#ffffff\",\n dangerFill: \"#b91c1c\",\n onDanger: \"#ffffff\",\n} as const satisfies ThemeColors;\n\nexport const THEMES: Readonly<\n Record<ResolvedTheme, Readonly<ThemeColors>>\n> = { light, dark };\n\n/** Generic emphasis roles. Product-specific meanings are mapped by each consumer. */\nexport type AccentTone = \"info\" | \"success\" | \"warning\" | \"attention\";\n\nexport const ACCENTS: Readonly<\n Record<ResolvedTheme, Readonly<Record<AccentTone, string>>>\n> = {\n light: {\n info: \"#6d28d9\",\n success: \"#065f46\",\n warning: \"#92400e\",\n attention: \"#9a3412\",\n },\n dark: {\n info: \"#a78bfa\",\n success: \"#34d399\",\n warning: \"#fbbf24\",\n attention: \"#fb923c\",\n },\n};\n\n/** Solid emphasis fills always pair with `onAccentFill`. */\nexport const accentFill: Readonly<Record<AccentTone, string>> = {\n info: \"#6d28d9\",\n success: \"#065f46\",\n warning: \"#92400e\",\n attention: \"#9a3412\",\n};\n\nexport const onAccentFill = \"#ffffff\";\n\nexport const accentTint = {\n weak: 0.1,\n base: 0.15,\n strong: 0.2,\n border: 0.3,\n} as const;\n\n/** Shared visual signature. Renderers translate the normalized coordinates. */\nexport const brandGradient = {\n from: \"#0369a1\",\n to: \"#155dfc\",\n start: { x: 0, y: 0 },\n end: { x: 1, y: 1 },\n} as const;\n\nexport const onBrandGradient = \"#ffffff\";\n\n/** Add an alpha channel without coupling callers to a CSS or React Native helper. */\nexport function withAlpha(hex: string, alpha: number): string {\n const match = /^#([0-9a-fA-F]{6})$/.exec(hex);\n if (!match) {\n throw new TypeError(\"A six-digit hex color is required\");\n }\n if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {\n throw new RangeError(\"Alpha must be a finite number between 0 and 1\");\n }\n\n const source = match[1]!;\n const channels = [0, 2, 4].map((offset) =>\n Number.parseInt(source.slice(offset, offset + 2), 16),\n );\n return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${alpha})`;\n}\n"]}
|