@hjmds/design-contracts 1.7.0 → 1.8.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/THIRD_PARTY_NOTICES.md +58 -0
- package/dist/agreement.d.ts +3 -3
- package/dist/agreement.js +1 -1
- package/dist/agreement.js.map +1 -1
- package/dist/asset.d.ts +1 -1
- package/dist/asset.js +1 -1
- package/dist/asset.js.map +1 -1
- package/dist/base-recipes.d.ts +12 -12
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +15 -14
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/catalog.d.ts +417 -561
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +138 -90
- package/dist/catalog.js.map +1 -1
- package/dist/command-palette.d.ts +3 -3
- package/dist/component-contracts.d.ts +4 -4
- package/dist/component-contracts.d.ts.map +1 -1
- package/dist/component-contracts.js +5 -4
- package/dist/component-contracts.js.map +1 -1
- package/dist/component-definitions.d.ts +1 -4
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +1 -4
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +76 -83
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +29 -49
- package/dist/component-recipes.js.map +1 -1
- package/dist/component-references.d.ts +0 -15
- package/dist/component-references.d.ts.map +1 -1
- package/dist/component-references.js +0 -3
- package/dist/component-references.js.map +1 -1
- package/dist/data-table.d.ts +3 -3
- package/dist/data-table.js +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.d.ts +2 -2
- package/dist/icon-button-recipe.d.ts +2 -2
- package/dist/icon-button-recipe.js +1 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/image.d.ts +2 -2
- package/dist/image.js +2 -2
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/thinking-orb/braid.d.ts +3 -0
- package/dist/internal/thinking-orb/braid.d.ts.map +1 -0
- package/dist/internal/thinking-orb/braid.js +47 -0
- package/dist/internal/thinking-orb/braid.js.map +1 -0
- package/dist/internal/thinking-orb/core.d.ts +60 -0
- package/dist/internal/thinking-orb/core.d.ts.map +1 -0
- package/dist/internal/thinking-orb/core.js +86 -0
- package/dist/internal/thinking-orb/core.js.map +1 -0
- package/dist/internal/thinking-orb/lattice.d.ts +5 -0
- package/dist/internal/thinking-orb/lattice.d.ts.map +1 -0
- package/dist/internal/thinking-orb/lattice.js +187 -0
- package/dist/internal/thinking-orb/lattice.js.map +1 -0
- package/dist/internal/thinking-orb/morph.d.ts +3 -0
- package/dist/internal/thinking-orb/morph.d.ts.map +1 -0
- package/dist/internal/thinking-orb/morph.js +127 -0
- package/dist/internal/thinking-orb/morph.js.map +1 -0
- package/dist/internal/thinking-orb/orbits.d.ts +3 -0
- package/dist/internal/thinking-orb/orbits.d.ts.map +1 -0
- package/dist/internal/thinking-orb/orbits.js +68 -0
- package/dist/internal/thinking-orb/orbits.js.map +1 -0
- package/dist/internal/thinking-orb/presets.d.ts +21 -0
- package/dist/internal/thinking-orb/presets.d.ts.map +1 -0
- package/dist/internal/thinking-orb/presets.js +77 -0
- package/dist/internal/thinking-orb/presets.js.map +1 -0
- package/dist/internal/thinking-orb/profiles.d.ts +8 -0
- package/dist/internal/thinking-orb/profiles.d.ts.map +1 -0
- package/dist/internal/thinking-orb/profiles.js +158 -0
- package/dist/internal/thinking-orb/profiles.js.map +1 -0
- package/dist/internal/thinking-orb/registry.d.ts +9 -0
- package/dist/internal/thinking-orb/registry.d.ts.map +1 -0
- package/dist/internal/thinking-orb/registry.js +27 -0
- package/dist/internal/thinking-orb/registry.js.map +1 -0
- package/dist/internal/thinking-orb/ribbon.d.ts +3 -0
- package/dist/internal/thinking-orb/ribbon.d.ts.map +1 -0
- package/dist/internal/thinking-orb/ribbon.js +87 -0
- package/dist/internal/thinking-orb/ribbon.js.map +1 -0
- package/dist/internal/thinking-orb/types.d.ts +15 -0
- package/dist/internal/thinking-orb/types.d.ts.map +1 -0
- package/dist/internal/thinking-orb/types.js +4 -0
- package/dist/internal/thinking-orb/types.js.map +1 -0
- package/dist/internal/thinking-orb/web.d.ts +3 -0
- package/dist/internal/thinking-orb/web.d.ts.map +1 -0
- package/dist/internal/thinking-orb/web.js +92 -0
- package/dist/internal/thinking-orb/web.js.map +1 -0
- package/dist/masonry.d.ts +17 -0
- package/dist/masonry.d.ts.map +1 -0
- package/dist/masonry.js +18 -0
- package/dist/masonry.js.map +1 -0
- package/dist/menubar.d.ts +3 -3
- package/dist/menubar.js +1 -1
- package/dist/menubar.js.map +1 -1
- package/dist/number-field.d.ts +2 -2
- package/dist/otp-field.d.ts +1 -1
- package/dist/password-field.d.ts +2 -2
- package/dist/popover.d.ts +7 -8
- package/dist/popover.d.ts.map +1 -1
- package/dist/popover.js +2 -0
- package/dist/popover.js.map +1 -1
- package/dist/qr-code-recipe.d.ts +7 -0
- package/dist/qr-code-recipe.d.ts.map +1 -0
- package/dist/qr-code-recipe.js +2 -0
- package/dist/qr-code-recipe.js.map +1 -0
- package/dist/qr-code.d.ts +16 -0
- package/dist/qr-code.d.ts.map +1 -0
- package/dist/qr-code.js +37 -0
- package/dist/qr-code.js.map +1 -0
- package/dist/semantic-colors.d.ts +1 -1
- package/dist/semantic-colors.js +1 -1
- package/dist/semantic-colors.js.map +1 -1
- package/dist/showcase.d.ts +5 -1
- package/dist/showcase.d.ts.map +1 -1
- package/dist/showcase.js +51 -13
- package/dist/showcase.js.map +1 -1
- package/dist/sidebar.d.ts +3 -3
- package/dist/sidebar.js +1 -1
- package/dist/sidebar.js.map +1 -1
- package/dist/tag.d.ts +4 -4
- package/dist/tag.js +3 -3
- package/dist/tag.js.map +1 -1
- package/dist/tags-input.d.ts +3 -3
- package/dist/tags-input.js +1 -1
- package/dist/tags-input.js.map +1 -1
- package/dist/text-formats.d.ts +2 -2
- package/dist/text-formats.js +2 -2
- package/dist/text-formats.js.map +1 -1
- package/dist/thinking-orb-recipe.d.ts +15 -0
- package/dist/thinking-orb-recipe.d.ts.map +1 -0
- package/dist/thinking-orb-recipe.js +9 -0
- package/dist/thinking-orb-recipe.js.map +1 -0
- package/dist/thinking-orb.d.ts +25 -0
- package/dist/thinking-orb.d.ts.map +1 -0
- package/dist/thinking-orb.js +40 -0
- package/dist/thinking-orb.js.map +1 -0
- package/dist/toast-liquid.d.ts +116 -0
- package/dist/toast-liquid.d.ts.map +1 -0
- package/dist/toast-liquid.js +100 -0
- package/dist/toast-liquid.js.map +1 -0
- package/dist/toast.d.ts +7 -1
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +5 -0
- package/dist/toast.js.map +1 -1
- package/dist/toggle-group.d.ts +2 -2
- package/dist/toggle-group.js +3 -3
- package/dist/toggle-group.js.map +1 -1
- package/dist/transfer-list.d.ts +3 -3
- package/dist/transfer-list.js +1 -1
- package/dist/transfer-list.js.map +1 -1
- package/dist/tree.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/virtual-list.d.ts +13 -0
- package/dist/virtual-list.d.ts.map +1 -0
- package/dist/virtual-list.js +13 -0
- package/dist/virtual-list.js.map +1 -0
- package/docs/anchor.md +5 -4
- package/docs/ant-design-coverage.md +2 -2
- package/docs/architecture.md +6 -2
- package/docs/authoring-brief.md +3 -2
- package/docs/bottom-navigation.md +7 -6
- package/docs/breadcrumb.md +5 -2
- package/docs/calendar.md +6 -3
- package/docs/carousel.md +2 -2
- package/docs/cascader.md +6 -0
- package/docs/catalog-cleanup.md +7 -0
- package/docs/catalog-decision-status.md +3 -3
- package/docs/catalog-freeze.json +9 -7
- package/docs/collapsible.md +12 -0
- package/docs/command-palette.md +5 -3
- package/docs/consumer-policy.md +29 -12
- package/docs/context-menu.md +12 -2
- package/docs/data-table.md +11 -6
- package/docs/date-picker.md +6 -4
- package/docs/date-range.md +4 -2
- package/docs/description-list.md +8 -9
- package/docs/dialog.md +39 -0
- package/docs/expansion-roadmap.md +11 -7
- package/docs/file-picker.md +5 -3
- package/docs/form.md +16 -4
- package/docs/generated/component-maturity.md +88 -91
- package/docs/generated/renderer-evidence.json +1848 -1023
- package/docs/generated/renderer-evidence.md +153 -145
- package/docs/generated/showcase-manifest.json +431 -497
- package/docs/layout.md +8 -9
- package/docs/library-gap-analysis.md +1 -1
- package/docs/masonry.md +11 -0
- package/docs/mentions.md +14 -7
- package/docs/optional-adapters.md +106 -0
- package/docs/otp-field.md +2 -1
- package/docs/pagination.md +4 -1
- package/docs/password-field.md +2 -1
- package/docs/popover.md +9 -6
- package/docs/promotion-candidates.md +2 -1
- package/docs/qr-code.md +11 -65
- package/docs/react-native-completion.md +1 -1
- package/docs/result.md +8 -5
- package/docs/screen-chrome.md +2 -1
- package/docs/sheet.md +8 -2
- package/docs/showcase.md +10 -2
- package/docs/side-panel.md +12 -6
- package/docs/sidebar.md +3 -0
- package/docs/splitter.md +6 -6
- package/docs/stable-core.md +72 -0
- package/docs/stable-promotion.md +47 -43
- package/docs/tags-input.md +5 -2
- package/docs/theme-palette.md +27 -0
- package/docs/thinking-orb.md +124 -0
- package/docs/timeline.md +2 -3
- package/docs/toast.md +6 -0
- package/docs/tooltip.md +2 -1
- package/docs/tour.md +14 -8
- package/docs/transfer-list.md +24 -21
- package/docs/tree-select.md +2 -1
- package/docs/tree.md +12 -7
- package/docs/upload-item.md +7 -3
- package/docs/virtual-list.md +10 -67
- package/package.json +30 -3
- package/docs/app-provider.md +0 -50
- package/docs/border-beam.md +0 -66
- package/docs/chart.md +0 -33
- package/docs/utility.md +0 -53
package/dist/toggle-group.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { focusIndicatorContract } from "./component-contracts.js";
|
|
2
2
|
import { control, radius, spacing } from "./foundations.js";
|
|
3
3
|
import { semanticColors } from "./semantic-colors.js";
|
|
4
4
|
import { reconcileCheckboxSelection, toggleCheckboxSelection, } from "./selection-helpers.js";
|
|
@@ -41,12 +41,12 @@ export const toggleGroupRecipe = {
|
|
|
41
41
|
radius: radius.md,
|
|
42
42
|
gap: spacing.xxs,
|
|
43
43
|
idle: {
|
|
44
|
-
background: semanticColors.
|
|
44
|
+
background: semanticColors.canvas,
|
|
45
45
|
color: semanticColors.content.body,
|
|
46
46
|
border: semanticColors.border.control,
|
|
47
47
|
},
|
|
48
48
|
pressed: {
|
|
49
|
-
background:
|
|
49
|
+
background: semanticColors.canvas,
|
|
50
50
|
color: semanticColors.content.brand,
|
|
51
51
|
border: semanticColors.border.focus,
|
|
52
52
|
},
|
package/dist/toggle-group.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toggle-group.js","sourceRoot":"","sources":["../src/toggle-group.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,
|
|
1
|
+
{"version":3,"file":"toggle-group.js","sourceRoot":"","sources":["../src/toggle-group.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, radius, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport {\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n type CheckboxSelectionItem,\n} from \"./selection-helpers.js\";\n\n/**\n * 여러 개를 동시에 켜고 끄는 버튼 묶음이다 — 굵게/기울임/밑줄, 필터 칩 여러 개처럼.\n *\n * `SegmentedControl`과 경계가 분명하다. Segmented는 **하나를 고른다**(어떤 화면을 볼지),\n * ToggleGroup은 **여러 개를 켠다**(무엇을 적용할지). 그래서 Segmented에는 \"선택 없음\"이\n * 없고 ToggleGroup에는 있다. 둘을 한 컴포넌트의 `mode` 축으로 합치면 그 차이가 사라지고,\n * \"아무것도 선택되지 않은 Segmented\"라는 표현 불가능한 상태가 타입에 생긴다.\n *\n * `CheckboxGroup`과도 다르다. CheckboxGroup은 목록 안의 동의/선택이라 각 항목이 자기\n * 줄과 설명을 갖는다. ToggleGroup은 도구 모음이라 한 줄에 붙어 있고 라벨이 짧다.\n * 판정(무엇을 켤 수 있고 비활성은 어떻게 되는가)은 같은 helper를 공유한다.\n */\nexport type ToggleGroupItemDescriptor<Id extends string = string> =\n CheckboxSelectionItem<Id>;\n\nexport type ToggleGroupDescriptor<Id extends string = string> = Readonly<{\n /** 묶음 전체의 접근성 이름. 개별 버튼 이름으로 대체할 수 없다. */\n accessibilityLabel: string;\n items: readonly ToggleGroupItemDescriptor<Id>[];\n}>;\n\nexport type ToggleGroupSize = \"small\" | \"medium\";\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`ToggleGroup ${field} must not be empty`);\n }\n}\n\nexport function validateToggleGroupDescriptor<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.items) || descriptor.items.length === 0) {\n throw new RangeError(\"ToggleGroup must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, \"item label\");\n if (ids.has(item.id)) throw new TypeError(`Duplicate ToggleGroup item id: ${item.id}`);\n ids.add(item.id);\n }\n}\n\n/** 하나를 켜고 끈다. 비활성 보호는 공용 helper가 이미 갖고 있다. */\nexport function toggleGroupSelection<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n pressedIds: ReadonlySet<Id>,\n id: Id,\n): ReadonlySet<Id> {\n validateToggleGroupDescriptor(descriptor);\n return toggleCheckboxSelection(descriptor.items, pressedIds, id);\n}\n\n/** 목록에서 사라진 항목의 눌림 상태는 남기지 않는다. */\nexport function reconcileToggleGroupSelection<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n pressedIds: ReadonlySet<Id>,\n): ReadonlySet<Id> {\n validateToggleGroupDescriptor(descriptor);\n return reconcileCheckboxSelection(descriptor.items, pressedIds);\n}\n\nexport const toggleGroupRecipe = {\n slots: [\"root\", \"item\", \"label\"] as const,\n defaults: { size: \"medium\" as ToggleGroupSize },\n sizes: {\n small: { minHeight: control.minTouchTarget, paddingHorizontal: spacing.sm, textVariant: \"label\" as const },\n medium: { minHeight: 44, paddingHorizontal: spacing.md, textVariant: \"body\" as const },\n },\n radius: radius.md,\n gap: spacing.xxs,\n idle: {\n background: semanticColors.canvas,\n color: semanticColors.content.body,\n border: semanticColors.border.control,\n },\n pressed: {\n background: semanticColors.canvas,\n color: semanticColors.content.brand,\n border: semanticColors.border.focus,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"item\", \"label\"];\n defaults: { size: ToggleGroupSize };\n sizes: Record<ToggleGroupSize, { minHeight: number; paddingHorizontal: number; textVariant: \"label\" | \"body\" }>;\n radius: number;\n gap: number;\n idle: { background: ColorReference; color: ColorReference; border: ColorReference };\n pressed: { background: ColorReference; color: ColorReference; border: ColorReference };\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const toggleGroupBehavior = {\n controlled: [\"pressedIds\", \"defaultPressedIds\", \"onPressedIdsChange\"],\n inputs: [\"items\", \"accessibilityLabel\", \"size\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"pressed\", \"unpressed\"],\n },\n web: {\n roles: [\"group\", \"button\"],\n keyboard: [\"Tab\", \"Enter\", \"Space\"],\n /** Toolbar-style roving focus is not used: each toggle is its own tab stop, like a row of checkboxes. */\n focus: \"native\",\n },\n native: { roles: [\"button\"], states: [\"selected\", \"disabled\"], actions: [\"toggle\"] },\n scenarios: [\n \"several-items-can-be-pressed-at-once-and-none-is-a-valid-state\",\n \"pressed-state-rides-on-aria-pressed-not-on-color-alone\",\n \"a-disabled-item-never-toggles-and-never-enters-the-pressed-set\",\n \"removing-an-item-drops-its-pressed-state-instead-of-keeping-an-orphan-id\",\n \"single-choice-belongs-to-segmented-control-this-contract-never-adds-a-single-mode\",\n \"the-group-carries-its-own-accessible-name-separate-from-each-buttons-name\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
package/dist/transfer-list.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export declare const transferListRecipe: {
|
|
|
70
70
|
readonly panel: {
|
|
71
71
|
readonly background: Readonly<{
|
|
72
72
|
source: "theme";
|
|
73
|
-
key: "
|
|
73
|
+
key: "bg";
|
|
74
74
|
alpha?: number;
|
|
75
75
|
}>;
|
|
76
76
|
readonly border: Readonly<{
|
|
@@ -124,7 +124,7 @@ export declare const transferListRecipe: {
|
|
|
124
124
|
};
|
|
125
125
|
readonly highlightedBackground: Readonly<{
|
|
126
126
|
source: "theme";
|
|
127
|
-
key: "
|
|
127
|
+
key: "bg";
|
|
128
128
|
alpha?: number;
|
|
129
129
|
}>;
|
|
130
130
|
readonly focus: {
|
|
@@ -138,7 +138,7 @@ export declare const transferListRecipe: {
|
|
|
138
138
|
};
|
|
139
139
|
readonly selectedBackground: Readonly<{
|
|
140
140
|
source: "theme";
|
|
141
|
-
key: "
|
|
141
|
+
key: "bg";
|
|
142
142
|
alpha?: number;
|
|
143
143
|
}>;
|
|
144
144
|
readonly selectedIndicator: Readonly<{
|
package/dist/transfer-list.js
CHANGED
|
@@ -138,7 +138,7 @@ export const transferListRecipe = {
|
|
|
138
138
|
"moveButton",
|
|
139
139
|
],
|
|
140
140
|
panel: {
|
|
141
|
-
background: semanticColors.
|
|
141
|
+
background: semanticColors.canvas,
|
|
142
142
|
border: semanticColors.border.default,
|
|
143
143
|
borderWidth: stroke.default,
|
|
144
144
|
radius: "md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer-list.js","sourceRoot":"","sources":["../src/transfer-list.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type {\n BehaviorContract,\n CheckboxState,\n CollectionKey,\n SelectItemDescriptor,\n} from \"./behaviors.js\";\nimport {\n getCheckboxNextState,\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n} from \"./selection-helpers.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport { validateCollection } from \"./collection.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `docs/transfer-list.md`: two lists sharing one item set, split by whether\n * each id is in `targetKeys`. This is the same shape CheckboxGroup already\n * commits to (`ReadonlySet<Id>`, no third \"which panel\" enum to keep in\n * sync with membership) — one source of truth instead of two arrays that can\n * drift apart when an item is moved.\n */\nexport type TransferListPanel = \"source\" | \"target\";\n\nexport type TransferListDescriptor<Id extends CollectionKey = CollectionKey> = Readonly<{\n items: readonly SelectItemDescriptor<Id>[];\n targetKeys: ReadonlySet<Id>;\n}>;\n\nexport type TransferListSelection<Id extends CollectionKey = CollectionKey> = Readonly<{\n source: ReadonlySet<Id>;\n target: ReadonlySet<Id>;\n}>;\n\nfunction withPanelSelection<Id extends CollectionKey>(\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n next: ReadonlySet<Id>,\n): TransferListSelection<Id> {\n return panel === \"source\"\n ? { source: next, target: selection.target }\n : { source: selection.source, target: next };\n}\n\nfunction assertKnownIds<Id extends CollectionKey>(\n label: string,\n ids: ReadonlySet<Id>,\n knownIds: ReadonlySet<Id>,\n): void {\n for (const id of ids) {\n if (!knownIds.has(id)) {\n throw new RangeError(`TransferList ${label} references an unknown item id: ${id}`);\n }\n }\n}\n\n/** An item already sitting entirely inside one panel (including all-in-target\n * or all-in-source) is valid — nothing here requires either panel to be\n * non-empty, and a disabled item is valid in either panel even though it can\n * never be moved by the UI. */\nexport function validateTransferListDescriptor<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n): void {\n validateCollection({ items: descriptor.items });\n const knownIds = new Set(descriptor.items.map((item) => item.id));\n assertKnownIds(\"targetKeys\", descriptor.targetKeys, knownIds);\n}\n\nexport type TransferListPanels<Id extends CollectionKey = CollectionKey> = Readonly<{\n source: readonly SelectItemDescriptor<Id>[];\n target: readonly SelectItemDescriptor<Id>[];\n}>;\n\n/** Splits by `targetKeys` membership, preserving each item's relative order from `items`. */\nexport function resolveTransferListPanels<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n): TransferListPanels<Id> {\n validateTransferListDescriptor(descriptor);\n const source: SelectItemDescriptor<Id>[] = [];\n const target: SelectItemDescriptor<Id>[] = [];\n for (const item of descriptor.items) {\n (descriptor.targetKeys.has(item.id) ? target : source).push(item);\n }\n return { source, target };\n}\n\nexport function validateTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n): void {\n const panels = resolveTransferListPanels(descriptor);\n assertKnownIds(\"selection.source\", selection.source, new Set(panels.source.map((i) => i.id)));\n assertKnownIds(\"selection.target\", selection.target, new Set(panels.target.map((i) => i.id)));\n}\n\n/** Toggles one row's checkbox within its own panel; the other panel's selection is untouched. */\nexport function toggleTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n id: Id,\n): TransferListSelection<Id> {\n const panels = resolveTransferListPanels(descriptor);\n const next = toggleCheckboxSelection(panels[panel], selection[panel], id);\n return withPanelSelection(selection, panel, next);\n}\n\n/**\n * Same tri-state `CheckboxState` `resolveDataTableSelectAllState` derives for\n * a table header — disabled rows excluded from both the denominator and the\n * count — generalized from \"all rows\" to \"this one panel's rows\".\n */\nexport function resolveTransferListSelectAllState<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n): CheckboxState {\n const items = resolveTransferListPanels(descriptor)[panel];\n const selectable = items.filter((item) => !item.disabled);\n if (selectable.length === 0) return false;\n const selectedCount = selectable.filter((item) => selection[panel].has(item.id)).length;\n if (selectedCount === 0) return false;\n return selectedCount === selectable.length ? true : \"mixed\";\n}\n\n/** Mixed-defaults-to-checked, matching `getCheckboxNextState`'s existing convention. */\nexport function toggleTransferListSelectAll<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n): TransferListSelection<Id> {\n const current = resolveTransferListSelectAllState(descriptor, selection, panel);\n const checkAll = getCheckboxNextState(current);\n const items = resolveTransferListPanels(descriptor)[panel];\n const next = checkAll\n ? new Set(items.filter((item) => !item.disabled).map((item) => item.id))\n : new Set<Id>();\n return withPanelSelection(selection, panel, next);\n}\n\nexport type TransferListMoveDirection = \"toTarget\" | \"toSource\";\n\nexport type TransferListMoveResult<Id extends CollectionKey = CollectionKey> = Readonly<{\n targetKeys: ReadonlySet<Id>;\n selection: TransferListSelection<Id>;\n /** In origin-panel order — the product formats the announcement string from this. */\n movedIds: readonly Id[];\n}>;\n\n/**\n * Moves every enabled, currently-selected id out of its origin panel.\n * Disabled ids are skipped rather than rejected (a disabled row can never\n * enter a selection through `toggleTransferListSelection`, but a product\n * could hand one to `defaultSelectedKeys`, and skipping mirrors\n * `toggleTreeCheckedSelection`'s disabled guard rather than throwing on it).\n * Moved ids are cleared from the origin panel's selection and deliberately\n * left unselected in the destination panel — a move commits a value, it does\n * not also commit a new pending selection the user did not make.\n */\nexport function moveTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n direction: TransferListMoveDirection,\n): TransferListMoveResult<Id> {\n validateTransferListSelection(descriptor, selection);\n const fromPanel: TransferListPanel = direction === \"toTarget\" ? \"source\" : \"target\";\n const fromItems = resolveTransferListPanels(descriptor)[fromPanel];\n const movedIds = fromItems\n .filter((item) => !item.disabled && selection[fromPanel].has(item.id))\n .map((item) => item.id);\n\n if (movedIds.length === 0) {\n return { targetKeys: descriptor.targetKeys, selection, movedIds: [] };\n }\n\n const movedIdSet = new Set(movedIds);\n const targetKeys =\n direction === \"toTarget\"\n ? new Set([...descriptor.targetKeys, ...movedIds])\n : new Set([...descriptor.targetKeys].filter((id) => !movedIdSet.has(id)));\n\n const nextFromSelection = new Set(\n [...selection[fromPanel]].filter((id) => !movedIdSet.has(id)),\n );\n return {\n targetKeys,\n selection: withPanelSelection(selection, fromPanel, nextFromSelection),\n movedIds,\n };\n}\n\n/**\n * Where focus lands once the moved rows are gone from a panel: the item that\n * slid into the first removed row's position, so keyboard users keep moving\n * down the list without their focus jumping to the top or vanishing. `null`\n * means the panel is now empty — the renderer falls back to the panel's\n * empty-state message or the move button, never to the document body.\n */\nexport function resolveTransferListFocusAfterMove<Id extends CollectionKey>(\n remainingPanelItems: readonly SelectItemDescriptor<Id>[],\n removedIndex: number,\n): Id | null {\n if (remainingPanelItems.length === 0) return null;\n const clamped = Math.min(Math.max(removedIndex, 0), remainingPanelItems.length - 1);\n return remainingPanelItems[clamped]!.id;\n}\n\n/** Drops selected ids that left either panel (item removed from `items` entirely). */\nexport function reconcileTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n): TransferListSelection<Id> {\n const panels = resolveTransferListPanels(descriptor);\n const source = reconcileCheckboxSelection(panels.source, selection.source);\n const target = reconcileCheckboxSelection(panels.target, selection.target);\n if (source === selection.source && target === selection.target) return selection;\n return { source, target };\n}\n\nexport const transferListRecipe = {\n slots: [\n \"root\",\n \"panel\",\n \"panelHeader\",\n \"panelTitle\",\n \"panelCount\",\n \"list\",\n \"item\",\n \"itemCheckbox\",\n \"emptyState\",\n \"moveControls\",\n \"moveButton\",\n ] as const,\n panel: {\n background: semanticColors.surface.default,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n radius: \"md\" as const,\n },\n panelHeader: {\n minHeight: 44,\n paddingHorizontal: spacing.sm,\n title: { color: semanticColors.content.body, textVariant: \"body\" as const },\n count: { color: semanticColors.content.secondary, textVariant: \"label\" as const },\n },\n item: collectionItemContract,\n emptyState: { color: semanticColors.content.secondary, textVariant: \"label\" as const },\n moveControls: { gap: spacing.sm },\n moveButton: {\n minTarget: control.minTouchTarget,\n color: semanticColors.content.brand,\n disabledOpacity: 0.4,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\n \"root\",\n \"panel\",\n \"panelHeader\",\n \"panelTitle\",\n \"panelCount\",\n \"list\",\n \"item\",\n \"itemCheckbox\",\n \"emptyState\",\n \"moveControls\",\n \"moveButton\",\n ];\n panel: { background: ColorReference; border: ColorReference; borderWidth: number; radius: keyof typeof radius };\n panelHeader: {\n minHeight: number;\n paddingHorizontal: number;\n title: { color: ColorReference; textVariant: \"body\" };\n count: { color: ColorReference; textVariant: \"label\" };\n };\n item: typeof collectionItemContract;\n emptyState: { color: ColorReference; textVariant: \"label\" };\n moveControls: { gap: number };\n moveButton: { minTarget: number; color: ColorReference; disabledOpacity: number };\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const transferListBehavior = {\n controlled: [\"targetKeys\", \"defaultTargetKeys\", \"onTargetKeysChange\"],\n inputs: [\"items\"],\n events: [\"onMove\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"empty\", \"filled\", \"selected\", \"mixed\"],\n },\n web: {\n roles: [\"group\", \"listbox\", \"option\", \"checkbox\", \"button\"],\n keyboard: [\"Tab\", \"Space\", \"Enter\", \"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"],\n focus: \"roving\",\n },\n native: {\n roles: [\"list\", \"checkbox\", \"button\"],\n states: [\"disabled\", \"selected\", \"checked\"],\n actions: [\"toggle\", \"toggleSelectAll\", \"moveSelection\", \"moveItem\"],\n },\n scenarios: [\n \"moving-is-reachable-entirely-by-keyboard-select-with-space-then-activate-the-move-button\",\n \"moving-a-single-focused-row-does-not-require-first-opening-multi-select\",\n \"focus-after-a-move-lands-on-the-item-that-slid-into-the-removed-rows-position\",\n \"focus-after-emptying-a-panel-falls-back-to-its-empty-state-never-lost-to-the-document\",\n \"every-move-emits-which-ids-moved-so-the-product-can-announce-a-formatted-sentence\",\n \"disabled-items-are-never-selectable-and-never-move\",\n \"moved-items-are-cleared-from-the-origin-panels-selection-and-left-unselected-at-the-destination\",\n \"select-all-in-a-panel-excludes-disabled-items-from-both-the-denominator-and-the-count\",\n \"an-item-with-nothing-selected-in-its-panel-still-supports-direct-single-item-move\",\n \"search-and-pagination-inside-a-panel-are-product-composition-not-this-contract\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
|
1
|
+
{"version":3,"file":"transfer-list.js","sourceRoot":"","sources":["../src/transfer-list.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type {\n BehaviorContract,\n CheckboxState,\n CollectionKey,\n SelectItemDescriptor,\n} from \"./behaviors.js\";\nimport {\n getCheckboxNextState,\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n} from \"./selection-helpers.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport { validateCollection } from \"./collection.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `docs/transfer-list.md`: two lists sharing one item set, split by whether\n * each id is in `targetKeys`. This is the same shape CheckboxGroup already\n * commits to (`ReadonlySet<Id>`, no third \"which panel\" enum to keep in\n * sync with membership) — one source of truth instead of two arrays that can\n * drift apart when an item is moved.\n */\nexport type TransferListPanel = \"source\" | \"target\";\n\nexport type TransferListDescriptor<Id extends CollectionKey = CollectionKey> = Readonly<{\n items: readonly SelectItemDescriptor<Id>[];\n targetKeys: ReadonlySet<Id>;\n}>;\n\nexport type TransferListSelection<Id extends CollectionKey = CollectionKey> = Readonly<{\n source: ReadonlySet<Id>;\n target: ReadonlySet<Id>;\n}>;\n\nfunction withPanelSelection<Id extends CollectionKey>(\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n next: ReadonlySet<Id>,\n): TransferListSelection<Id> {\n return panel === \"source\"\n ? { source: next, target: selection.target }\n : { source: selection.source, target: next };\n}\n\nfunction assertKnownIds<Id extends CollectionKey>(\n label: string,\n ids: ReadonlySet<Id>,\n knownIds: ReadonlySet<Id>,\n): void {\n for (const id of ids) {\n if (!knownIds.has(id)) {\n throw new RangeError(`TransferList ${label} references an unknown item id: ${id}`);\n }\n }\n}\n\n/** An item already sitting entirely inside one panel (including all-in-target\n * or all-in-source) is valid — nothing here requires either panel to be\n * non-empty, and a disabled item is valid in either panel even though it can\n * never be moved by the UI. */\nexport function validateTransferListDescriptor<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n): void {\n validateCollection({ items: descriptor.items });\n const knownIds = new Set(descriptor.items.map((item) => item.id));\n assertKnownIds(\"targetKeys\", descriptor.targetKeys, knownIds);\n}\n\nexport type TransferListPanels<Id extends CollectionKey = CollectionKey> = Readonly<{\n source: readonly SelectItemDescriptor<Id>[];\n target: readonly SelectItemDescriptor<Id>[];\n}>;\n\n/** Splits by `targetKeys` membership, preserving each item's relative order from `items`. */\nexport function resolveTransferListPanels<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n): TransferListPanels<Id> {\n validateTransferListDescriptor(descriptor);\n const source: SelectItemDescriptor<Id>[] = [];\n const target: SelectItemDescriptor<Id>[] = [];\n for (const item of descriptor.items) {\n (descriptor.targetKeys.has(item.id) ? target : source).push(item);\n }\n return { source, target };\n}\n\nexport function validateTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n): void {\n const panels = resolveTransferListPanels(descriptor);\n assertKnownIds(\"selection.source\", selection.source, new Set(panels.source.map((i) => i.id)));\n assertKnownIds(\"selection.target\", selection.target, new Set(panels.target.map((i) => i.id)));\n}\n\n/** Toggles one row's checkbox within its own panel; the other panel's selection is untouched. */\nexport function toggleTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n id: Id,\n): TransferListSelection<Id> {\n const panels = resolveTransferListPanels(descriptor);\n const next = toggleCheckboxSelection(panels[panel], selection[panel], id);\n return withPanelSelection(selection, panel, next);\n}\n\n/**\n * Same tri-state `CheckboxState` `resolveDataTableSelectAllState` derives for\n * a table header — disabled rows excluded from both the denominator and the\n * count — generalized from \"all rows\" to \"this one panel's rows\".\n */\nexport function resolveTransferListSelectAllState<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n): CheckboxState {\n const items = resolveTransferListPanels(descriptor)[panel];\n const selectable = items.filter((item) => !item.disabled);\n if (selectable.length === 0) return false;\n const selectedCount = selectable.filter((item) => selection[panel].has(item.id)).length;\n if (selectedCount === 0) return false;\n return selectedCount === selectable.length ? true : \"mixed\";\n}\n\n/** Mixed-defaults-to-checked, matching `getCheckboxNextState`'s existing convention. */\nexport function toggleTransferListSelectAll<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n): TransferListSelection<Id> {\n const current = resolveTransferListSelectAllState(descriptor, selection, panel);\n const checkAll = getCheckboxNextState(current);\n const items = resolveTransferListPanels(descriptor)[panel];\n const next = checkAll\n ? new Set(items.filter((item) => !item.disabled).map((item) => item.id))\n : new Set<Id>();\n return withPanelSelection(selection, panel, next);\n}\n\nexport type TransferListMoveDirection = \"toTarget\" | \"toSource\";\n\nexport type TransferListMoveResult<Id extends CollectionKey = CollectionKey> = Readonly<{\n targetKeys: ReadonlySet<Id>;\n selection: TransferListSelection<Id>;\n /** In origin-panel order — the product formats the announcement string from this. */\n movedIds: readonly Id[];\n}>;\n\n/**\n * Moves every enabled, currently-selected id out of its origin panel.\n * Disabled ids are skipped rather than rejected (a disabled row can never\n * enter a selection through `toggleTransferListSelection`, but a product\n * could hand one to `defaultSelectedKeys`, and skipping mirrors\n * `toggleTreeCheckedSelection`'s disabled guard rather than throwing on it).\n * Moved ids are cleared from the origin panel's selection and deliberately\n * left unselected in the destination panel — a move commits a value, it does\n * not also commit a new pending selection the user did not make.\n */\nexport function moveTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n direction: TransferListMoveDirection,\n): TransferListMoveResult<Id> {\n validateTransferListSelection(descriptor, selection);\n const fromPanel: TransferListPanel = direction === \"toTarget\" ? \"source\" : \"target\";\n const fromItems = resolveTransferListPanels(descriptor)[fromPanel];\n const movedIds = fromItems\n .filter((item) => !item.disabled && selection[fromPanel].has(item.id))\n .map((item) => item.id);\n\n if (movedIds.length === 0) {\n return { targetKeys: descriptor.targetKeys, selection, movedIds: [] };\n }\n\n const movedIdSet = new Set(movedIds);\n const targetKeys =\n direction === \"toTarget\"\n ? new Set([...descriptor.targetKeys, ...movedIds])\n : new Set([...descriptor.targetKeys].filter((id) => !movedIdSet.has(id)));\n\n const nextFromSelection = new Set(\n [...selection[fromPanel]].filter((id) => !movedIdSet.has(id)),\n );\n return {\n targetKeys,\n selection: withPanelSelection(selection, fromPanel, nextFromSelection),\n movedIds,\n };\n}\n\n/**\n * Where focus lands once the moved rows are gone from a panel: the item that\n * slid into the first removed row's position, so keyboard users keep moving\n * down the list without their focus jumping to the top or vanishing. `null`\n * means the panel is now empty — the renderer falls back to the panel's\n * empty-state message or the move button, never to the document body.\n */\nexport function resolveTransferListFocusAfterMove<Id extends CollectionKey>(\n remainingPanelItems: readonly SelectItemDescriptor<Id>[],\n removedIndex: number,\n): Id | null {\n if (remainingPanelItems.length === 0) return null;\n const clamped = Math.min(Math.max(removedIndex, 0), remainingPanelItems.length - 1);\n return remainingPanelItems[clamped]!.id;\n}\n\n/** Drops selected ids that left either panel (item removed from `items` entirely). */\nexport function reconcileTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n): TransferListSelection<Id> {\n const panels = resolveTransferListPanels(descriptor);\n const source = reconcileCheckboxSelection(panels.source, selection.source);\n const target = reconcileCheckboxSelection(panels.target, selection.target);\n if (source === selection.source && target === selection.target) return selection;\n return { source, target };\n}\n\nexport const transferListRecipe = {\n slots: [\n \"root\",\n \"panel\",\n \"panelHeader\",\n \"panelTitle\",\n \"panelCount\",\n \"list\",\n \"item\",\n \"itemCheckbox\",\n \"emptyState\",\n \"moveControls\",\n \"moveButton\",\n ] as const,\n panel: {\n background: semanticColors.canvas,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n radius: \"md\" as const,\n },\n panelHeader: {\n minHeight: 44,\n paddingHorizontal: spacing.sm,\n title: { color: semanticColors.content.body, textVariant: \"body\" as const },\n count: { color: semanticColors.content.secondary, textVariant: \"label\" as const },\n },\n item: collectionItemContract,\n emptyState: { color: semanticColors.content.secondary, textVariant: \"label\" as const },\n moveControls: { gap: spacing.sm },\n moveButton: {\n minTarget: control.minTouchTarget,\n color: semanticColors.content.brand,\n disabledOpacity: 0.4,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\n \"root\",\n \"panel\",\n \"panelHeader\",\n \"panelTitle\",\n \"panelCount\",\n \"list\",\n \"item\",\n \"itemCheckbox\",\n \"emptyState\",\n \"moveControls\",\n \"moveButton\",\n ];\n panel: { background: ColorReference; border: ColorReference; borderWidth: number; radius: keyof typeof radius };\n panelHeader: {\n minHeight: number;\n paddingHorizontal: number;\n title: { color: ColorReference; textVariant: \"body\" };\n count: { color: ColorReference; textVariant: \"label\" };\n };\n item: typeof collectionItemContract;\n emptyState: { color: ColorReference; textVariant: \"label\" };\n moveControls: { gap: number };\n moveButton: { minTarget: number; color: ColorReference; disabledOpacity: number };\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const transferListBehavior = {\n controlled: [\"targetKeys\", \"defaultTargetKeys\", \"onTargetKeysChange\"],\n inputs: [\"items\"],\n events: [\"onMove\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"empty\", \"filled\", \"selected\", \"mixed\"],\n },\n web: {\n roles: [\"group\", \"listbox\", \"option\", \"checkbox\", \"button\"],\n keyboard: [\"Tab\", \"Space\", \"Enter\", \"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"],\n focus: \"roving\",\n },\n native: {\n roles: [\"list\", \"checkbox\", \"button\"],\n states: [\"disabled\", \"selected\", \"checked\"],\n actions: [\"toggle\", \"toggleSelectAll\", \"moveSelection\", \"moveItem\"],\n },\n scenarios: [\n \"moving-is-reachable-entirely-by-keyboard-select-with-space-then-activate-the-move-button\",\n \"moving-a-single-focused-row-does-not-require-first-opening-multi-select\",\n \"focus-after-a-move-lands-on-the-item-that-slid-into-the-removed-rows-position\",\n \"focus-after-emptying-a-panel-falls-back-to-its-empty-state-never-lost-to-the-document\",\n \"every-move-emits-which-ids-moved-so-the-product-can-announce-a-formatted-sentence\",\n \"disabled-items-are-never-selectable-and-never-move\",\n \"moved-items-are-cleared-from-the-origin-panels-selection-and-left-unselected-at-the-destination\",\n \"select-all-in-a-panel-excludes-disabled-items-from-both-the-denominator-and-the-count\",\n \"an-item-with-nothing-selected-in-its-panel-still-supports-direct-single-item-move\",\n \"search-and-pagination-inside-a-panel-are-product-composition-not-this-contract\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
package/dist/tree.d.ts
CHANGED
|
@@ -161,7 +161,7 @@ export declare const treeRecipe: {
|
|
|
161
161
|
};
|
|
162
162
|
readonly highlightedBackground: Readonly<{
|
|
163
163
|
source: "theme";
|
|
164
|
-
key: "
|
|
164
|
+
key: "bg";
|
|
165
165
|
alpha?: number;
|
|
166
166
|
}>;
|
|
167
167
|
readonly focus: {
|
|
@@ -175,7 +175,7 @@ export declare const treeRecipe: {
|
|
|
175
175
|
};
|
|
176
176
|
readonly selectedBackground: Readonly<{
|
|
177
177
|
source: "theme";
|
|
178
|
-
key: "
|
|
178
|
+
key: "bg";
|
|
179
179
|
alpha?: number;
|
|
180
180
|
}>;
|
|
181
181
|
readonly selectedIndicator: Readonly<{
|
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,OAAgB,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,OAAgB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.8.0\" as const;\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function resolveVirtualWindow(count: number, rowHeight: number, viewportHeight: number, scrollTop: number, overscan?: number): {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
totalHeight: number;
|
|
5
|
+
offset: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function validateListKeys(keys: readonly string[], label: string): void;
|
|
8
|
+
export declare const virtualListRecipe: {
|
|
9
|
+
readonly layout: "fixed-row-window";
|
|
10
|
+
readonly overscan: 3;
|
|
11
|
+
readonly readingOrder: "source";
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=virtual-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virtual-list.d.ts","sourceRoot":"","sources":["../src/virtual-list.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAI;;;;;EAK7H;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,QAEtE;AACD,eAAO,MAAM,iBAAiB;;;;CAA+E,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function resolveVirtualWindow(count, rowHeight, viewportHeight, scrollTop, overscan = 3) {
|
|
2
|
+
if (!Number.isInteger(count) || count < 0 || !Number.isFinite(rowHeight) || rowHeight <= 0 || !Number.isFinite(viewportHeight) || viewportHeight <= 0 || !Number.isFinite(scrollTop) || scrollTop < 0 || !Number.isInteger(overscan) || overscan < 0)
|
|
3
|
+
throw new TypeError("Invalid VirtualList dimensions");
|
|
4
|
+
const totalHeight = count * rowHeight;
|
|
5
|
+
const offset = Math.min(scrollTop, Math.max(0, totalHeight - viewportHeight));
|
|
6
|
+
return { start: Math.max(0, Math.floor(offset / rowHeight) - overscan), end: Math.min(count, Math.ceil((offset + viewportHeight) / rowHeight) + overscan), totalHeight, offset };
|
|
7
|
+
}
|
|
8
|
+
export function validateListKeys(keys, label) {
|
|
9
|
+
if (!label.trim() || new Set(keys).size !== keys.length || keys.some(key => !key.trim()))
|
|
10
|
+
throw new TypeError("List needs a label and unique nonempty keys");
|
|
11
|
+
}
|
|
12
|
+
export const virtualListRecipe = { layout: "fixed-row-window", overscan: 3, readingOrder: "source" };
|
|
13
|
+
//# sourceMappingURL=virtual-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virtual-list.js","sourceRoot":"","sources":["../src/virtual-list.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,SAAiB,EAAE,cAAsB,EAAE,SAAiB,EAAE,QAAQ,GAAG,CAAC;IAC5H,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,gCAAgC,CAAC,CAAC;IAC5S,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC;IAC9E,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,cAAc,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AACnL,CAAC;AACD,MAAM,UAAU,gBAAgB,CAAC,IAAuB,EAAE,KAAa;IACrE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,6CAA6C,CAAC,CAAC;AAC/J,CAAC;AACD,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,MAAM,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAW,CAAC","sourcesContent":["export function resolveVirtualWindow(count: number, rowHeight: number, viewportHeight: number, scrollTop: number, overscan = 3) {\n if (!Number.isInteger(count) || count < 0 || !Number.isFinite(rowHeight) || rowHeight <= 0 || !Number.isFinite(viewportHeight) || viewportHeight <= 0 || !Number.isFinite(scrollTop) || scrollTop < 0 || !Number.isInteger(overscan) || overscan < 0) throw new TypeError(\"Invalid VirtualList dimensions\");\n const totalHeight = count * rowHeight;\n const offset = Math.min(scrollTop, Math.max(0, totalHeight - viewportHeight));\n return { start: Math.max(0, Math.floor(offset / rowHeight) - overscan), end: Math.min(count, Math.ceil((offset + viewportHeight) / rowHeight) + overscan), totalHeight, offset };\n}\nexport function validateListKeys(keys: readonly string[], label: string) {\n if (!label.trim() || new Set(keys).size !== keys.length || keys.some(key => !key.trim())) throw new TypeError(\"List needs a label and unique nonempty keys\");\n}\nexport const virtualListRecipe = { layout: \"fixed-row-window\", overscan: 3, readingOrder: \"source\" } as const;\n"]}
|
package/docs/anchor.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Anchor — 같은 문서 안의 목차
|
|
2
2
|
|
|
3
|
-
2026-09-16
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
2026-09-16 Web 라이브러리를 beta로 시작했다. 실제 browser keyboard·fragment 복구와 긴 문구
|
|
4
|
+
증거가 보강되어 2026-09-29 Web renderer를 stable로 승격한다. 소비 제품 채택과 보조기기 검증은
|
|
5
|
+
별도 release QA다.
|
|
6
6
|
|
|
7
7
|
[Ant Design Anchor](https://ant.design/components/anchor/)의 문서 내 링크·현재 위치·스크롤
|
|
8
8
|
영역 구분을 비교했습니다. HJM은 content-brand 글자와 얇은 논리 방향 표시선으로 위치를
|
|
@@ -56,4 +56,5 @@ Web 전용입니다. Native 라우팅·목록 위치 이동은 해당 플랫폼
|
|
|
56
56
|
|
|
57
57
|
`Patterns/Anchor`는 세 부분으로 나눈 읽기 가이드와 별도 스크롤 영역을 제공합니다.
|
|
58
58
|
브라우저 테스트는 위치 동기화·짧은 마지막 부분·offset·reduced motion·임시 focus 복원·
|
|
59
|
-
fragment 복구·나중에 삽입된
|
|
59
|
+
fragment 복구·나중에 삽입된 대상·좁은 목차의 긴 label 줄바꿈·Tab/Enter 활성화와 문서 스크롤을 검사합니다.
|
|
60
|
+
실제 제품 문서 채택·보조기기 검증은 남아 있습니다.
|
|
@@ -89,8 +89,8 @@ evidence registry로 판단합니다.
|
|
|
89
89
|
- planned only: 모든 target이 planned
|
|
90
90
|
|
|
91
91
|
2026-09-18 SidePanel·Splitter·Tour·Tree·TransferList·Mentions·CommandPalette·DataTable renderer까지 추가한 뒤 status 기반 분포는
|
|
92
|
-
**fully mature
|
|
93
|
-
planned only
|
|
92
|
+
**fully mature 62 / partial maturity 0 /
|
|
93
|
+
planned only 8**입니다. decomposed Drawer의 두 갈래(Sheet·SidePanel)가 모두 구현되면서
|
|
94
94
|
partial maturity가 비었습니다 — 0은 "부분 구현이 없다"는 뜻이고 planned only 20은 그대로입니다. 따라서 73/73 tracking은 73개 구현 완료를 의미하지 않습니다.
|
|
95
95
|
홈과 Component Explorer는 이 수치를 분리해 표시합니다. 이 숫자는 source inventory 수가
|
|
96
96
|
아니라 HJM target의 maturity에서 계산하므로 catalog status가 바뀌면 함께 갱신합니다.
|
package/docs/architecture.md
CHANGED
|
@@ -56,11 +56,15 @@ Typography recipe의 `fontWeight`·`selectedFontWeight`·`checkedFontWeight`는
|
|
|
56
56
|
|
|
57
57
|
## 지원 단계
|
|
58
58
|
|
|
59
|
-
- `stable`:
|
|
60
|
-
- `beta`:
|
|
59
|
+
- `stable`: 공개 API와 동작을 SemVer로 지원하며 해당 renderer의 필수 검증을 통과함
|
|
60
|
+
- `beta`: 공개 API·동작 또는 renderer 검증에 구체적인 미완료 항목이 있음
|
|
61
61
|
- `planned`: 범위에 포함되지만 API를 아직 안정화하지 않음
|
|
62
62
|
- `deprecated`: 새 사용을 막고 대체 경로와 제거 예정 버전을 문서화한 호환 계약
|
|
63
63
|
|
|
64
|
+
제품 채택 수·제품 배포·제품 전체 QA는 성숙도 관문이 아닙니다. 승격 기준은
|
|
65
|
+
[stable 승격 절차](stable-promotion.md) 한 곳에서 관리합니다. 2026-09-29에 서로 달랐던
|
|
66
|
+
채택 수 조건을 제거했으며 제품 검증은 소비 앱의 릴리스 책임으로 분리했습니다.
|
|
67
|
+
|
|
64
68
|
현재 전체 범위와 목표 플랫폼 분류는 `componentCatalog`가 기계 판독 가능한 형태로 제공합니다.
|
|
65
69
|
계획된 컴포넌트를 catalog에 올리는 것은 구현 완료를 의미하지 않습니다.
|
|
66
70
|
`recipeRegistry`가 실제 recipe와 catalog 이름을 타입으로 묶어 오타나 유령 계약을 막습니다.
|
package/docs/authoring-brief.md
CHANGED
|
@@ -57,8 +57,9 @@
|
|
|
57
57
|
## maturity에 대해
|
|
58
58
|
|
|
59
59
|
당신의 산출물로 컴포넌트는 **"계약+recipe 준비됨"**이 된다. catalog의 `planned → beta`
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
승격에는 실제 first-party renderer export와 실행되는 default proof가 필요하다.
|
|
61
|
+
2026-09-29 개정에서 제품 vertical slice는 승격 조건에서 분리했다. stable은
|
|
62
|
+
[승격 기준](stable-promotion.md)을 따르며 recipe만으로 구현 완료를 주장하지 않는다.
|
|
62
63
|
|
|
63
64
|
## 게이트
|
|
64
65
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
`BottomNavigation`은 콘텐츠 panel을 바꾸는 `Tabs`가 아니라 앱의 안정된 최상위 route를
|
|
4
4
|
이동합니다. 같은 destination 의미를 Web의 link와 React Native navigator tab으로 적응시키며,
|
|
5
|
-
route state는 제품 router 한 곳에서만 소유합니다.
|
|
6
|
-
|
|
7
|
-
`
|
|
5
|
+
route state는 제품 router 한 곳에서만 소유합니다. 각 renderer의 환경 matrix, Web route-link
|
|
6
|
+
키보드 동작, Native navigate/reselect host action과 긴 목적지 label 회귀가 연결되어 catalog
|
|
7
|
+
status는 `stable`입니다. 구체적인 router·navigator와 결합한 화면 동작은 소비 앱 QA입니다.
|
|
8
8
|
|
|
9
9
|
## Descriptor와 configuration
|
|
10
10
|
|
|
@@ -46,9 +46,8 @@ const descriptor = {
|
|
|
46
46
|
예약하며 action을 collection에 추가하지 않습니다.
|
|
47
47
|
|
|
48
48
|
Web renderer도 이름이 있는 실제 link landmark, `aria-current`, modifier click 보존,
|
|
49
|
-
keyboard viewport hide와 center-gap 배치를 first-party SSR·browser test로
|
|
50
|
-
|
|
51
|
-
debt로 남습니다.
|
|
49
|
+
keyboard viewport hide와 center-gap 배치를 first-party SSR·browser test로 검증합니다. 제품
|
|
50
|
+
router와 결합한 브라우저 릴리스 증거는 컴포넌트 API 성숙도가 아니라 소비 앱 QA에 속합니다.
|
|
52
51
|
|
|
53
52
|
## Route source of truth
|
|
54
53
|
|
|
@@ -111,6 +110,8 @@ status/live role이나 별도 accessibility label을 추가하면 같은 정보
|
|
|
111
110
|
visual label은 최대 `1.4×`까지만 커지고, 원문 전체는 item의 접근성 이름으로 유지합니다.
|
|
112
111
|
- safe-area bottom inset은 recipe의 최소 padding에 더합니다. `max(base, inset)`으로 대체하지
|
|
113
112
|
않습니다.
|
|
113
|
+
- 긴 label은 320px viewport의 Web geometry matrix와 React Native renderer matrix에서 확인합니다.
|
|
114
|
+
Web link의 전체 이름/visible label 보존과 Native accessibility name/Text 보존은 전용 interaction proof에 둡니다.
|
|
114
115
|
- 기본 keyboard behavior는 `hide`입니다. software keyboard 위로 bottom navigation을 밀어
|
|
115
116
|
올려 입력 영역을 가리지 않습니다.
|
|
116
117
|
- RTL에서는 item 순서와 badge의 inline-end anchor가 함께 뒤집힙니다. icon 자체의 mirror 여부는
|
package/docs/breadcrumb.md
CHANGED
|
@@ -78,5 +78,8 @@ Web에서는:
|
|
|
78
78
|
돌아오는 작동 예제입니다. 제품 채택이나 stable 증거로 계산하지 않습니다.
|
|
79
79
|
|
|
80
80
|
[WAI Breadcrumb](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/)의 landmark·조상 링크·
|
|
81
|
-
현재 위치 의미를 확인했습니다.
|
|
82
|
-
|
|
81
|
+
현재 위치 의미를 확인했습니다. Web renderer evidence는 component 자신의 긴 label이 320px
|
|
82
|
+
컨테이너 안에서 넘치지 않는지 확인하고, 실제 Tab/Shift+Tab/Enter로 조상 anchor를 따라가며
|
|
83
|
+
현재 위치가 tab stop이나 link가 아닌 `aria-current="page"` 텍스트로 남는지 확인합니다.
|
|
84
|
+
별도 브라우저 회귀는 실제 anchor URL, 장식 구분자, 320px/2배 글자·RTL 줄바꿈을 다룹니다.
|
|
85
|
+
실제 제품 라우팅·보조기기 검증은 소비 앱의 별도 범위입니다. Web renderer evidence의 필수 시나리오 누락은 0이며 Breadcrumb는 Web-only stable입니다.
|
package/docs/calendar.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Calendar
|
|
2
2
|
|
|
3
|
-
2026-09-16 · React / React Native beta
|
|
3
|
+
2026-09-16 · React / React Native renderer beta로 시작했다. 2026-09-29 두 surface의 키·host action·긴 title proof를 보강해 stable로 승격한다. 제품 채택은 별도 관측이다.
|
|
4
4
|
제품의 실제 채택·기기 검증·npm 게시를 뜻하지 않는다.
|
|
5
5
|
|
|
6
6
|
## 설계와 참조
|
|
@@ -69,7 +69,8 @@ Web의 `autoFocus`는 기본 false이며 DatePicker가 열린 격자에만 true
|
|
|
69
69
|
- 7열의 최소 touch target을 유지한다. 폭이 부족하면 격자만 가로 스크롤하고 본문은 넘치지 않는다.
|
|
70
70
|
- 셀 높이를 고정하지 않아 2배 글자나 짧은 제품 콘텐츠가 잘리지 않는다.
|
|
71
71
|
- Web: grid / row / columnheader / gridcell, 하나의 날짜 tab stop, visible focus.
|
|
72
|
-
- Native: 각 날짜는 독립 접근 가능한 button.
|
|
72
|
+
- Native: 각 날짜는 독립 접근 가능한 button. `focusDate`는 Native 접근성 초점 요청으로,
|
|
73
|
+
RN Web에서는 DOM 초점으로 전달한다. 이는 하드웨어 키보드 방향키 탐색 지원의 증거가 아니다.
|
|
73
74
|
- 단일 날짜·한 페이지 계약이다. range, 연간 view, 비그레고리력 계산 엔진은 이 컴포넌트에 없다.
|
|
74
75
|
제품이 준비한 날짜 배열의 의미를 보존하며 HJM은 날짜 형식·중복·격자 모양을 검증한다.
|
|
75
76
|
|
|
@@ -77,6 +78,8 @@ Web의 `autoFocus`는 기본 false이며 DatePicker가 열린 격자에만 true
|
|
|
77
78
|
|
|
78
79
|
Web 테스트는 비활성 날짜 focus/활성화 분리, 하루/주/Home/End, 경계에서 새 페이지의 지정 날짜로
|
|
79
80
|
focus 이동, controlled 선택 보존, 320px·2배 글자·RTL 최소 target과 본문 overflow를 검사한다.
|
|
80
|
-
Native 테스트는 개별 날짜의 접근성·비활성·선택, 명시적 월 이동, 크기와 스크롤
|
|
81
|
+
Native 테스트는 개별 날짜의 접근성·비활성·선택, 명시적 월 이동, 크기와 스크롤 경계,
|
|
82
|
+
긴 월 제목의 잘림 방지, `focusDate`의 접근성 초점 요청을 검사한다. 실제 VoiceOver/TalkBack
|
|
83
|
+
기기나 하드웨어 키보드 탐색 검증을 뜻하지 않는다.
|
|
81
84
|
DatePicker의 기존 선택·닫기·방향키 회귀와 controlled-open 읽기 전용/disabled 활성화 방지도 검사한다.
|
|
82
85
|
전체 패키지·Showcase 검증 및 브라우저 확인은 `react-native-completion.md` 실행 기록에 남긴다.
|
package/docs/carousel.md
CHANGED
|
@@ -127,8 +127,8 @@ mount policy 참고) — 새로 만든 개념이 아니라 이미 검증된 패
|
|
|
127
127
|
|
|
128
128
|
## 검증 화면
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
이 조사 당시 제품 채택은 미확인이었다. 후보는 야잘알 홈의 내 구단 경기 스트립이다.
|
|
131
|
+
2026-09-29부터 제품 채택은 관측으로 분리하며 [현재 승격 기준](stable-promotion.md)을 따른다.
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
## React / React Native renderer (2026-09-16)
|
package/docs/cascader.md
CHANGED
|
@@ -108,3 +108,9 @@ collection + tri-state 판정 모듈)으로 확정됐고, Tree renderer가 들
|
|
|
108
108
|
작동 예제는 Showcase `Patterns/Tree`의 Cascader 화면이다. "만들지 않는다"는 판정 자체는
|
|
109
109
|
그대로다 — 바뀐 것은 흡수하는 쪽이 컴포넌트가 아니라 조합이라는 점뿐이다.
|
|
110
110
|
|
|
111
|
+
|
|
112
|
+
## Native 조합 완성 — 2026-09-29
|
|
113
|
+
|
|
114
|
+
`showcase/native/src/Cascader.stories.tsx`는 단계별 Select로 도시→지역을 선택한다.
|
|
115
|
+
상위 항목 변경 시 하위 선택을 비워 이전 경로가 남지 않게 하고 전체 초기화를 제공한다.
|
|
116
|
+
기존 Web Tree 조합과 함께 제공하며 새 primitive나 별도 상태 계약은 만들지 않는다.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# 제외 후보 삭제 — 2026-09-29
|
|
2
|
+
|
|
3
|
+
사용자가 도입 제외 항목을 삭제하도록 요청했다. Chart, AppProvider, BorderBeam, Utility를
|
|
4
|
+
활성 카탈로그·Planned 집계·쇼케이스에서 제거했다. 구현 계획이 없는 후보를 미래 작업처럼
|
|
5
|
+
표시하던 문제가 원인이다. 기존 dataviz 토큰, DesignSystemProvider, Toast/Dialog API는 유지한다.
|
|
6
|
+
외부 참조표에서도 App/BorderBeam/Util 후보를 제외해 지원 범위는 70개다. 이 표는 Ant Design
|
|
7
|
+
전체 73개와의 100% 구현 주장이 아니다. 이전 삭제 항목의 이력은 Git 기록으로 남긴다.
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
`planned`은 "아직 구현하지 않았지만 구현할 것"을 뜻해 왔다. 그런데 저작 과정에서 이 뜻이
|
|
7
7
|
거짓인 행이 실제로 쌓였다:
|
|
8
8
|
|
|
9
|
-
- `AppProvider` — 런타임(Context+훅)뿐이라 계약할 값 타입조차 남지 않는다(`docs/
|
|
9
|
+
- `AppProvider` — 런타임(Context+훅)뿐이라 계약할 값 타입조차 남지 않는다(`docs/catalog-cleanup.md`).
|
|
10
10
|
- `Utility` — antd `Util`이 가리키는 문제(토큰을 코드에서 읽는 법) 자체가 이 패키지의
|
|
11
|
-
기존 정적 export로 이미 해소돼 있다(`docs/
|
|
11
|
+
기존 정적 export로 이미 해소돼 있다(`docs/catalog-cleanup.md`).
|
|
12
12
|
- `BorderBeam` — antd에 실재하는 컴포넌트이지만(crosswalk은 정확하다, 정정: 이전 판은
|
|
13
13
|
"오염됐다"고 잘못 주장했었다), 상시 반복 장식 모션이 `docs/identity.md`와 정면으로
|
|
14
|
-
충돌해 만들지 않기로 확정했다(`docs/
|
|
14
|
+
충돌해 만들지 않기로 확정했다(`docs/catalog-cleanup.md`).
|
|
15
15
|
|
|
16
16
|
세 행 모두 **"언젠가 화면이 생기면 만든다"가 아니라 "화면이 생겨도 안 만든다"**다. 그런데
|
|
17
17
|
행을 지울 수도 없다 — `component-references.test.ts`의
|
package/docs/catalog-freeze.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "카탈로그 확장 동결. 이 목록에 없는 이름을 catalog.ts에 추가하면 test/catalog-freeze.test.ts가 실패한다.",
|
|
2
|
+
"$comment": "카탈로그 확장 동결. 이 목록에 없는 이름을 catalog.ts에 추가하면 test/catalog-freeze.test.ts가 실패한다. 2026-09-29 사용자 요청으로 도입 제외 후보 네 개 삭제; 기존 dataviz/provider API는 유지.",
|
|
3
3
|
"frozenAt": "2026-09-26",
|
|
4
|
-
"reason": "2026-09-26 설계 점검: 111개 중 stable 4개였고, 세 제품 이상이 쓰는 약 30개가 beta에 묶여 있었다. 1.5.0에서 13개를 실제 증거로 승격했지만
|
|
4
|
+
"reason": "2026-09-26 설계 점검: 111개 중 stable 4개였고, 세 제품 이상이 쓰는 약 30개가 beta에 묶여 있었다. 1.5.0에서 13개를 실제 증거로 승격했지만 입력·접근성 동작 증거가 필요한 핵심(Checkbox, Switch, Dialog, Sheet, SegmentedControl, RadioGroup, Link, PasswordField 등)은 아직 beta다. 폭을 넓히기 전에 이것들을 먼저 올린다.",
|
|
5
5
|
"unfreezeWhen": "위 핵심 beta가 stable이 되면 동결을 푼다. 그 전에 추가가 필요하면 이 파일의 exceptions에 이름·날짜·근거(ADR 또는 이슈)를 적는다.",
|
|
6
|
-
"exceptions": [
|
|
6
|
+
"exceptions": [
|
|
7
|
+
{
|
|
8
|
+
"name": "ThinkingOrb",
|
|
9
|
+
"date": "2026-09-29",
|
|
10
|
+
"rationale": "사용자가 Thinking Orbs의 HJM 흡수를 명시적으로 요청함. 기존 Spinner를 교체하지 않는 AI 상태 표시이며 선택형 경로로 격리. docs/thinking-orb.md"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
7
13
|
"names": [
|
|
8
14
|
"Accordion",
|
|
9
15
|
"Affix",
|
|
10
16
|
"Agreement",
|
|
11
17
|
"AlertDialog",
|
|
12
18
|
"Anchor",
|
|
13
|
-
"AppProvider",
|
|
14
19
|
"AspectRatio",
|
|
15
20
|
"Asset",
|
|
16
21
|
"AuthProviderButton",
|
|
17
22
|
"AuthScreenLayout",
|
|
18
23
|
"Avatar",
|
|
19
24
|
"Badge",
|
|
20
|
-
"BorderBeam",
|
|
21
25
|
"BottomCTA",
|
|
22
26
|
"BottomInfo",
|
|
23
27
|
"BottomNavigation",
|
|
@@ -27,7 +31,6 @@
|
|
|
27
31
|
"Card",
|
|
28
32
|
"Carousel",
|
|
29
33
|
"Cascader",
|
|
30
|
-
"Chart",
|
|
31
34
|
"Checkbox",
|
|
32
35
|
"CheckboxGroup",
|
|
33
36
|
"Chip",
|
|
@@ -112,7 +115,6 @@
|
|
|
112
115
|
"Tree",
|
|
113
116
|
"TreeSelect",
|
|
114
117
|
"UploadItem",
|
|
115
|
-
"Utility",
|
|
116
118
|
"VirtualList",
|
|
117
119
|
"VisuallyHidden",
|
|
118
120
|
"Watermark"
|
package/docs/collapsible.md
CHANGED
|
@@ -17,3 +17,15 @@ chrome이 따라오고 그걸 다시 CSS로 지우게 된다. 그래서 별도
|
|
|
17
17
|
가리키지 않으면 사용자는 방금 나타난 것을 찾아야 한다.
|
|
18
18
|
|
|
19
19
|
**Web·Native 공통.** 네이티브도 `button` role에 expanded 상태를 그대로 쓴다.
|
|
20
|
+
|
|
21
|
+
## Renderer evidence
|
|
22
|
+
|
|
23
|
+
- Web keyboard proof tabs to the native button, toggles with Enter, then closes with Space;
|
|
24
|
+
it checks `aria-expanded`, the controlled region relationship, and that closed content leaves
|
|
25
|
+
the tree. The narrow long-copy case places a long unbroken label in the trigger to verify that
|
|
26
|
+
disclosure chrome does not widen its container.
|
|
27
|
+
- Native renderer tests inspect the `button` role and expanded state, invoke the rendered
|
|
28
|
+
`Pressable` action, and verify that opening and closing adds and removes the content. This is
|
|
29
|
+
host-renderer evidence; it does not claim physical keyboard, VoiceOver, or TalkBack verification.
|
|
30
|
+
- Native and Web long-copy fixtures put the long label in the trigger itself. The child body stays
|
|
31
|
+
short so the scenario tests the constrained interactive label rather than arbitrary content.
|
package/docs/command-palette.md
CHANGED
|
@@ -113,12 +113,14 @@ heading을 가지므로) — CommandPalette는 다르다: `role="dialog"` 표면
|
|
|
113
113
|
|
|
114
114
|
## 검증 화면
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
이 조사 당시 제품 채택은 미확인이었다. 2026-09-29부터 제품 채택은 관측으로 분리하며,
|
|
117
|
+
현재 성숙도는 catalog와 [승격 기준](stable-promotion.md)을 따른다.
|
|
117
118
|
|
|
118
119
|
## Web renderer (2026-09-18)
|
|
119
120
|
|
|
120
|
-
`@hjmds/react/command-palette`의 `CommandPalette`가 이 계약을 실행한다.
|
|
121
|
-
|
|
121
|
+
`@hjmds/react/command-palette`의 `CommandPalette`가 이 계약을 실행한다. Web은 키보드 검색·
|
|
122
|
+
실행·dismiss와 320px의 긴 한글 명령 설명 proof를 통과해 2026-09-29 stable로 승격한다.
|
|
123
|
+
Native는 `unsupported`다. 전역 단축키는 제품 소유다.
|
|
122
124
|
|
|
123
125
|
- **모달 takeover다.** Dialog·Sheet·SidePanel과 같은 모달 스택·스크롤 락·배경 격리를
|
|
124
126
|
공유한다(`packages/react/src/modal.tsx`). 별도 `modal` 축은 없다.
|