@hjmds/design-contracts 1.47.0 → 1.49.0
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/dist/card.d.ts +5 -0
- package/dist/card.d.ts.map +1 -1
- package/dist/card.js +3 -0
- package/dist/card.js.map +1 -1
- package/dist/catalog.d.ts +56 -18
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +15 -11
- package/dist/catalog.js.map +1 -1
- package/dist/component-recipes.d.ts +5 -1
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +4 -2
- package/dist/component-recipes.js.map +1 -1
- package/dist/data-table.d.ts +12 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +2 -0
- package/dist/data-table.js.map +1 -1
- package/dist/drawing-editor.d.ts +96 -0
- package/dist/drawing-editor.d.ts.map +1 -0
- package/dist/drawing-editor.js +43 -0
- package/dist/drawing-editor.js.map +1 -0
- package/dist/share-sheet.d.ts +115 -0
- package/dist/share-sheet.d.ts.map +1 -0
- package/dist/share-sheet.js +62 -0
- package/dist/share-sheet.js.map +1 -0
- package/dist/timeline.d.ts +7 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +7 -0
- package/dist/timeline.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/dialog.md +2 -3
- package/docs/drawing-editor.md +14 -0
- package/docs/generated/component-maturity.md +13 -12
- package/docs/generated/renderer-evidence.json +122 -22
- package/docs/generated/renderer-evidence.md +18 -16
- package/docs/generated/showcase-manifest.json +70 -19
- package/docs/optional-adapters.md +21 -5
- package/docs/stable-core.md +37 -0
- package/docs/timeline.md +13 -0
- package/docs/usage/README.md +13 -11
- package/docs/usage/components/badge.md +10 -1
- package/docs/usage/components/card.md +1 -0
- package/docs/usage/components/data-table.md +2 -0
- package/docs/usage/components/dialog.md +3 -3
- package/docs/usage/components/drawing-editor.md +133 -0
- package/docs/usage/components/follow-list-screen.md +3 -3
- package/docs/usage/components/image-choice-group.md +1 -1
- package/docs/usage/components/share-sheet.md +139 -0
- package/docs/usage/components/shared-transition-element.md +1 -1
- package/docs/usage/components/sheet.md +13 -1
- package/docs/usage/components/timeline.md +27 -3
- package/docs/usage/compositions/interaction-adapters.md +2 -2
- package/docs/usage/compositions/optional-adapters.md +2 -2
- package/docs/usage/screens/common-follow-list.md +3 -3
- package/package.json +12 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { validateImageChoiceItems } from "./image-choice.js";
|
|
2
|
+
export const shareSheetRecipe = {
|
|
3
|
+
/** The picker scrolls in a tall sheet; external actions alone keep the sheet at its content height. */
|
|
4
|
+
size: { recipients: "large", externalOnly: "auto" },
|
|
5
|
+
/** Body regions (notices, search, list) and footer rows (send button above the action row). */
|
|
6
|
+
bodyGap: "md",
|
|
7
|
+
footerGap: "xs",
|
|
8
|
+
/** Two round external actions centred in one row, each with its caption below (BurnTok 1.28 share footer). */
|
|
9
|
+
actionGap: "xxxl",
|
|
10
|
+
actionCaptionGap: "xs",
|
|
11
|
+
actionButton: { tone: "secondary", size: "large" },
|
|
12
|
+
actionCaptionVariant: "caption",
|
|
13
|
+
sendButton: { tone: "primary", size: "large" },
|
|
14
|
+
/**
|
|
15
|
+
* Recipients are picked by face and name. Two name lines: a 12-character nickname ends near six Hangul glyphs on
|
|
16
|
+
* one 80-wide caption line (BurnTok share recipients, 2026-10-18).
|
|
17
|
+
*/
|
|
18
|
+
picker: { showItemLabels: true, itemLabelLines: 2 },
|
|
19
|
+
};
|
|
20
|
+
export function validateShareSheet(input) {
|
|
21
|
+
if (!input.title.trim())
|
|
22
|
+
throw new TypeError("ShareSheet title must not be empty");
|
|
23
|
+
if (!input.link.trim())
|
|
24
|
+
throw new TypeError("ShareSheet link must not be empty");
|
|
25
|
+
for (const [key, value] of Object.entries(input.labels)) {
|
|
26
|
+
if (typeof value !== "string" || !value.trim())
|
|
27
|
+
throw new TypeError(`ShareSheet labels.${key} must be localized text`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Recipients the sheet shows for a search: every item for a blank query, else label matches (locale lower case). */
|
|
31
|
+
export function filterShareSheetRecipients(items, query) {
|
|
32
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
33
|
+
if (!needle)
|
|
34
|
+
return items;
|
|
35
|
+
return items.filter(item => item.label.toLocaleLowerCase().includes(needle));
|
|
36
|
+
}
|
|
37
|
+
/** Validates the full recipient list (unique ids, localized labels) the same way the picker does. */
|
|
38
|
+
export function validateShareSheetRecipients(items) {
|
|
39
|
+
if (items.length)
|
|
40
|
+
validateImageChoiceItems(items);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Splits one send attempt into delivered and failed ids. A product reporting an id it was not asked to send is a bug
|
|
44
|
+
* (it would mark someone as sent who never was), so it throws instead of being ignored.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveShareSheetSendOutcome(requested, result) {
|
|
47
|
+
const asked = new Set(requested);
|
|
48
|
+
for (const id of result.sentIds) {
|
|
49
|
+
if (!asked.has(id))
|
|
50
|
+
throw new TypeError(`ShareSheet onSend reported "${id}", which was not requested`);
|
|
51
|
+
}
|
|
52
|
+
const delivered = new Set(result.sentIds);
|
|
53
|
+
return { sent: requested.filter(id => delivered.has(id)), failed: requested.filter(id => !delivered.has(id)) };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The picker value: the user's selection plus every already-sent recipient, which stays checked and locked so it
|
|
57
|
+
* cannot be sent twice.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveShareSheetPickerValue(selected, sent, visibleIds) {
|
|
60
|
+
return [...selected.filter(id => !sent.has(id)), ...visibleIds.filter(id => sent.has(id))];
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=share-sheet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share-sheet.js","sourceRoot":"","sources":["../src/share-sheet.ts"],"names":[],"mappings":"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","sourcesContent":["import { validateImageChoiceItems, type ImageChoiceItemDescriptor, type ImageChoiceItemLabelLines } from \"./image-choice.js\";\nimport type { sheetRecipe, StackGap } from \"./component-recipes.js\";\nimport type { TextVariant } from \"./foundations.js\";\n\ntype SheetSize = keyof typeof sheetRecipe.sizes;\n\n/**\n * Sharing one link (2026-10-26 user request \"번뚝 공유 그대로 공통화\"): BurnTok's Web and Native share sheets are the\n * reference. One Sheet offers in-app recipients (optional) plus two external actions — copy link and the OS share\n * sheet. The product owns the link, the recipient data and the delivery; HJM owns the layout, selection, in-flight\n * guards and the honest feedback (the OS share sheet only proves it opened, never that anything was delivered).\n */\nexport type ShareSheetRecipientDescriptor = ImageChoiceItemDescriptor;\n\n/** Where the recipient list is: the product fetches it, the sheet shows the matching state. */\nexport type ShareSheetRecipientStatus = \"ready\" | \"loading\" | \"error\";\n\n/**\n * What `onSend` reports back. `sentIds` are the requested ids the product delivered; every other requested id stays\n * selected so the user can retry only those (a partial batch must not resend the delivered ones). `error` is the\n * product's localized reason for the failures; without it the sheet shows `labels.sendFailed`.\n */\nexport type ShareSheetSendResult = Readonly<{ sentIds: readonly string[]; error?: string }>;\n\n/** Payload for the OS share sheet. */\nexport type ShareSheetShareContent = Readonly<{ url: string; title?: string; text?: string }>;\n\n/**\n * How the OS share sheet closed. `completed` means the user picked a target, not that it was delivered; `dismissed`\n * means they backed out (Web `AbortError`, iOS `dismissedAction`). Android always reports `completed`.\n */\nexport type ShareSheetSystemShareOutcome = \"completed\" | \"dismissed\";\n\n/** Localized copy for the sheet chrome and the external actions. The renderer invents none. */\nexport type ShareSheetLabels = Readonly<{\n /** Accessible name of the Sheet close action. */\n close: string;\n copyLink: string;\n systemShare: string;\n /** Announced (polite) after the link reached the clipboard. */\n linkCopied: string;\n /** Shown when copying or opening the OS share sheet failed. */\n actionFailed: string;\n}>;\n\n/** Localized copy for the recipient section. */\nexport type ShareSheetRecipientLabels = Readonly<{\n /** Accessible name of the recipient group (e.g. \"Choose who to send to\"). */\n group: string;\n /** Search field name and placeholder. */\n search: string;\n clearSearch: string;\n loading: string;\n loadFailed: string;\n retry: string;\n /** No recipients at all. */\n empty: string;\n /** Recipients exist but none match the search. */\n noResults: string;\n /** Item description of an already-sent recipient. */\n sent: string;\n /** Send failure without a product reason. */\n sendFailed: string;\n /** Footer action, e.g. `count => \\`Send to ${count}\\``. */\n send: (count: number) => string;\n}>;\n\nexport const shareSheetRecipe = {\n /** The picker scrolls in a tall sheet; external actions alone keep the sheet at its content height. */\n size: { recipients: \"large\", externalOnly: \"auto\" } satisfies Readonly<Record<string, SheetSize>>,\n /** Body regions (notices, search, list) and footer rows (send button above the action row). */\n bodyGap: \"md\" satisfies StackGap,\n footerGap: \"xs\" satisfies StackGap,\n /** Two round external actions centred in one row, each with its caption below (BurnTok 1.28 share footer). */\n actionGap: \"xxxl\" satisfies StackGap,\n actionCaptionGap: \"xs\" satisfies StackGap,\n actionButton: { tone: \"secondary\", size: \"large\" },\n actionCaptionVariant: \"caption\" satisfies TextVariant,\n sendButton: { tone: \"primary\", size: \"large\" },\n /**\n * Recipients are picked by face and name. Two name lines: a 12-character nickname ends near six Hangul glyphs on\n * one 80-wide caption line (BurnTok share recipients, 2026-10-18).\n */\n picker: { showItemLabels: true, itemLabelLines: 2 satisfies ImageChoiceItemLabelLines },\n} as const;\n\nexport function validateShareSheet(input: Readonly<{ title: string; link: string; labels: ShareSheetLabels }>): void {\n if (!input.title.trim()) throw new TypeError(\"ShareSheet title must not be empty\");\n if (!input.link.trim()) throw new TypeError(\"ShareSheet link must not be empty\");\n for (const [key, value] of Object.entries(input.labels)) {\n if (typeof value !== \"string\" || !value.trim()) throw new TypeError(`ShareSheet labels.${key} must be localized text`);\n }\n}\n\n/** Recipients the sheet shows for a search: every item for a blank query, else label matches (locale lower case). */\nexport function filterShareSheetRecipients<Item extends ShareSheetRecipientDescriptor>(items: readonly Item[], query: string): readonly Item[] {\n const needle = query.trim().toLocaleLowerCase();\n if (!needle) return items;\n return items.filter(item => item.label.toLocaleLowerCase().includes(needle));\n}\n\n/** Validates the full recipient list (unique ids, localized labels) the same way the picker does. */\nexport function validateShareSheetRecipients(items: readonly ShareSheetRecipientDescriptor[]): void {\n if (items.length) validateImageChoiceItems(items);\n}\n\n/**\n * Splits one send attempt into delivered and failed ids. A product reporting an id it was not asked to send is a bug\n * (it would mark someone as sent who never was), so it throws instead of being ignored.\n */\nexport function resolveShareSheetSendOutcome(requested: readonly string[], result: ShareSheetSendResult): Readonly<{ sent: readonly string[]; failed: readonly string[] }> {\n const asked = new Set(requested);\n for (const id of result.sentIds) {\n if (!asked.has(id)) throw new TypeError(`ShareSheet onSend reported \"${id}\", which was not requested`);\n }\n const delivered = new Set(result.sentIds);\n return { sent: requested.filter(id => delivered.has(id)), failed: requested.filter(id => !delivered.has(id)) };\n}\n\n/**\n * The picker value: the user's selection plus every already-sent recipient, which stays checked and locked so it\n * cannot be sent twice.\n */\nexport function resolveShareSheetPickerValue(selected: readonly string[], sent: ReadonlySet<string>, visibleIds: readonly string[]): string[] {\n return [...selected.filter(id => !sent.has(id)), ...visibleIds.filter(id => sent.has(id))];\n}\n"]}
|
package/dist/timeline.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export type TimelineDescriptor<Id extends string = string> = Readonly<{
|
|
|
33
33
|
}>;
|
|
34
34
|
export declare const timelineDefaults: {
|
|
35
35
|
readonly itemTone: "neutral";
|
|
36
|
+
readonly variant: "journal";
|
|
36
37
|
};
|
|
37
38
|
export type TimelineAccessibleNameInfo = Readonly<{
|
|
38
39
|
position: number;
|
|
@@ -67,6 +68,7 @@ export declare function validateTimelineDescriptor<Id extends string>(descriptor
|
|
|
67
68
|
*/
|
|
68
69
|
export declare function validateTimelineSelection(selectedId: unknown, onSelectedIdChange: unknown, options?: Readonly<{
|
|
69
70
|
hasActions: boolean;
|
|
71
|
+
variant?: "record" | "journal";
|
|
70
72
|
}>): void;
|
|
71
73
|
/**
|
|
72
74
|
* Keyboard target for a selectable Timeline (vertical listbox): ArrowDown/ArrowUp step one record, Home/End jump to
|
|
@@ -84,6 +86,11 @@ export declare function resolveTimelineDescriptor<Id extends string>(descriptor:
|
|
|
84
86
|
export declare const timelineRecipe: {
|
|
85
87
|
readonly slots: readonly ["root", "item", "dot", "connector", "content", "timestamp", "label", "description"];
|
|
86
88
|
readonly gap: 20;
|
|
89
|
+
readonly journal: {
|
|
90
|
+
readonly gap: 0;
|
|
91
|
+
readonly paddingBlock: 16;
|
|
92
|
+
readonly dotDiameter: 8;
|
|
93
|
+
};
|
|
87
94
|
readonly item: {
|
|
88
95
|
readonly padding: 12;
|
|
89
96
|
readonly emphasisBackground: Readonly<{
|
package/dist/timeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;AAE5E,MAAM,MAAM,sBAAsB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACxE,EAAE,EAAE,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,CAAC;IACd,0HAA0H;IAC1H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,iFAAiF;IACjF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,kBAAkB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACpE,KAAK,EAAE,SAAS,sBAAsB,CAAC,EAAE,CAAC,EAAE,CAAC;IAC7C;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC;CACxB,CAAC,CAAC;AAEH,eAAO,MAAM,gBAAgB
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
|
package/dist/timeline.js
CHANGED
|
@@ -3,6 +3,7 @@ import { semanticColors } from "./semantic-colors.js";
|
|
|
3
3
|
import { getCollectionNavigationIntent, getCollectionNavigationTarget } from "./collection.js";
|
|
4
4
|
export const timelineDefaults = {
|
|
5
5
|
itemTone: "neutral",
|
|
6
|
+
variant: "journal",
|
|
6
7
|
};
|
|
7
8
|
function assertNonEmpty(value, field) {
|
|
8
9
|
if (value.trim().length === 0) {
|
|
@@ -60,6 +61,9 @@ export function validateTimelineDescriptor(descriptor) {
|
|
|
60
61
|
* the passive Timeline.
|
|
61
62
|
*/
|
|
62
63
|
export function validateTimelineSelection(selectedId, onSelectedIdChange, options = { hasActions: false }) {
|
|
64
|
+
if (options.variant !== undefined && options.variant !== "record" && options.variant !== "journal") {
|
|
65
|
+
throw new TypeError("Unsupported Timeline variant");
|
|
66
|
+
}
|
|
63
67
|
if ((selectedId === undefined) !== (onSelectedIdChange === undefined)) {
|
|
64
68
|
throw new TypeError("Timeline selectedId and onSelectedIdChange must be supplied together");
|
|
65
69
|
}
|
|
@@ -125,6 +129,9 @@ export const timelineRecipe = {
|
|
|
125
129
|
"description",
|
|
126
130
|
],
|
|
127
131
|
gap: spacing.lg,
|
|
132
|
+
// 2026-10-27: the approved journey journal keeps the reading rail, without large selected cards.
|
|
133
|
+
// Passive histories keep every detail and command; selection adds disclosure without changing event semantics.
|
|
134
|
+
journal: { gap: 0, paddingBlock: spacing.md, dotDiameter: 8 },
|
|
128
135
|
// A record remains passive: emphasis highlights its history without implying progress.
|
|
129
136
|
item: {
|
|
130
137
|
padding: spacing.sm,
|
package/dist/timeline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { getCollectionNavigationIntent, getCollectionNavigationTarget } from \"./collection.js\";\n\n/**\n * Timeline is a record of what already happened, not a position in a flow —\n * that is Steps' job (see docs/steps.md and the boundary note in\n * docs/timeline.md). Because there is no cursor, Timeline never derives a\n * status from position: every item is simply \"happened\", and the connector\n * between items carries no reached/unreached meaning. Ant Design's\n * alternating left/right layout is intentionally not reproduced — it is a\n * Web-only decoration with no equivalent in a single-direction native list;\n * see docs/timeline.md for the rationale.\n */\nexport type TimelineItemTone = \"neutral\" | \"info\" | \"success\" | \"attention\";\n\nexport type TimelineItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n /** Product-formatted \"when\" copy, e.g. \"3회 초\" or \"2024-01-15\". Timeline never formats a clock, inning, or date itself. */\n timestamp?: string;\n /** Machine-readable date for the formatted timestamp; products retain the original instant. */\n dateTime?: string;\n /** Product-formatted supplementary detail, e.g. \"안타로 1루 진출\". */\n description?: string;\n tone?: TimelineItemTone;\n /** Passive emphasis for a notable record, not a progress cursor or selection. */\n emphasized?: boolean;\n}>;\n\nexport type TimelineDescriptor<Id extends string = string> = Readonly<{\n items: readonly TimelineItemDescriptor<Id>[];\n /**\n * Single selection of one record (2026-10-23): `undefined` keeps the passive record, `null` makes the list selectable\n * with nothing chosen yet. Selection is not a progress cursor — no status is derived from it. Diairy's journey log\n * rebuilt this with a `Button` tone toggle + `aria-pressed` per step (SELECTION forbids button-as-selection).\n */\n selectedId?: Id | null;\n}>;\n\nexport const timelineDefaults = {\n itemTone: \"neutral\",\n} as const satisfies Readonly<{ itemTone: TimelineItemTone }>;\n\nexport type TimelineAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar, same reason\n * Steps does not assemble its own accessible name (see docs/steps.md).\n */\nexport type ComposeTimelineAccessibleName = (\n info: TimelineAccessibleNameInfo,\n) => string;\n\nexport type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<\n TimelineItemDescriptor<Id>,\n \"tone\"\n> &\n Readonly<{\n tone: TimelineItemTone;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Order + label, for platforms without built-in list-position semantics. */\n accessibleName: string;\n /** True only for the record matching a selectable Timeline's `selectedId`. */\n selected: boolean;\n }>;\n\nexport type ResolveTimelineOptions = Readonly<{\n composeAccessibleName: ComposeTimelineAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Timeline ${field} must not be empty`);\n }\n}\n\nexport function validateTimelineItemDescriptor<Id extends string>(\n item: TimelineItemDescriptor<Id>,\n): void {\n assertNonEmpty(item.id, \"item id\");\n if (item.emphasized !== undefined && typeof item.emphasized !== \"boolean\") {\n throw new TypeError(\"Timeline item emphasized must be a boolean\");\n }\n if (item.id !== item.id.trim()) {\n throw new TypeError(\"Timeline item id must not start or end with whitespace\");\n }\n assertNonEmpty(item.label, \"item label\");\n if (item.timestamp !== undefined) {\n assertNonEmpty(item.timestamp, \"item timestamp\");\n }\n if (item.dateTime !== undefined) {\n assertNonEmpty(item.dateTime, \"item dateTime\");\n if (item.timestamp === undefined) throw new TypeError(\"Timeline dateTime requires timestamp\");\n }\n if (item.description !== undefined) {\n assertNonEmpty(item.description, \"item description\");\n }\n if (\n item.tone !== undefined &&\n item.tone !== \"neutral\" &&\n item.tone !== \"info\" &&\n item.tone !== \"success\" &&\n item.tone !== \"attention\"\n ) {\n throw new TypeError(`Unsupported Timeline item tone: ${String(item.tone)}`);\n }\n}\n\nexport function validateTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Timeline must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n validateTimelineItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Timeline item id: ${item.id}`);\n }\n ids.add(item.id);\n }\n if (descriptor.selectedId !== undefined && descriptor.selectedId !== null && !ids.has(descriptor.selectedId)) {\n throw new RangeError(`Timeline selectedId does not match any item id: ${String(descriptor.selectedId)}`);\n }\n}\n\n/**\n * Selection is a pair: a controlled `selectedId` (null for none) and the change callback, or neither. A selectable\n * record is one target (Web option, Native selected button), so it cannot also host `renderActions` commands —\n * nested controls inside an option are not operable by assistive tech. Products that need a per-record command keep\n * the passive Timeline.\n */\nexport function validateTimelineSelection(\n selectedId: unknown,\n onSelectedIdChange: unknown,\n options: Readonly<{ hasActions: boolean }> = { hasActions: false },\n): void {\n if ((selectedId === undefined) !== (onSelectedIdChange === undefined)) {\n throw new TypeError(\"Timeline selectedId and onSelectedIdChange must be supplied together\");\n }\n if (onSelectedIdChange !== undefined && typeof onSelectedIdChange !== \"function\") {\n throw new TypeError(\"Timeline onSelectedIdChange must be a function\");\n }\n if (onSelectedIdChange !== undefined && options.hasActions) {\n throw new TypeError(\"A selectable Timeline cannot render per-record actions; the record itself is the selection target\");\n }\n}\n\n/**\n * Keyboard target for a selectable Timeline (vertical listbox): ArrowDown/ArrowUp step one record, Home/End jump to\n * the ends, and the ends do not wrap — a history has a first and a last record. Selection follows focus, as in the\n * single-select listbox pattern, because choosing a record only changes what the product shows next to it.\n * Returns undefined for keys Timeline does not own. Reuses the shared collection navigation math.\n */\nexport function getTimelineSelectionTarget<Id extends string>(\n items: readonly TimelineItemDescriptor<Id>[],\n currentId: Id | null,\n key: string,\n): Id | undefined {\n const intent = getCollectionNavigationIntent(key as Parameters<typeof getCollectionNavigationIntent>[0]);\n if (!intent) return undefined;\n return getCollectionNavigationTarget(\n { items: items.map((item) => ({ id: item.id, label: item.label, textValue: item.label })) },\n currentId,\n intent,\n false,\n );\n}\n\n/**\n * Attaches order-preserving accessible names to an already-ordered item list.\n * Unlike Steps, no status is derived from position — every item already\n * happened, so this only adds order and the resolved tone default.\n */\nexport function resolveTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n options: ResolveTimelineOptions,\n): readonly ResolvedTimelineItemDescriptor<Id>[] {\n validateTimelineDescriptor(descriptor);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Timeline composeAccessibleName must be a function\");\n }\n\n const total = descriptor.items.length;\n return descriptor.items.map((item, index) => {\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: item.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Timeline composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...item,\n tone: item.tone ?? timelineDefaults.itemTone,\n position,\n total,\n accessibleName,\n selected: descriptor.selectedId !== undefined && descriptor.selectedId === item.id,\n };\n });\n}\n\nexport const timelineRecipe = {\n slots: [\n \"root\",\n \"item\",\n \"dot\",\n \"connector\",\n \"content\",\n \"timestamp\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.lg,\n // A record remains passive: emphasis highlights its history without implying progress.\n item: {\n padding: spacing.sm,\n emphasisBackground: semanticColors.feedback.info.background,\n /**\n * Selected record (selectable Timeline): the neutral persistent-selection surface and ring shared with ListRow,\n * not brand fill (theme-palette.md 선택 상태 색상 기준). Selection outranks passive emphasis on the same record.\n */\n selected: {\n background: semanticColors.selection.background,\n border: semanticColors.selection.border,\n borderWidth: stroke.default,\n },\n },\n dot: {\n diameter: 10,\n borderWidth: stroke.default,\n tones: {\n neutral: { border: null, fill: semanticColors.content.secondary },\n info: {\n border: semanticColors.feedback.info.border,\n fill: semanticColors.feedback.info.foreground,\n },\n success: {\n border: semanticColors.feedback.success.border,\n fill: semanticColors.feedback.success.foreground,\n },\n attention: {\n border: semanticColors.feedback.attention.border,\n fill: semanticColors.feedback.attention.foreground,\n },\n },\n },\n /**\n * A single tone, unlike stepsRecipe.connector's reached/unreached pair —\n * Timeline has no cursor, so no segment is \"not yet reached\".\n */\n connector: {\n width: stroke.default,\n tone: semanticColors.border.default,\n },\n timestamp: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n label: {\n textVariant: \"body\",\n fontWeight: fontWeight.medium,\n color: semanticColors.content.primary,\n },\n description: {\n textVariant: \"body\",\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n item: {\n padding: number;\n emphasisBackground: ColorReference;\n selected: { background: ColorReference; border: ColorReference; borderWidth: number };\n };\n dot: {\n diameter: number;\n borderWidth: number;\n tones: Record<\n TimelineItemTone,\n { border: ColorReference | null; fill: ColorReference }\n >;\n };\n connector: {\n width: number;\n tone: ColorReference;\n };\n timestamp: { textVariant: TextVariant; color: ColorReference };\n label: { textVariant: TextVariant; fontWeight: FontWeightValue; color: ColorReference };\n description: { textVariant: TextVariant; color: ColorReference };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { getCollectionNavigationIntent, getCollectionNavigationTarget } from \"./collection.js\";\n\n/**\n * Timeline is a record of what already happened, not a position in a flow —\n * that is Steps' job (see docs/steps.md and the boundary note in\n * docs/timeline.md). Because there is no cursor, Timeline never derives a\n * status from position: every item is simply \"happened\", and the connector\n * between items carries no reached/unreached meaning. Ant Design's\n * alternating left/right layout is intentionally not reproduced — it is a\n * Web-only decoration with no equivalent in a single-direction native list;\n * see docs/timeline.md for the rationale.\n */\nexport type TimelineItemTone = \"neutral\" | \"info\" | \"success\" | \"attention\";\n\nexport type TimelineItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n /** Product-formatted \"when\" copy, e.g. \"3회 초\" or \"2024-01-15\". Timeline never formats a clock, inning, or date itself. */\n timestamp?: string;\n /** Machine-readable date for the formatted timestamp; products retain the original instant. */\n dateTime?: string;\n /** Product-formatted supplementary detail, e.g. \"안타로 1루 진출\". */\n description?: string;\n tone?: TimelineItemTone;\n /** Passive emphasis for a notable record, not a progress cursor or selection. */\n emphasized?: boolean;\n}>;\n\nexport type TimelineDescriptor<Id extends string = string> = Readonly<{\n items: readonly TimelineItemDescriptor<Id>[];\n /**\n * Single selection of one record (2026-10-23): `undefined` keeps the passive record, `null` makes the list selectable\n * with nothing chosen yet. Selection is not a progress cursor — no status is derived from it. Diairy's journey log\n * rebuilt this with a `Button` tone toggle + `aria-pressed` per step (SELECTION forbids button-as-selection).\n */\n selectedId?: Id | null;\n}>;\n\nexport const timelineDefaults = {\n itemTone: \"neutral\",\n variant: \"journal\",\n} as const satisfies Readonly<{ itemTone: TimelineItemTone; variant: \"record\" | \"journal\" }>;\n\nexport type TimelineAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar, same reason\n * Steps does not assemble its own accessible name (see docs/steps.md).\n */\nexport type ComposeTimelineAccessibleName = (\n info: TimelineAccessibleNameInfo,\n) => string;\n\nexport type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<\n TimelineItemDescriptor<Id>,\n \"tone\"\n> &\n Readonly<{\n tone: TimelineItemTone;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Order + label, for platforms without built-in list-position semantics. */\n accessibleName: string;\n /** True only for the record matching a selectable Timeline's `selectedId`. */\n selected: boolean;\n }>;\n\nexport type ResolveTimelineOptions = Readonly<{\n composeAccessibleName: ComposeTimelineAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Timeline ${field} must not be empty`);\n }\n}\n\nexport function validateTimelineItemDescriptor<Id extends string>(\n item: TimelineItemDescriptor<Id>,\n): void {\n assertNonEmpty(item.id, \"item id\");\n if (item.emphasized !== undefined && typeof item.emphasized !== \"boolean\") {\n throw new TypeError(\"Timeline item emphasized must be a boolean\");\n }\n if (item.id !== item.id.trim()) {\n throw new TypeError(\"Timeline item id must not start or end with whitespace\");\n }\n assertNonEmpty(item.label, \"item label\");\n if (item.timestamp !== undefined) {\n assertNonEmpty(item.timestamp, \"item timestamp\");\n }\n if (item.dateTime !== undefined) {\n assertNonEmpty(item.dateTime, \"item dateTime\");\n if (item.timestamp === undefined) throw new TypeError(\"Timeline dateTime requires timestamp\");\n }\n if (item.description !== undefined) {\n assertNonEmpty(item.description, \"item description\");\n }\n if (\n item.tone !== undefined &&\n item.tone !== \"neutral\" &&\n item.tone !== \"info\" &&\n item.tone !== \"success\" &&\n item.tone !== \"attention\"\n ) {\n throw new TypeError(`Unsupported Timeline item tone: ${String(item.tone)}`);\n }\n}\n\nexport function validateTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Timeline must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n validateTimelineItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Timeline item id: ${item.id}`);\n }\n ids.add(item.id);\n }\n if (descriptor.selectedId !== undefined && descriptor.selectedId !== null && !ids.has(descriptor.selectedId)) {\n throw new RangeError(`Timeline selectedId does not match any item id: ${String(descriptor.selectedId)}`);\n }\n}\n\n/**\n * Selection is a pair: a controlled `selectedId` (null for none) and the change callback, or neither. A selectable\n * record is one target (Web option, Native selected button), so it cannot also host `renderActions` commands —\n * nested controls inside an option are not operable by assistive tech. Products that need a per-record command keep\n * the passive Timeline.\n */\nexport function validateTimelineSelection(\n selectedId: unknown,\n onSelectedIdChange: unknown,\n options: Readonly<{ hasActions: boolean; variant?: \"record\" | \"journal\" }> = { hasActions: false },\n): void {\n if (options.variant !== undefined && options.variant !== \"record\" && options.variant !== \"journal\") {\n throw new TypeError(\"Unsupported Timeline variant\");\n }\n if ((selectedId === undefined) !== (onSelectedIdChange === undefined)) {\n throw new TypeError(\"Timeline selectedId and onSelectedIdChange must be supplied together\");\n }\n if (onSelectedIdChange !== undefined && typeof onSelectedIdChange !== \"function\") {\n throw new TypeError(\"Timeline onSelectedIdChange must be a function\");\n }\n if (onSelectedIdChange !== undefined && options.hasActions) {\n throw new TypeError(\"A selectable Timeline cannot render per-record actions; the record itself is the selection target\");\n }\n}\n\n/**\n * Keyboard target for a selectable Timeline (vertical listbox): ArrowDown/ArrowUp step one record, Home/End jump to\n * the ends, and the ends do not wrap — a history has a first and a last record. Selection follows focus, as in the\n * single-select listbox pattern, because choosing a record only changes what the product shows next to it.\n * Returns undefined for keys Timeline does not own. Reuses the shared collection navigation math.\n */\nexport function getTimelineSelectionTarget<Id extends string>(\n items: readonly TimelineItemDescriptor<Id>[],\n currentId: Id | null,\n key: string,\n): Id | undefined {\n const intent = getCollectionNavigationIntent(key as Parameters<typeof getCollectionNavigationIntent>[0]);\n if (!intent) return undefined;\n return getCollectionNavigationTarget(\n { items: items.map((item) => ({ id: item.id, label: item.label, textValue: item.label })) },\n currentId,\n intent,\n false,\n );\n}\n\n/**\n * Attaches order-preserving accessible names to an already-ordered item list.\n * Unlike Steps, no status is derived from position — every item already\n * happened, so this only adds order and the resolved tone default.\n */\nexport function resolveTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n options: ResolveTimelineOptions,\n): readonly ResolvedTimelineItemDescriptor<Id>[] {\n validateTimelineDescriptor(descriptor);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Timeline composeAccessibleName must be a function\");\n }\n\n const total = descriptor.items.length;\n return descriptor.items.map((item, index) => {\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: item.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Timeline composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...item,\n tone: item.tone ?? timelineDefaults.itemTone,\n position,\n total,\n accessibleName,\n selected: descriptor.selectedId !== undefined && descriptor.selectedId === item.id,\n };\n });\n}\n\nexport const timelineRecipe = {\n slots: [\n \"root\",\n \"item\",\n \"dot\",\n \"connector\",\n \"content\",\n \"timestamp\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.lg,\n // 2026-10-27: the approved journey journal keeps the reading rail, without large selected cards.\n // Passive histories keep every detail and command; selection adds disclosure without changing event semantics.\n journal: { gap: 0, paddingBlock: spacing.md, dotDiameter: 8 },\n // A record remains passive: emphasis highlights its history without implying progress.\n item: {\n padding: spacing.sm,\n emphasisBackground: semanticColors.feedback.info.background,\n /**\n * Selected record (selectable Timeline): the neutral persistent-selection surface and ring shared with ListRow,\n * not brand fill (theme-palette.md 선택 상태 색상 기준). Selection outranks passive emphasis on the same record.\n */\n selected: {\n background: semanticColors.selection.background,\n border: semanticColors.selection.border,\n borderWidth: stroke.default,\n },\n },\n dot: {\n diameter: 10,\n borderWidth: stroke.default,\n tones: {\n neutral: { border: null, fill: semanticColors.content.secondary },\n info: {\n border: semanticColors.feedback.info.border,\n fill: semanticColors.feedback.info.foreground,\n },\n success: {\n border: semanticColors.feedback.success.border,\n fill: semanticColors.feedback.success.foreground,\n },\n attention: {\n border: semanticColors.feedback.attention.border,\n fill: semanticColors.feedback.attention.foreground,\n },\n },\n },\n /**\n * A single tone, unlike stepsRecipe.connector's reached/unreached pair —\n * Timeline has no cursor, so no segment is \"not yet reached\".\n */\n connector: {\n width: stroke.default,\n tone: semanticColors.border.default,\n },\n timestamp: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n label: {\n textVariant: \"body\",\n fontWeight: fontWeight.medium,\n color: semanticColors.content.primary,\n },\n description: {\n textVariant: \"body\",\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n journal: { gap: number; paddingBlock: number; dotDiameter: number };\n item: {\n padding: number;\n emphasisBackground: ColorReference;\n selected: { background: ColorReference; border: ColorReference; borderWidth: number };\n };\n dot: {\n diameter: number;\n borderWidth: number;\n tones: Record<\n TimelineItemTone,\n { border: ColorReference | null; fill: ColorReference }\n >;\n };\n connector: {\n width: number;\n tone: ColorReference;\n };\n timestamp: { textVariant: TextVariant; color: ColorReference };\n label: { textVariant: TextVariant; fontWeight: FontWeightValue; color: ColorReference };\n description: { textVariant: TextVariant; color: ColorReference };\n};\n"]}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.49.0\" as const;\n"]}
|
package/docs/dialog.md
CHANGED
|
@@ -46,7 +46,7 @@ Native accessibility follow-up (2026-10-01): at 200% text scale, the close glyph
|
|
|
46
46
|
|
|
47
47
|
Native viewport candidate (BT-QA-027, 2026-10-05): BurnTok local QA exposed a long confirmation extending beyond the visible screen. Dialog and AlertDialog now constrain the content surface and provide a shrinking scroll body, with actions outside that body and provider safe-area padding. Host tests cover those structural contracts and cancellation; they cannot prove Yoga geometry. Ordinary-size iOS confirmation and cancellation were checked. Enlarged-text device verification remains pending: the user stopped that QA scope before the candidate was confirmed, so this is not a completed device fix. Font caps and clipped copy were rejected because they remove readable content.
|
|
48
48
|
|
|
49
|
-
## Optional measured origin motion (published API,
|
|
49
|
+
## Optional measured origin motion (published API, stable presentation contract)
|
|
50
50
|
|
|
51
51
|
2026-10-07 Aceternity card/grid comparison exposed a stale publication label here.
|
|
52
52
|
Both npm 1.14.0 renderer tarballs include `DialogProps.motionOrigin` in `dist/overlays.d.ts`,
|
|
@@ -61,8 +61,7 @@ the modal engine. It does not implement shared image/text identities or a new Po
|
|
|
61
61
|
Web retains one subtree through exit and suppresses stale animation completion when reopened.
|
|
62
62
|
Native uses the actual modal measurement and bounds its optional measurement wait by the existing
|
|
63
63
|
enter duration, falling back to ordinary presentation if the host fails to reply. Reduced motion skips
|
|
64
|
-
spatial interpolation.
|
|
65
|
-
experimental until the QA report verifies them. See the usage guide and
|
|
64
|
+
spatial interpolation. The optional presentation contract is stable as of 2026-10-26: browser/host regressions and the current iOS failed-save → close → reopen → successful-save flow passed. Per-device geometry/performance and consuming-product certification remain scoped QA; stable does not promise a frame-rate target on every device. See the usage guide and
|
|
66
65
|
[origin transition QA](../../../docs/qa/2026-10-07-overlay-origin-transition.md).
|
|
67
66
|
|
|
68
67
|
## Decorative layer and immersive presentation (1.31.0)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# 손그림 편집기 계약
|
|
2
|
+
|
|
3
|
+
2026-10-27 사용자 요청으로 다에리·스핀트의 동일한 그림 편집기를 공통 API로 옮겼다.
|
|
4
|
+
`DrawingEditor`는 `EditorScreen`·`ColorPicker`·`Slider`를 합성하는 beta 컴포넌트다.
|
|
5
|
+
|
|
6
|
+
순수 이력·선·지우개·배경·팔레트·배치는 `src/drawing-editor.ts`가 소유한다. Web pointer capture와 Native responder,
|
|
7
|
+
SVG/Canvas 이미지 추출은 각 renderer에 남는다. 제품 계정·업로드·저장 형식 제한·성공 후 탐색은 포함하지 않는다.
|
|
8
|
+
|
|
9
|
+
프로필 사진은 `cropPreview="circle"`을 전달한다. 기본 사각 캔버스와 원형 미리보기 모두 실제 출력에는 오버레이를 넣지 않는다.
|
|
10
|
+
저장 실패는 그림을 보존하며 같은 초안으로 다시 시도한다. 한 획을 하나의 이력 단계로 취급하고 새로운 획은 redo 분기를 없앤다.
|
|
11
|
+
펜과 지우개의 실제 굵기는 같은 슬라이더 값이며 지우개는 항상 현재 배경색을 따른다.
|
|
12
|
+
|
|
13
|
+
사용법과 소비 이관은 [DrawingEditor 지침](usage/components/drawing-editor.md)을 따른다. 미게시 개발 후보이며
|
|
14
|
+
구현·표면 테스트·제품 채택과 패키지 게시·실기기 QA는 각각 별도 상태다.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Component maturity and evidence manifest
|
|
2
2
|
|
|
3
|
-
> Generated from `@hjmds/design-contracts` 1.
|
|
3
|
+
> Generated from `@hjmds/design-contracts` 1.49.0. Do not edit this file directly; run `pnpm contracts:sync`.
|
|
4
4
|
|
|
5
5
|
This document is the human-readable projection of the same catalog and Showcase manifest consumed by product CI.
|
|
6
6
|
|
|
7
|
-
- Contract: stable
|
|
8
|
-
- Web renderer: stable
|
|
9
|
-
- Native renderer: stable
|
|
7
|
+
- Contract: stable 131 · beta 1
|
|
8
|
+
- Web renderer: stable 131 · beta 1
|
|
9
|
+
- Native renderer: stable 114 · beta 1 · unsupported 17
|
|
10
10
|
|
|
11
11
|
| Component | Canonical story ID | Contract | Web | Native | Documentation | Required evidence |
|
|
12
12
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
@@ -43,7 +43,7 @@ This document is the human-readable projection of the same catalog and Showcase
|
|
|
43
43
|
| RadioGroup | `input/radio-group` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
44
44
|
| Switch | `input/switch` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
45
45
|
| Chip | `input/chip` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
46
|
-
| ImageChoiceGroup | `input/image-choice-group` |
|
|
46
|
+
| ImageChoiceGroup | `input/image-choice-group` | stable | stable | stable | generated only | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility; native: default, dark, large-text, rtl, reduced-motion, accessibility |
|
|
47
47
|
| SegmentedControl | `input/segmented-control` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
48
48
|
| ToggleGroup | `input/toggle-group` | stable | stable | stable | [authored](../toggle-group.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
49
49
|
| TagsInput | `input/tags-input` | stable | stable | stable | [authored](../tags-input.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
@@ -54,7 +54,8 @@ This document is the human-readable projection of the same catalog and Showcase
|
|
|
54
54
|
| TimePicker | `input/time-picker` | stable | stable | stable | [authored](../time-picker.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
55
55
|
| DatePicker | `input/date-picker` | stable | stable | stable | [authored](../date-picker.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
56
56
|
| DateRangePicker | `input/date-range-picker` | stable | stable | stable | generated only | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
57
|
-
|
|
|
57
|
+
| DrawingEditor | `input/drawing-editor` | beta | beta | beta | [authored](../drawing-editor.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
58
|
+
| ColorPicker | `input/color-picker` | stable | stable | stable | [authored](../color-picker.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
58
59
|
| FilePicker | `input/file-picker` | stable | stable | stable | [authored](../file-picker.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
59
60
|
| Form | `input/form` | stable | stable | stable | [authored](../form.md) | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
60
61
|
| Agreement | `input/agreement` | stable | stable | stable | [authored](../agreement.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
@@ -66,8 +67,8 @@ This document is the human-readable projection of the same catalog and Showcase
|
|
|
66
67
|
| Sidebar | `navigation/sidebar` | stable | stable | unsupported | [authored](../sidebar.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard |
|
|
67
68
|
| BottomNavigation | `navigation/bottom-navigation` | stable | stable | stable | [authored](../bottom-navigation.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
68
69
|
| Breadcrumb | `navigation/breadcrumb` | stable | stable | unsupported | [authored](../breadcrumb.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard |
|
|
69
|
-
| PageIndicator | `navigation/page-indicator` |
|
|
70
|
-
| Pagination | `navigation/pagination` | stable | stable |
|
|
70
|
+
| PageIndicator | `navigation/page-indicator` | stable | stable | stable | generated only | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility; native: default, dark, large-text, rtl, reduced-motion, accessibility |
|
|
71
|
+
| Pagination | `navigation/pagination` | stable | stable | stable | [authored](../pagination.md) | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
71
72
|
| LoadMore | `navigation/load-more` | stable | stable | stable | [authored](../load-more.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
72
73
|
| Steps | `navigation/steps` | stable | stable | stable | [authored](../steps.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
73
74
|
| Menubar | `navigation/menubar` | stable | stable | unsupported | [authored](../menubar.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard |
|
|
@@ -86,8 +87,8 @@ This document is the human-readable projection of the same catalog and Showcase
|
|
|
86
87
|
| Accordion | `data-display/accordion` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
87
88
|
| Statistic | `data-display/statistic` | stable | stable | stable | [authored](../statistic.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
88
89
|
| Timeline | `data-display/timeline` | stable | stable | stable | [authored](../timeline.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
89
|
-
| DataTable | `data-display/data-table` | stable | stable |
|
|
90
|
-
| Chart | `data-display/chart` |
|
|
90
|
+
| DataTable | `data-display/data-table` | stable | stable | stable | [authored](../data-table.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
91
|
+
| Chart | `data-display/chart` | stable | stable | stable | [authored](../chart.md) | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
91
92
|
| Tree | `data-display/tree` | stable | stable | unsupported | [authored](../tree.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard |
|
|
92
93
|
| Calendar | `data-display/calendar` | stable | stable | stable | [authored](../calendar.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
93
94
|
| Carousel | `data-display/carousel` | stable | stable | stable | [authored](../carousel.md) | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, keyboard; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility, native-actions |
|
|
@@ -132,8 +133,8 @@ This document is the human-readable projection of the same catalog and Showcase
|
|
|
132
133
|
| MediaSelectionScreen | `layout/media-selection-screen` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
133
134
|
| ProfileScreen | `layout/profile-screen` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
134
135
|
| ChatMessage | `data-display/chat-message` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
135
|
-
| TypingIndicator | `feedback/typing-indicator` |
|
|
136
|
-
| ChatDateSeparator | `data-display/chat-date-separator` |
|
|
136
|
+
| TypingIndicator | `feedback/typing-indicator` | stable | stable | stable | generated only | contract: contract; web: default, dark, large-text, rtl, reduced-motion, accessibility; native: default, dark, large-text, rtl, reduced-motion, accessibility |
|
|
137
|
+
| ChatDateSeparator | `data-display/chat-date-separator` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
137
138
|
| NotificationRow | `data-display/notification-row` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
138
139
|
| NotificationItem | `data-display/notification-item` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|
|
139
140
|
| CommentThreadRow | `data-display/comment-thread-row` | stable | stable | stable | generated only | contract: contract; web: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility; native: default, dark, long-copy, large-text, rtl, reduced-motion, accessibility |
|