@hjmds/design-contracts 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/dist/alert-dialog.d.ts +102 -0
- package/dist/alert-dialog.d.ts.map +1 -0
- package/dist/alert-dialog.js +136 -0
- package/dist/alert-dialog.js.map +1 -0
- package/dist/base-recipes.d.ts +184 -0
- package/dist/base-recipes.d.ts.map +1 -0
- package/dist/base-recipes.js +129 -0
- package/dist/base-recipes.js.map +1 -0
- package/dist/behaviors.d.ts +1254 -0
- package/dist/behaviors.d.ts.map +1 -0
- package/dist/behaviors.js +972 -0
- package/dist/behaviors.js.map +1 -0
- package/dist/bottom-navigation-defaults.d.ts +13 -0
- package/dist/bottom-navigation-defaults.d.ts.map +1 -0
- package/dist/bottom-navigation-defaults.js +10 -0
- package/dist/bottom-navigation-defaults.js.map +1 -0
- package/dist/bottom-navigation.d.ts +114 -0
- package/dist/bottom-navigation.d.ts.map +1 -0
- package/dist/bottom-navigation.js +223 -0
- package/dist/bottom-navigation.js.map +1 -0
- package/dist/breadcrumb.d.ts +97 -0
- package/dist/breadcrumb.d.ts.map +1 -0
- package/dist/breadcrumb.js +99 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/calendar.d.ts +285 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +297 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +39 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +50 -0
- package/dist/card.js.map +1 -0
- package/dist/carousel.d.ts +180 -0
- package/dist/carousel.d.ts.map +1 -0
- package/dist/carousel.js +172 -0
- package/dist/carousel.js.map +1 -0
- package/dist/catalog.d.ts +7158 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +220 -0
- package/dist/catalog.js.map +1 -0
- package/dist/collection.d.ts +123 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +211 -0
- package/dist/collection.js.map +1 -0
- package/dist/color-references.d.ts +46 -0
- package/dist/color-references.d.ts.map +1 -0
- package/dist/color-references.js +39 -0
- package/dist/color-references.js.map +1 -0
- package/dist/colors.d.ts +56 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +90 -0
- package/dist/colors.js.map +1 -0
- package/dist/command-palette.d.ts +240 -0
- package/dist/command-palette.d.ts.map +1 -0
- package/dist/command-palette.js +97 -0
- package/dist/command-palette.js.map +1 -0
- package/dist/component-contracts.d.ts +137 -0
- package/dist/component-contracts.d.ts.map +1 -0
- package/dist/component-contracts.js +53 -0
- package/dist/component-contracts.js.map +1 -0
- package/dist/component-definitions.d.ts +135 -0
- package/dist/component-definitions.d.ts.map +1 -0
- package/dist/component-definitions.js +142 -0
- package/dist/component-definitions.js.map +1 -0
- package/dist/component-recipes.d.ts +3583 -0
- package/dist/component-recipes.d.ts.map +1 -0
- package/dist/component-recipes.js +1503 -0
- package/dist/component-recipes.js.map +1 -0
- package/dist/component-references.d.ts +416 -0
- package/dist/component-references.d.ts.map +1 -0
- package/dist/component-references.js +140 -0
- package/dist/component-references.js.map +1 -0
- package/dist/content-state.d.ts +104 -0
- package/dist/content-state.d.ts.map +1 -0
- package/dist/content-state.js +116 -0
- package/dist/content-state.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +80 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -0
- package/dist/counter-badge-recipe.js +44 -0
- package/dist/counter-badge-recipe.js.map +1 -0
- package/dist/counter-badge.d.ts +12 -0
- package/dist/counter-badge.d.ts.map +1 -0
- package/dist/counter-badge.js +21 -0
- package/dist/counter-badge.js.map +1 -0
- package/dist/data-table.d.ts +203 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +182 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +268 -0
- package/dist/date-picker.d.ts.map +1 -0
- package/dist/date-picker.js +168 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/description-list.d.ts +68 -0
- package/dist/description-list.d.ts.map +1 -0
- package/dist/description-list.js +85 -0
- package/dist/description-list.js.map +1 -0
- package/dist/design-system-provider.d.ts +80 -0
- package/dist/design-system-provider.d.ts.map +1 -0
- package/dist/design-system-provider.js +147 -0
- package/dist/design-system-provider.js.map +1 -0
- package/dist/evidence.d.ts +49 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +133 -0
- package/dist/evidence.js.map +1 -0
- package/dist/file-picker.d.ts +183 -0
- package/dist/file-picker.d.ts.map +1 -0
- package/dist/file-picker.js +224 -0
- package/dist/file-picker.js.map +1 -0
- package/dist/floating-action-button.d.ts +143 -0
- package/dist/floating-action-button.d.ts.map +1 -0
- package/dist/floating-action-button.js +149 -0
- package/dist/floating-action-button.js.map +1 -0
- package/dist/form.d.ts +143 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +206 -0
- package/dist/form.js.map +1 -0
- package/dist/foundations.d.ts +300 -0
- package/dist/foundations.d.ts.map +1 -0
- package/dist/foundations.js +238 -0
- package/dist/foundations.js.map +1 -0
- package/dist/grid.d.ts +75 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +133 -0
- package/dist/grid.js.map +1 -0
- package/dist/icon-button-recipe.d.ts +99 -0
- package/dist/icon-button-recipe.d.ts.map +1 -0
- package/dist/icon-button-recipe.js +53 -0
- package/dist/icon-button-recipe.js.map +1 -0
- package/dist/icon.d.ts +41 -0
- package/dist/icon.d.ts.map +1 -0
- package/dist/icon.js +147 -0
- package/dist/icon.js.map +1 -0
- package/dist/image.d.ts +82 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +100 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +118 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +118 -0
- package/dist/layout.js.map +1 -0
- package/dist/link.d.ts +57 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +142 -0
- package/dist/link.js.map +1 -0
- package/dist/load-more.d.ts +64 -0
- package/dist/load-more.d.ts.map +1 -0
- package/dist/load-more.js +124 -0
- package/dist/load-more.js.map +1 -0
- package/dist/mentions.d.ts +67 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +107 -0
- package/dist/mentions.js.map +1 -0
- package/dist/number-field.d.ts +208 -0
- package/dist/number-field.d.ts.map +1 -0
- package/dist/number-field.js +247 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +152 -0
- package/dist/otp-field.d.ts.map +1 -0
- package/dist/otp-field.js +117 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/pagination.d.ts +181 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +226 -0
- package/dist/pagination.js.map +1 -0
- package/dist/password-field.d.ts +187 -0
- package/dist/password-field.d.ts.map +1 -0
- package/dist/password-field.js +120 -0
- package/dist/password-field.js.map +1 -0
- package/dist/popover.d.ts +152 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +137 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress-recipe.d.ts +43 -0
- package/dist/progress-recipe.d.ts.map +1 -0
- package/dist/progress-recipe.js +16 -0
- package/dist/progress-recipe.js.map +1 -0
- package/dist/recipes.d.ts +31 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +46 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responsive.d.ts +27 -0
- package/dist/responsive.d.ts.map +1 -0
- package/dist/responsive.js +66 -0
- package/dist/responsive.js.map +1 -0
- package/dist/result.d.ts +111 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +97 -0
- package/dist/result.js.map +1 -0
- package/dist/selection-helpers.d.ts +16 -0
- package/dist/selection-helpers.d.ts.map +1 -0
- package/dist/selection-helpers.js +52 -0
- package/dist/selection-helpers.js.map +1 -0
- package/dist/semantic-colors.d.ts +275 -0
- package/dist/semantic-colors.d.ts.map +1 -0
- package/dist/semantic-colors.js +84 -0
- package/dist/semantic-colors.js.map +1 -0
- package/dist/sheet.d.ts +51 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +69 -0
- package/dist/sheet.js.map +1 -0
- package/dist/showcase.d.ts +153 -0
- package/dist/showcase.d.ts.map +1 -0
- package/dist/showcase.js +210 -0
- package/dist/showcase.js.map +1 -0
- package/dist/side-panel.d.ts +199 -0
- package/dist/side-panel.d.ts.map +1 -0
- package/dist/side-panel.js +111 -0
- package/dist/side-panel.js.map +1 -0
- package/dist/slider.d.ts +138 -0
- package/dist/slider.d.ts.map +1 -0
- package/dist/slider.js +150 -0
- package/dist/slider.js.map +1 -0
- package/dist/splitter.d.ts +113 -0
- package/dist/splitter.d.ts.map +1 -0
- package/dist/splitter.js +99 -0
- package/dist/splitter.js.map +1 -0
- package/dist/statistic.d.ts +41 -0
- package/dist/statistic.d.ts.map +1 -0
- package/dist/statistic.js +76 -0
- package/dist/statistic.js.map +1 -0
- package/dist/steps.d.ts +196 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +160 -0
- package/dist/steps.js.map +1 -0
- package/dist/tag.d.ts +128 -0
- package/dist/tag.d.ts.map +1 -0
- package/dist/tag.js +93 -0
- package/dist/tag.js.map +1 -0
- package/dist/timeline.d.ts +147 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +127 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toast.d.ts +164 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +529 -0
- package/dist/toast.js.map +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +9 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tooltip.d.ts +44 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +88 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tour.d.ts +218 -0
- package/dist/tour.d.ts.map +1 -0
- package/dist/tour.js +211 -0
- package/dist/tour.js.map +1 -0
- package/dist/transfer-list.d.ts +207 -0
- package/dist/transfer-list.d.ts.map +1 -0
- package/dist/transfer-list.js +193 -0
- package/dist/transfer-list.js.map +1 -0
- package/dist/tree-select.d.ts +78 -0
- package/dist/tree-select.d.ts.map +1 -0
- package/dist/tree-select.js +132 -0
- package/dist/tree-select.js.map +1 -0
- package/dist/tree.d.ts +206 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +223 -0
- package/dist/tree.js.map +1 -0
- package/dist/upload-item.d.ts +173 -0
- package/dist/upload-item.d.ts.map +1 -0
- package/dist/upload-item.js +156 -0
- package/dist/upload-item.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/docs/affix.md +63 -0
- package/docs/anchor.md +59 -0
- package/docs/ant-design-coverage.md +119 -0
- package/docs/app-provider.md +50 -0
- package/docs/app-rn-adoption.md +227 -0
- package/docs/architecture.md +320 -0
- package/docs/authoring-brief.md +87 -0
- package/docs/border-beam.md +66 -0
- package/docs/bottom-navigation.md +127 -0
- package/docs/breadcrumb.md +82 -0
- package/docs/calendar.md +154 -0
- package/docs/carousel.md +130 -0
- package/docs/cascader.md +93 -0
- package/docs/catalog-decision-status.md +306 -0
- package/docs/color-picker.md +72 -0
- package/docs/command-palette.md +116 -0
- package/docs/confirm-popover.md +93 -0
- package/docs/consistency-audit.md +383 -0
- package/docs/consumer-release-gate.md +90 -0
- package/docs/content-state.md +156 -0
- package/docs/context-panel.md +88 -0
- package/docs/cross-platform-core-normalization.md +118 -0
- package/docs/data-table.md +79 -0
- package/docs/date-picker.md +84 -0
- package/docs/description-list.md +75 -0
- package/docs/design-system-provider.md +139 -0
- package/docs/dropdown.md +78 -0
- package/docs/expansion-roadmap.md +285 -0
- package/docs/file-picker.md +59 -0
- package/docs/floating-action-button.md +111 -0
- package/docs/form.md +134 -0
- package/docs/generated/component-maturity.md +103 -0
- package/docs/generated/renderer-evidence.json +5597 -0
- package/docs/generated/renderer-evidence.md +132 -0
- package/docs/generated/showcase-manifest.json +3732 -0
- package/docs/icon.md +22 -0
- package/docs/identity.md +121 -0
- package/docs/image.md +84 -0
- package/docs/implementation-0.5.md +85 -0
- package/docs/layout-primitives.md +107 -0
- package/docs/layout.md +83 -0
- package/docs/library-reference-decisions.md +123 -0
- package/docs/link.md +67 -0
- package/docs/load-more.md +31 -0
- package/docs/mentions.md +82 -0
- package/docs/migration-0.2.md +108 -0
- package/docs/migration-0.3.md +72 -0
- package/docs/migration-0.5.md +82 -0
- package/docs/migration-0.6.md +197 -0
- package/docs/notification.md +54 -0
- package/docs/number-field.md +82 -0
- package/docs/otp-field.md +103 -0
- package/docs/pagination.md +139 -0
- package/docs/password-field.md +98 -0
- package/docs/popover.md +124 -0
- package/docs/promotion-candidates.md +169 -0
- package/docs/qr-code.md +69 -0
- package/docs/rating.md +58 -0
- package/docs/responsive-grid.md +90 -0
- package/docs/result.md +96 -0
- package/docs/showcase.md +97 -0
- package/docs/side-panel.md +71 -0
- package/docs/slider.md +79 -0
- package/docs/splitter.md +55 -0
- package/docs/statistic.md +16 -0
- package/docs/steps.md +115 -0
- package/docs/tag.md +57 -0
- package/docs/time-picker.md +88 -0
- package/docs/timeline.md +146 -0
- package/docs/toast.md +127 -0
- package/docs/tooltip.md +58 -0
- package/docs/tour.md +136 -0
- package/docs/transfer-list.md +94 -0
- package/docs/tree-select.md +101 -0
- package/docs/tree.md +123 -0
- package/docs/upload-item.md +68 -0
- package/docs/utility.md +53 -0
- package/docs/virtual-list.md +70 -0
- package/docs/watermark.md +58 -0
- package/package.json +402 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-only by roadmap decision, not an oversight: "Native 긴 목록에는 페이지
|
|
3
|
+
* 번호보다 LoadMore/infinite loading을 사용한다" (docs/load-more.md). Numbered
|
|
4
|
+
* pages assume a stable total and a pointing device/keyboard that can jump
|
|
5
|
+
* around; a touch list scrolling through hundreds of rows has neither. The two
|
|
6
|
+
* contracts do not need to know about each other — LoadMore never becomes
|
|
7
|
+
* Pagination and vice versa — but a product choosing between them should pick
|
|
8
|
+
* Pagination for a bounded, page-addressable result set (search results,
|
|
9
|
+
* admin tables) and LoadMore for an open-ended feed the user just keeps
|
|
10
|
+
* scrolling.
|
|
11
|
+
*/
|
|
12
|
+
export type PaginationByCount = Readonly<{
|
|
13
|
+
currentPage: number;
|
|
14
|
+
totalCount: number;
|
|
15
|
+
pageSize: number;
|
|
16
|
+
totalPages?: never;
|
|
17
|
+
siblingCount?: number;
|
|
18
|
+
boundaryCount?: number;
|
|
19
|
+
}>;
|
|
20
|
+
export type PaginationByPageCount = Readonly<{
|
|
21
|
+
currentPage: number;
|
|
22
|
+
totalPages: number;
|
|
23
|
+
totalCount?: never;
|
|
24
|
+
pageSize?: never;
|
|
25
|
+
siblingCount?: number;
|
|
26
|
+
boundaryCount?: number;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Required contract is exactly current page + (total count or total page
|
|
30
|
+
* count) + page size — nothing else. A page-size changer and a "jump to page"
|
|
31
|
+
* input are intentionally absent: neither has measured product demand, and
|
|
32
|
+
* both add a second way to reach the same currentPage/totalPages state this
|
|
33
|
+
* module already owns. If a product needs them, they compose as separate
|
|
34
|
+
* controls that call the same onPageChange — they are not missing API surface
|
|
35
|
+
* here, they are a deliberate exclusion (see docs/pagination.md).
|
|
36
|
+
*/
|
|
37
|
+
export type PaginationDescriptor = PaginationByCount | PaginationByPageCount;
|
|
38
|
+
export type PaginationLabels = Readonly<{
|
|
39
|
+
previous: string;
|
|
40
|
+
next: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare const paginationDescriptorDefaults: {
|
|
43
|
+
readonly siblingCount: 1;
|
|
44
|
+
readonly boundaryCount: 1;
|
|
45
|
+
};
|
|
46
|
+
export type PaginationItem = Readonly<{
|
|
47
|
+
type: "page";
|
|
48
|
+
page: number;
|
|
49
|
+
}> | Readonly<{
|
|
50
|
+
type: "ellipsis";
|
|
51
|
+
}>;
|
|
52
|
+
export type PaginationWindowOptions = Readonly<{
|
|
53
|
+
siblingCount?: number;
|
|
54
|
+
boundaryCount?: number;
|
|
55
|
+
}>;
|
|
56
|
+
export type PaginationAccessibleNameInfo = Readonly<{
|
|
57
|
+
page: number;
|
|
58
|
+
totalPages: number;
|
|
59
|
+
current: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Products own the exact phrase order, same reason as Steps'
|
|
63
|
+
* composeAccessibleName: Korean "5 페이지 중 3 페이지" and an English "page 3
|
|
64
|
+
* of 5" have different word order, and every page button gets its own
|
|
65
|
+
* composed name (not just a landmark summary) because ellipsis gaps mean a
|
|
66
|
+
* bare visible number ("7") does not by itself convey how many pages exist.
|
|
67
|
+
*/
|
|
68
|
+
export type ComposePaginationAccessibleName = (info: PaginationAccessibleNameInfo) => string;
|
|
69
|
+
export type ResolvedPaginationItem = Readonly<{
|
|
70
|
+
type: "page";
|
|
71
|
+
page: number;
|
|
72
|
+
current: boolean;
|
|
73
|
+
accessibleName: string;
|
|
74
|
+
}> | Readonly<{
|
|
75
|
+
type: "ellipsis";
|
|
76
|
+
}>;
|
|
77
|
+
export type ResolvedPaginationDescriptor = Readonly<{
|
|
78
|
+
currentPage: number;
|
|
79
|
+
totalPages: number;
|
|
80
|
+
hasPrevious: boolean;
|
|
81
|
+
hasNext: boolean;
|
|
82
|
+
labels: PaginationLabels;
|
|
83
|
+
items: readonly ResolvedPaginationItem[];
|
|
84
|
+
}>;
|
|
85
|
+
export type ResolvePaginationOptions = Readonly<{
|
|
86
|
+
labels: PaginationLabels;
|
|
87
|
+
composeAccessibleName: ComposePaginationAccessibleName;
|
|
88
|
+
}>;
|
|
89
|
+
/** Why a page change was requested — analytics and controllers key off this, not the target page alone. */
|
|
90
|
+
export type PaginationChangeReason = "previous" | "next" | "page";
|
|
91
|
+
export type PaginationChangeHandler = (page: number, reason: PaginationChangeReason) => void;
|
|
92
|
+
export declare function validatePaginationDescriptor(descriptor: PaginationDescriptor): void;
|
|
93
|
+
export declare function validatePaginationLabels(labels: PaginationLabels): void;
|
|
94
|
+
/**
|
|
95
|
+
* Pure page-window function, independent of accessibility naming, so its
|
|
96
|
+
* boundary inputs (total 1 page, total 2 pages, current at the first/last
|
|
97
|
+
* page) can be locked with tests without wiring a composer. Builds the set of
|
|
98
|
+
* pages that must always be visible (both boundaries + the sibling window
|
|
99
|
+
* around currentPage) and inserts a single ellipsis marker for any gap larger
|
|
100
|
+
* than one page — never a lone hidden page, never two consecutive ellipses.
|
|
101
|
+
*/
|
|
102
|
+
export declare function computePaginationItems(totalPages: number, currentPage: number, options?: PaginationWindowOptions): readonly PaginationItem[];
|
|
103
|
+
/**
|
|
104
|
+
* Attaches per-button accessible names and current-page derivation on top of
|
|
105
|
+
* computePaginationItems. Ellipsis items intentionally carry no
|
|
106
|
+
* accessibleName — they are decoration ("..."), not content, and must be
|
|
107
|
+
* `aria-hidden`/excluded from the announcement.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolvePaginationDescriptor(descriptor: PaginationDescriptor, options: ResolvePaginationOptions): ResolvedPaginationDescriptor;
|
|
110
|
+
/**
|
|
111
|
+
* Visual recipe only covers chrome (size, gap, focus, current-item tone).
|
|
112
|
+
* Following identity.md's "primary fill은 주요 행동에, contentBrand는 ...
|
|
113
|
+
* 현재 위치에" rule and Steps' own current-step treatment
|
|
114
|
+
* (stepsRecipe.indicator: border + content color, no background fill), the
|
|
115
|
+
* current page is marked with a `border.focus`/`content.brand` outline, not a
|
|
116
|
+
* filled `action.brand` pill — a page number is a location, not a command.
|
|
117
|
+
*/
|
|
118
|
+
export declare const paginationRecipe: {
|
|
119
|
+
readonly slots: readonly ["root", "item", "ellipsis", "previous", "next"];
|
|
120
|
+
readonly gap: 4;
|
|
121
|
+
readonly item: {
|
|
122
|
+
readonly minSize: 44;
|
|
123
|
+
readonly radius: "md";
|
|
124
|
+
readonly color: {
|
|
125
|
+
readonly default: Readonly<{
|
|
126
|
+
source: "theme";
|
|
127
|
+
key: "textBody";
|
|
128
|
+
alpha?: number;
|
|
129
|
+
}>;
|
|
130
|
+
readonly current: Readonly<{
|
|
131
|
+
source: "theme";
|
|
132
|
+
key: "contentBrand";
|
|
133
|
+
alpha?: number;
|
|
134
|
+
}>;
|
|
135
|
+
};
|
|
136
|
+
readonly background: {
|
|
137
|
+
readonly hover: Readonly<{
|
|
138
|
+
source: "theme";
|
|
139
|
+
key: "text";
|
|
140
|
+
alpha?: number;
|
|
141
|
+
}>;
|
|
142
|
+
};
|
|
143
|
+
readonly border: {
|
|
144
|
+
readonly current: Readonly<{
|
|
145
|
+
source: "theme";
|
|
146
|
+
key: "contentBrand";
|
|
147
|
+
alpha?: number;
|
|
148
|
+
}>;
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
readonly ellipsis: {
|
|
152
|
+
readonly color: Readonly<{
|
|
153
|
+
source: "theme";
|
|
154
|
+
key: "textMuted";
|
|
155
|
+
alpha?: number;
|
|
156
|
+
}>;
|
|
157
|
+
readonly icon: "more";
|
|
158
|
+
};
|
|
159
|
+
readonly navIcon: {
|
|
160
|
+
readonly previous: "chevronStart";
|
|
161
|
+
readonly next: "chevronEnd";
|
|
162
|
+
};
|
|
163
|
+
readonly focus: {
|
|
164
|
+
readonly color: Readonly<{
|
|
165
|
+
source: "theme";
|
|
166
|
+
key: "contentBrand";
|
|
167
|
+
alpha?: number;
|
|
168
|
+
}>;
|
|
169
|
+
readonly width: 2;
|
|
170
|
+
readonly offset: 2;
|
|
171
|
+
};
|
|
172
|
+
readonly states: {
|
|
173
|
+
readonly disabledOpacity: 0.5;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Literal scenario names for behaviorRegistry.pagination (lead wires into
|
|
178
|
+
* src/behaviors.ts).
|
|
179
|
+
*/
|
|
180
|
+
export declare const paginationBehaviorScenarios: readonly ["current-page-marked-and-announced-without-a-composed-live-region-spam", "ellipsis-is-decorative-and-excluded-from-the-accessible-tree", "previous-disabled-on-first-page-next-disabled-on-last-page", "single-page-renders-no-ellipsis-and-no-disabled-dead-buttons", "page-window-never-produces-two-consecutive-ellipses", "nav-landmark-present-with-a-localized-accessible-name"];
|
|
181
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { focusIndicatorContract } from "./component-contracts.js";
|
|
2
|
+
import { control, opacity, spacing } from "./foundations.js";
|
|
3
|
+
import { semanticColors } from "./semantic-colors.js";
|
|
4
|
+
export const paginationDescriptorDefaults = {
|
|
5
|
+
siblingCount: 1,
|
|
6
|
+
boundaryCount: 1,
|
|
7
|
+
};
|
|
8
|
+
function isObject(value) {
|
|
9
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function isPositiveInteger(value) {
|
|
12
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
13
|
+
}
|
|
14
|
+
function isNonNegativeInteger(value) {
|
|
15
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
16
|
+
}
|
|
17
|
+
const descriptorKeys = new Set([
|
|
18
|
+
"currentPage",
|
|
19
|
+
"totalCount",
|
|
20
|
+
"pageSize",
|
|
21
|
+
"totalPages",
|
|
22
|
+
"siblingCount",
|
|
23
|
+
"boundaryCount",
|
|
24
|
+
]);
|
|
25
|
+
function rejectUnknownKeys(value, allowed, field) {
|
|
26
|
+
for (const key of Object.keys(value)) {
|
|
27
|
+
if (!allowed.has(key)) {
|
|
28
|
+
throw new TypeError(`Unsupported Pagination ${field} field: ${key}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function resolveTotalPages(runtime) {
|
|
33
|
+
const hasTotalPages = Object.prototype.hasOwnProperty.call(runtime, "totalPages");
|
|
34
|
+
const hasTotalCount = Object.prototype.hasOwnProperty.call(runtime, "totalCount");
|
|
35
|
+
const hasPageSize = Object.prototype.hasOwnProperty.call(runtime, "pageSize");
|
|
36
|
+
if (hasTotalPages) {
|
|
37
|
+
if (hasTotalCount || hasPageSize) {
|
|
38
|
+
throw new TypeError("Pagination descriptor must not combine totalPages with totalCount/pageSize");
|
|
39
|
+
}
|
|
40
|
+
if (!isPositiveInteger(runtime.totalPages)) {
|
|
41
|
+
throw new RangeError("Pagination totalPages must be a positive integer");
|
|
42
|
+
}
|
|
43
|
+
return runtime.totalPages;
|
|
44
|
+
}
|
|
45
|
+
if (!hasTotalCount || !hasPageSize) {
|
|
46
|
+
throw new TypeError("Pagination descriptor requires either totalPages or totalCount with pageSize");
|
|
47
|
+
}
|
|
48
|
+
if (!isNonNegativeInteger(runtime.totalCount)) {
|
|
49
|
+
throw new RangeError("Pagination totalCount must be a non-negative integer");
|
|
50
|
+
}
|
|
51
|
+
if (!isPositiveInteger(runtime.pageSize)) {
|
|
52
|
+
throw new RangeError("Pagination pageSize must be a positive integer");
|
|
53
|
+
}
|
|
54
|
+
return Math.max(1, Math.ceil(runtime.totalCount / runtime.pageSize));
|
|
55
|
+
}
|
|
56
|
+
export function validatePaginationDescriptor(descriptor) {
|
|
57
|
+
if (!isObject(descriptor)) {
|
|
58
|
+
throw new TypeError("Pagination descriptor must be an object");
|
|
59
|
+
}
|
|
60
|
+
rejectUnknownKeys(descriptor, descriptorKeys, "descriptor");
|
|
61
|
+
const runtime = descriptor;
|
|
62
|
+
const totalPages = resolveTotalPages(runtime);
|
|
63
|
+
if (!isPositiveInteger(runtime.currentPage)) {
|
|
64
|
+
throw new RangeError("Pagination currentPage must be a positive integer");
|
|
65
|
+
}
|
|
66
|
+
if (runtime.currentPage > totalPages) {
|
|
67
|
+
throw new RangeError(`Pagination currentPage ${String(runtime.currentPage)} exceeds totalPages ${totalPages}`);
|
|
68
|
+
}
|
|
69
|
+
if (runtime.siblingCount !== undefined &&
|
|
70
|
+
!isNonNegativeInteger(runtime.siblingCount)) {
|
|
71
|
+
throw new RangeError("Pagination siblingCount must be a non-negative integer");
|
|
72
|
+
}
|
|
73
|
+
if (runtime.boundaryCount !== undefined &&
|
|
74
|
+
!isNonNegativeInteger(runtime.boundaryCount)) {
|
|
75
|
+
throw new RangeError("Pagination boundaryCount must be a non-negative integer");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function validatePaginationLabels(labels) {
|
|
79
|
+
if (!isObject(labels)) {
|
|
80
|
+
throw new TypeError("Pagination labels must be an object");
|
|
81
|
+
}
|
|
82
|
+
for (const field of ["previous", "next"]) {
|
|
83
|
+
const value = labels[field];
|
|
84
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
85
|
+
throw new TypeError(`Pagination labels.${field} must not be empty`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Pure page-window function, independent of accessibility naming, so its
|
|
91
|
+
* boundary inputs (total 1 page, total 2 pages, current at the first/last
|
|
92
|
+
* page) can be locked with tests without wiring a composer. Builds the set of
|
|
93
|
+
* pages that must always be visible (both boundaries + the sibling window
|
|
94
|
+
* around currentPage) and inserts a single ellipsis marker for any gap larger
|
|
95
|
+
* than one page — never a lone hidden page, never two consecutive ellipses.
|
|
96
|
+
*/
|
|
97
|
+
export function computePaginationItems(totalPages, currentPage, options = {}) {
|
|
98
|
+
if (!isPositiveInteger(totalPages)) {
|
|
99
|
+
throw new RangeError("Pagination totalPages must be a positive integer");
|
|
100
|
+
}
|
|
101
|
+
if (!isPositiveInteger(currentPage) || currentPage > totalPages) {
|
|
102
|
+
throw new RangeError(`Pagination currentPage must be an integer between 1 and ${totalPages}`);
|
|
103
|
+
}
|
|
104
|
+
const siblingCount = options.siblingCount ?? paginationDescriptorDefaults.siblingCount;
|
|
105
|
+
const boundaryCount = options.boundaryCount ?? paginationDescriptorDefaults.boundaryCount;
|
|
106
|
+
if (!isNonNegativeInteger(siblingCount)) {
|
|
107
|
+
throw new RangeError("Pagination siblingCount must be a non-negative integer");
|
|
108
|
+
}
|
|
109
|
+
if (!isNonNegativeInteger(boundaryCount)) {
|
|
110
|
+
throw new RangeError("Pagination boundaryCount must be a non-negative integer");
|
|
111
|
+
}
|
|
112
|
+
const shown = new Set();
|
|
113
|
+
for (let page = 1; page <= Math.min(boundaryCount, totalPages); page += 1) {
|
|
114
|
+
shown.add(page);
|
|
115
|
+
}
|
|
116
|
+
for (let page = Math.max(1, totalPages - boundaryCount + 1); page <= totalPages; page += 1) {
|
|
117
|
+
shown.add(page);
|
|
118
|
+
}
|
|
119
|
+
for (let page = Math.max(1, currentPage - siblingCount); page <= Math.min(totalPages, currentPage + siblingCount); page += 1) {
|
|
120
|
+
shown.add(page);
|
|
121
|
+
}
|
|
122
|
+
const sortedPages = [...shown].sort((a, b) => a - b);
|
|
123
|
+
const items = [];
|
|
124
|
+
let previousPage = null;
|
|
125
|
+
for (const page of sortedPages) {
|
|
126
|
+
if (previousPage !== null) {
|
|
127
|
+
const gap = page - previousPage;
|
|
128
|
+
// A gap of exactly 2 hides a single page — showing that number costs no
|
|
129
|
+
// more space than "..." and is more useful, so ellipsis is reserved for
|
|
130
|
+
// two or more genuinely hidden pages.
|
|
131
|
+
if (gap === 2) {
|
|
132
|
+
items.push({ type: "page", page: previousPage + 1 });
|
|
133
|
+
}
|
|
134
|
+
else if (gap > 2) {
|
|
135
|
+
items.push({ type: "ellipsis" });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
items.push({ type: "page", page });
|
|
139
|
+
previousPage = page;
|
|
140
|
+
}
|
|
141
|
+
return items;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Attaches per-button accessible names and current-page derivation on top of
|
|
145
|
+
* computePaginationItems. Ellipsis items intentionally carry no
|
|
146
|
+
* accessibleName — they are decoration ("..."), not content, and must be
|
|
147
|
+
* `aria-hidden`/excluded from the announcement.
|
|
148
|
+
*/
|
|
149
|
+
export function resolvePaginationDescriptor(descriptor, options) {
|
|
150
|
+
validatePaginationDescriptor(descriptor);
|
|
151
|
+
validatePaginationLabels(options.labels);
|
|
152
|
+
if (typeof options.composeAccessibleName !== "function") {
|
|
153
|
+
throw new TypeError("Pagination composeAccessibleName must be a function");
|
|
154
|
+
}
|
|
155
|
+
const runtime = descriptor;
|
|
156
|
+
const totalPages = resolveTotalPages(runtime);
|
|
157
|
+
const currentPage = descriptor.currentPage;
|
|
158
|
+
const rawItems = computePaginationItems(totalPages, currentPage, {
|
|
159
|
+
siblingCount: descriptor.siblingCount ?? paginationDescriptorDefaults.siblingCount,
|
|
160
|
+
boundaryCount: descriptor.boundaryCount ?? paginationDescriptorDefaults.boundaryCount,
|
|
161
|
+
});
|
|
162
|
+
const items = rawItems.map((item) => {
|
|
163
|
+
if (item.type === "ellipsis")
|
|
164
|
+
return item;
|
|
165
|
+
const current = item.page === currentPage;
|
|
166
|
+
const accessibleName = options.composeAccessibleName({
|
|
167
|
+
page: item.page,
|
|
168
|
+
totalPages,
|
|
169
|
+
current,
|
|
170
|
+
});
|
|
171
|
+
if (typeof accessibleName !== "string" || accessibleName.trim().length === 0) {
|
|
172
|
+
throw new TypeError("Pagination composeAccessibleName must return a non-empty string");
|
|
173
|
+
}
|
|
174
|
+
return { type: "page", page: item.page, current, accessibleName };
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
currentPage,
|
|
178
|
+
totalPages,
|
|
179
|
+
hasPrevious: currentPage > 1,
|
|
180
|
+
hasNext: currentPage < totalPages,
|
|
181
|
+
labels: options.labels,
|
|
182
|
+
items,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Visual recipe only covers chrome (size, gap, focus, current-item tone).
|
|
187
|
+
* Following identity.md's "primary fill은 주요 행동에, contentBrand는 ...
|
|
188
|
+
* 현재 위치에" rule and Steps' own current-step treatment
|
|
189
|
+
* (stepsRecipe.indicator: border + content color, no background fill), the
|
|
190
|
+
* current page is marked with a `border.focus`/`content.brand` outline, not a
|
|
191
|
+
* filled `action.brand` pill — a page number is a location, not a command.
|
|
192
|
+
*/
|
|
193
|
+
export const paginationRecipe = {
|
|
194
|
+
slots: ["root", "item", "ellipsis", "previous", "next"],
|
|
195
|
+
gap: spacing.xxs,
|
|
196
|
+
item: {
|
|
197
|
+
minSize: control.minTouchTarget,
|
|
198
|
+
radius: "md",
|
|
199
|
+
color: {
|
|
200
|
+
default: semanticColors.content.body,
|
|
201
|
+
current: semanticColors.content.brand,
|
|
202
|
+
},
|
|
203
|
+
background: { hover: semanticColors.interaction.hover },
|
|
204
|
+
border: { current: semanticColors.border.focus },
|
|
205
|
+
},
|
|
206
|
+
ellipsis: {
|
|
207
|
+
color: semanticColors.content.secondary,
|
|
208
|
+
icon: "more",
|
|
209
|
+
},
|
|
210
|
+
navIcon: { previous: "chevronStart", next: "chevronEnd" },
|
|
211
|
+
focus: focusIndicatorContract,
|
|
212
|
+
states: { disabledOpacity: opacity.disabled },
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Literal scenario names for behaviorRegistry.pagination (lead wires into
|
|
216
|
+
* src/behaviors.ts).
|
|
217
|
+
*/
|
|
218
|
+
export const paginationBehaviorScenarios = [
|
|
219
|
+
"current-page-marked-and-announced-without-a-composed-live-region-spam",
|
|
220
|
+
"ellipsis-is-decorative-and-excluded-from-the-accessible-tree",
|
|
221
|
+
"previous-disabled-on-first-page-next-disabled-on-last-page",
|
|
222
|
+
"single-page-renders-no-ellipsis-and-no-disabled-dead-buttons",
|
|
223
|
+
"page-window-never-produces-two-consecutive-ellipses",
|
|
224
|
+
"nav-landmark-present-with-a-localized-accessible-name",
|
|
225
|
+
];
|
|
226
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { SemanticIconName } from \"./icon.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, opacity, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * Web-only by roadmap decision, not an oversight: \"Native 긴 목록에는 페이지\n * 번호보다 LoadMore/infinite loading을 사용한다\" (docs/load-more.md). Numbered\n * pages assume a stable total and a pointing device/keyboard that can jump\n * around; a touch list scrolling through hundreds of rows has neither. The two\n * contracts do not need to know about each other — LoadMore never becomes\n * Pagination and vice versa — but a product choosing between them should pick\n * Pagination for a bounded, page-addressable result set (search results,\n * admin tables) and LoadMore for an open-ended feed the user just keeps\n * scrolling.\n */\nexport type PaginationByCount = Readonly<{\n currentPage: number;\n totalCount: number;\n pageSize: number;\n totalPages?: never;\n siblingCount?: number;\n boundaryCount?: number;\n}>;\n\nexport type PaginationByPageCount = Readonly<{\n currentPage: number;\n totalPages: number;\n totalCount?: never;\n pageSize?: never;\n siblingCount?: number;\n boundaryCount?: number;\n}>;\n\n/**\n * Required contract is exactly current page + (total count or total page\n * count) + page size — nothing else. A page-size changer and a \"jump to page\"\n * input are intentionally absent: neither has measured product demand, and\n * both add a second way to reach the same currentPage/totalPages state this\n * module already owns. If a product needs them, they compose as separate\n * controls that call the same onPageChange — they are not missing API surface\n * here, they are a deliberate exclusion (see docs/pagination.md).\n */\nexport type PaginationDescriptor = PaginationByCount | PaginationByPageCount;\n\nexport type PaginationLabels = Readonly<{\n previous: string;\n next: string;\n}>;\n\nexport const paginationDescriptorDefaults = {\n siblingCount: 1,\n boundaryCount: 1,\n} as const satisfies Readonly<{ siblingCount: number; boundaryCount: number }>;\n\nexport type PaginationItem =\n | Readonly<{ type: \"page\"; page: number }>\n | Readonly<{ type: \"ellipsis\" }>;\n\nexport type PaginationWindowOptions = Readonly<{\n siblingCount?: number;\n boundaryCount?: number;\n}>;\n\nexport type PaginationAccessibleNameInfo = Readonly<{\n page: number;\n totalPages: number;\n current: boolean;\n}>;\n\n/**\n * Products own the exact phrase order, same reason as Steps'\n * composeAccessibleName: Korean \"5 페이지 중 3 페이지\" and an English \"page 3\n * of 5\" have different word order, and every page button gets its own\n * composed name (not just a landmark summary) because ellipsis gaps mean a\n * bare visible number (\"7\") does not by itself convey how many pages exist.\n */\nexport type ComposePaginationAccessibleName = (\n info: PaginationAccessibleNameInfo,\n) => string;\n\nexport type ResolvedPaginationItem =\n | Readonly<{\n type: \"page\";\n page: number;\n current: boolean;\n accessibleName: string;\n }>\n | Readonly<{ type: \"ellipsis\" }>;\n\nexport type ResolvedPaginationDescriptor = Readonly<{\n currentPage: number;\n totalPages: number;\n hasPrevious: boolean;\n hasNext: boolean;\n labels: PaginationLabels;\n items: readonly ResolvedPaginationItem[];\n}>;\n\nexport type ResolvePaginationOptions = Readonly<{\n labels: PaginationLabels;\n composeAccessibleName: ComposePaginationAccessibleName;\n}>;\n\n/** Why a page change was requested — analytics and controllers key off this, not the target page alone. */\nexport type PaginationChangeReason = \"previous\" | \"next\" | \"page\";\nexport type PaginationChangeHandler = (\n page: number,\n reason: PaginationChangeReason,\n) => void;\n\nfunction isObject(value: unknown): value is Readonly<Record<string, unknown>> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isPositiveInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 1;\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\nconst descriptorKeys = new Set([\n \"currentPage\",\n \"totalCount\",\n \"pageSize\",\n \"totalPages\",\n \"siblingCount\",\n \"boundaryCount\",\n]);\n\nfunction rejectUnknownKeys(\n value: Readonly<Record<string, unknown>>,\n allowed: ReadonlySet<string>,\n field: string,\n): void {\n for (const key of Object.keys(value)) {\n if (!allowed.has(key)) {\n throw new TypeError(`Unsupported Pagination ${field} field: ${key}`);\n }\n }\n}\n\nfunction resolveTotalPages(runtime: Readonly<Record<string, unknown>>): number {\n const hasTotalPages = Object.prototype.hasOwnProperty.call(runtime, \"totalPages\");\n const hasTotalCount = Object.prototype.hasOwnProperty.call(runtime, \"totalCount\");\n const hasPageSize = Object.prototype.hasOwnProperty.call(runtime, \"pageSize\");\n\n if (hasTotalPages) {\n if (hasTotalCount || hasPageSize) {\n throw new TypeError(\n \"Pagination descriptor must not combine totalPages with totalCount/pageSize\",\n );\n }\n if (!isPositiveInteger(runtime.totalPages)) {\n throw new RangeError(\"Pagination totalPages must be a positive integer\");\n }\n return runtime.totalPages;\n }\n\n if (!hasTotalCount || !hasPageSize) {\n throw new TypeError(\n \"Pagination descriptor requires either totalPages or totalCount with pageSize\",\n );\n }\n if (!isNonNegativeInteger(runtime.totalCount)) {\n throw new RangeError(\"Pagination totalCount must be a non-negative integer\");\n }\n if (!isPositiveInteger(runtime.pageSize)) {\n throw new RangeError(\"Pagination pageSize must be a positive integer\");\n }\n return Math.max(1, Math.ceil(runtime.totalCount / runtime.pageSize));\n}\n\nexport function validatePaginationDescriptor(\n descriptor: PaginationDescriptor,\n): void {\n if (!isObject(descriptor)) {\n throw new TypeError(\"Pagination descriptor must be an object\");\n }\n rejectUnknownKeys(descriptor, descriptorKeys, \"descriptor\");\n const runtime = descriptor as Readonly<Record<string, unknown>>;\n const totalPages = resolveTotalPages(runtime);\n if (!isPositiveInteger(runtime.currentPage)) {\n throw new RangeError(\"Pagination currentPage must be a positive integer\");\n }\n if (runtime.currentPage > totalPages) {\n throw new RangeError(\n `Pagination currentPage ${String(runtime.currentPage)} exceeds totalPages ${totalPages}`,\n );\n }\n if (\n runtime.siblingCount !== undefined &&\n !isNonNegativeInteger(runtime.siblingCount)\n ) {\n throw new RangeError(\"Pagination siblingCount must be a non-negative integer\");\n }\n if (\n runtime.boundaryCount !== undefined &&\n !isNonNegativeInteger(runtime.boundaryCount)\n ) {\n throw new RangeError(\"Pagination boundaryCount must be a non-negative integer\");\n }\n}\n\nexport function validatePaginationLabels(labels: PaginationLabels): void {\n if (!isObject(labels)) {\n throw new TypeError(\"Pagination labels must be an object\");\n }\n for (const field of [\"previous\", \"next\"] as const) {\n const value = labels[field];\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Pagination labels.${field} must not be empty`);\n }\n }\n}\n\n/**\n * Pure page-window function, independent of accessibility naming, so its\n * boundary inputs (total 1 page, total 2 pages, current at the first/last\n * page) can be locked with tests without wiring a composer. Builds the set of\n * pages that must always be visible (both boundaries + the sibling window\n * around currentPage) and inserts a single ellipsis marker for any gap larger\n * than one page — never a lone hidden page, never two consecutive ellipses.\n */\nexport function computePaginationItems(\n totalPages: number,\n currentPage: number,\n options: PaginationWindowOptions = {},\n): readonly PaginationItem[] {\n if (!isPositiveInteger(totalPages)) {\n throw new RangeError(\"Pagination totalPages must be a positive integer\");\n }\n if (!isPositiveInteger(currentPage) || currentPage > totalPages) {\n throw new RangeError(\n `Pagination currentPage must be an integer between 1 and ${totalPages}`,\n );\n }\n const siblingCount = options.siblingCount ?? paginationDescriptorDefaults.siblingCount;\n const boundaryCount = options.boundaryCount ?? paginationDescriptorDefaults.boundaryCount;\n if (!isNonNegativeInteger(siblingCount)) {\n throw new RangeError(\"Pagination siblingCount must be a non-negative integer\");\n }\n if (!isNonNegativeInteger(boundaryCount)) {\n throw new RangeError(\"Pagination boundaryCount must be a non-negative integer\");\n }\n\n const shown = new Set<number>();\n for (let page = 1; page <= Math.min(boundaryCount, totalPages); page += 1) {\n shown.add(page);\n }\n for (\n let page = Math.max(1, totalPages - boundaryCount + 1);\n page <= totalPages;\n page += 1\n ) {\n shown.add(page);\n }\n for (\n let page = Math.max(1, currentPage - siblingCount);\n page <= Math.min(totalPages, currentPage + siblingCount);\n page += 1\n ) {\n shown.add(page);\n }\n\n const sortedPages = [...shown].sort((a, b) => a - b);\n const items: PaginationItem[] = [];\n let previousPage: number | null = null;\n for (const page of sortedPages) {\n if (previousPage !== null) {\n const gap = page - previousPage;\n // A gap of exactly 2 hides a single page — showing that number costs no\n // more space than \"...\" and is more useful, so ellipsis is reserved for\n // two or more genuinely hidden pages.\n if (gap === 2) {\n items.push({ type: \"page\", page: previousPage + 1 });\n } else if (gap > 2) {\n items.push({ type: \"ellipsis\" });\n }\n }\n items.push({ type: \"page\", page });\n previousPage = page;\n }\n return items;\n}\n\n/**\n * Attaches per-button accessible names and current-page derivation on top of\n * computePaginationItems. Ellipsis items intentionally carry no\n * accessibleName — they are decoration (\"...\"), not content, and must be\n * `aria-hidden`/excluded from the announcement.\n */\nexport function resolvePaginationDescriptor(\n descriptor: PaginationDescriptor,\n options: ResolvePaginationOptions,\n): ResolvedPaginationDescriptor {\n validatePaginationDescriptor(descriptor);\n validatePaginationLabels(options.labels);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Pagination composeAccessibleName must be a function\");\n }\n\n const runtime = descriptor as Readonly<Record<string, unknown>>;\n const totalPages = resolveTotalPages(runtime);\n const currentPage = descriptor.currentPage;\n const rawItems = computePaginationItems(totalPages, currentPage, {\n siblingCount:\n descriptor.siblingCount ?? paginationDescriptorDefaults.siblingCount,\n boundaryCount:\n descriptor.boundaryCount ?? paginationDescriptorDefaults.boundaryCount,\n });\n\n const items: ResolvedPaginationItem[] = rawItems.map((item) => {\n if (item.type === \"ellipsis\") return item;\n const current = item.page === currentPage;\n const accessibleName = options.composeAccessibleName({\n page: item.page,\n totalPages,\n current,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Pagination composeAccessibleName must return a non-empty string\",\n );\n }\n return { type: \"page\", page: item.page, current, accessibleName };\n });\n\n return {\n currentPage,\n totalPages,\n hasPrevious: currentPage > 1,\n hasNext: currentPage < totalPages,\n labels: options.labels,\n items,\n };\n}\n\n/**\n * Visual recipe only covers chrome (size, gap, focus, current-item tone).\n * Following identity.md's \"primary fill은 주요 행동에, contentBrand는 ...\n * 현재 위치에\" rule and Steps' own current-step treatment\n * (stepsRecipe.indicator: border + content color, no background fill), the\n * current page is marked with a `border.focus`/`content.brand` outline, not a\n * filled `action.brand` pill — a page number is a location, not a command.\n */\nexport const paginationRecipe = {\n slots: [\"root\", \"item\", \"ellipsis\", \"previous\", \"next\"] as const,\n gap: spacing.xxs,\n item: {\n minSize: control.minTouchTarget,\n radius: \"md\" as const,\n color: {\n default: semanticColors.content.body,\n current: semanticColors.content.brand,\n },\n background: { hover: semanticColors.interaction.hover },\n border: { current: semanticColors.border.focus },\n },\n ellipsis: {\n color: semanticColors.content.secondary,\n icon: \"more\" as const,\n },\n navIcon: { previous: \"chevronStart\" as const, next: \"chevronEnd\" as const },\n focus: focusIndicatorContract,\n states: { disabledOpacity: opacity.disabled },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n item: {\n minSize: number;\n radius: \"md\";\n color: Record<\"default\" | \"current\", ColorReference>;\n background: Record<\"hover\", ColorReference>;\n border: Record<\"current\", ColorReference>;\n };\n ellipsis: { color: ColorReference; icon: SemanticIconName };\n navIcon: Record<\"previous\" | \"next\", SemanticIconName>;\n focus: typeof focusIndicatorContract;\n states: { disabledOpacity: number };\n};\n\n/**\n * Literal scenario names for behaviorRegistry.pagination (lead wires into\n * src/behaviors.ts).\n */\nexport const paginationBehaviorScenarios = [\n \"current-page-marked-and-announced-without-a-composed-live-region-spam\",\n \"ellipsis-is-decorative-and-excluded-from-the-accessible-tree\",\n \"previous-disabled-on-first-page-next-disabled-on-last-page\",\n \"single-page-renders-no-ellipsis-and-no-disabled-dead-buttons\",\n \"page-window-never-produces-two-consecutive-ellipses\",\n \"nav-landmark-present-with-a-localized-accessible-name\",\n] as const;\n"]}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PasswordField is Field's existing frame plus exactly one new problem: a
|
|
3
|
+
* reveal toggle. It does not redeclare Field's own `value`/`onValueChange` —
|
|
4
|
+
* a password's value is a plain string, identical to Field's, so there is
|
|
5
|
+
* nothing new to contract there (unlike NumberField, whose value is a parsed
|
|
6
|
+
* number). See docs/password-field.md.
|
|
7
|
+
*/
|
|
8
|
+
export type PasswordFieldAutofillHint = "current" | "new";
|
|
9
|
+
export type PasswordFieldDescriptor = Readonly<{
|
|
10
|
+
revealed: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Which password-manager autofill bucket this field is for. Required, not
|
|
13
|
+
* inferred — HJM cannot tell a login screen from a signup screen from the
|
|
14
|
+
* descriptor alone, and guessing wrong actively harms the browser/OS
|
|
15
|
+
* autofill suggestion. See docs/password-field.md for the platform
|
|
16
|
+
* translation (Web autocomplete token, iOS textContentType, Android
|
|
17
|
+
* autofill hint) — the exact attribute values are a renderer concern, not
|
|
18
|
+
* asserted here.
|
|
19
|
+
*/
|
|
20
|
+
autofillHint: PasswordFieldAutofillHint;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function validatePasswordFieldDescriptor(descriptor: PasswordFieldDescriptor): void;
|
|
23
|
+
export type PasswordToggleAccessibleNameInfo = Readonly<{
|
|
24
|
+
/**
|
|
25
|
+
* The action pressing the toggle will perform — not whether the password
|
|
26
|
+
* is currently masked. See docs/password-field.md for why the name must
|
|
27
|
+
* describe the action.
|
|
28
|
+
*/
|
|
29
|
+
willReveal: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
export type ComposePasswordToggleAccessibleName = (info: PasswordToggleAccessibleNameInfo) => string;
|
|
32
|
+
export type ResolvedPasswordFieldDescriptor = PasswordFieldDescriptor & Readonly<{
|
|
33
|
+
toggleAccessibleName: string;
|
|
34
|
+
webInputType: "text" | "password";
|
|
35
|
+
nativeSecureTextEntry: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
export type ResolvePasswordFieldOptions = Readonly<{
|
|
38
|
+
composeToggleAccessibleName: ComposePasswordToggleAccessibleName;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Toggling `revealed` only ever changes how the value is displayed — it
|
|
42
|
+
* never touches the value itself. That guarantee is why this resolver takes
|
|
43
|
+
* no value parameter at all: there is nothing here that could reach into it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolvePasswordFieldDescriptor(descriptor: PasswordFieldDescriptor, options: ResolvePasswordFieldOptions): ResolvedPasswordFieldDescriptor;
|
|
46
|
+
export type PasswordFieldSize = "medium" | "large";
|
|
47
|
+
/**
|
|
48
|
+
* Reuses Field's frame and form support copy verbatim — the same judgment
|
|
49
|
+
* NumberField already made (see src/number-field.ts) — and adds only the
|
|
50
|
+
* toggle slot, styled like SearchField's trailing clear button (same
|
|
51
|
+
* diameter/hit-slop convention) but with the visibility icon pair instead.
|
|
52
|
+
*/
|
|
53
|
+
export declare const passwordFieldRecipe: {
|
|
54
|
+
readonly slots: readonly ["root", "frame", "input", "toggle", "description", "error"];
|
|
55
|
+
readonly defaults: {
|
|
56
|
+
readonly size: "medium";
|
|
57
|
+
};
|
|
58
|
+
readonly frame: {
|
|
59
|
+
readonly background: Readonly<{
|
|
60
|
+
source: "theme";
|
|
61
|
+
key: "surface";
|
|
62
|
+
alpha?: number;
|
|
63
|
+
}>;
|
|
64
|
+
readonly border: Readonly<{
|
|
65
|
+
source: "theme";
|
|
66
|
+
key: "textMuted";
|
|
67
|
+
alpha?: number;
|
|
68
|
+
}>;
|
|
69
|
+
readonly focusBorder: Readonly<{
|
|
70
|
+
source: "theme";
|
|
71
|
+
key: "contentBrand";
|
|
72
|
+
alpha?: number;
|
|
73
|
+
}>;
|
|
74
|
+
readonly invalidBorder: Readonly<{
|
|
75
|
+
source: "theme";
|
|
76
|
+
key: "danger";
|
|
77
|
+
alpha?: number;
|
|
78
|
+
}>;
|
|
79
|
+
readonly radius: "md";
|
|
80
|
+
readonly borderWidth: 1;
|
|
81
|
+
readonly minHeight: 44;
|
|
82
|
+
readonly paddingHorizontal: 16;
|
|
83
|
+
};
|
|
84
|
+
readonly support: {
|
|
85
|
+
readonly label: {
|
|
86
|
+
readonly color: Readonly<{
|
|
87
|
+
source: "theme";
|
|
88
|
+
key: "textBody";
|
|
89
|
+
alpha?: number;
|
|
90
|
+
}>;
|
|
91
|
+
readonly textVariant: "body";
|
|
92
|
+
readonly fontWeight: "600";
|
|
93
|
+
};
|
|
94
|
+
readonly hint: {
|
|
95
|
+
readonly color: Readonly<{
|
|
96
|
+
source: "theme";
|
|
97
|
+
key: "textMuted";
|
|
98
|
+
alpha?: number;
|
|
99
|
+
}>;
|
|
100
|
+
readonly textVariant: "label";
|
|
101
|
+
};
|
|
102
|
+
readonly error: {
|
|
103
|
+
readonly color: Readonly<{
|
|
104
|
+
source: "theme";
|
|
105
|
+
key: "danger";
|
|
106
|
+
alpha?: number;
|
|
107
|
+
}>;
|
|
108
|
+
readonly textVariant: "label";
|
|
109
|
+
};
|
|
110
|
+
readonly gap: 8;
|
|
111
|
+
};
|
|
112
|
+
readonly sizes: {
|
|
113
|
+
readonly medium: {
|
|
114
|
+
readonly minHeight: 44;
|
|
115
|
+
readonly paddingHorizontal: 16;
|
|
116
|
+
readonly textVariant: "body";
|
|
117
|
+
readonly toggleDiameter: 44;
|
|
118
|
+
};
|
|
119
|
+
readonly large: {
|
|
120
|
+
readonly minHeight: 52;
|
|
121
|
+
readonly paddingHorizontal: 20;
|
|
122
|
+
readonly textVariant: "bodyLarge";
|
|
123
|
+
readonly toggleDiameter: 44;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
readonly value: {
|
|
127
|
+
readonly color: Readonly<{
|
|
128
|
+
source: "theme";
|
|
129
|
+
key: "textBody";
|
|
130
|
+
alpha?: number;
|
|
131
|
+
}>;
|
|
132
|
+
};
|
|
133
|
+
readonly toggle: {
|
|
134
|
+
/**
|
|
135
|
+
* Icon mirrors the same action-not-state judgment as the accessible
|
|
136
|
+
* name: concealed shows the "eye" (pressing reveals), revealed shows the
|
|
137
|
+
* "eye-off" (pressing conceals).
|
|
138
|
+
*/
|
|
139
|
+
readonly icons: {
|
|
140
|
+
readonly concealed: "visibility";
|
|
141
|
+
readonly revealed: "visibilityOff";
|
|
142
|
+
};
|
|
143
|
+
readonly color: Readonly<{
|
|
144
|
+
source: "theme";
|
|
145
|
+
key: "textMuted";
|
|
146
|
+
alpha?: number;
|
|
147
|
+
}>;
|
|
148
|
+
};
|
|
149
|
+
readonly states: {
|
|
150
|
+
readonly focus: {
|
|
151
|
+
readonly color: Readonly<{
|
|
152
|
+
source: "theme";
|
|
153
|
+
key: "contentBrand";
|
|
154
|
+
alpha?: number;
|
|
155
|
+
}>;
|
|
156
|
+
readonly width: 2;
|
|
157
|
+
readonly offset: 2;
|
|
158
|
+
};
|
|
159
|
+
readonly invalidBorder: Readonly<{
|
|
160
|
+
source: "theme";
|
|
161
|
+
key: "danger";
|
|
162
|
+
alpha?: number;
|
|
163
|
+
}>;
|
|
164
|
+
readonly disabledOpacity: 0.5;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
export declare const passwordFieldBehavior: {
|
|
168
|
+
readonly controlled: readonly ["value", "defaultValue", "onValueChange", "revealed", "defaultRevealed", "onRevealedChange"];
|
|
169
|
+
readonly inputs: readonly ["autofillHint"];
|
|
170
|
+
readonly stateAxes: {
|
|
171
|
+
readonly availability: readonly ["enabled", "disabled", "readOnly"];
|
|
172
|
+
readonly value: readonly ["empty", "filled"];
|
|
173
|
+
readonly validation: readonly ["valid", "invalid"];
|
|
174
|
+
};
|
|
175
|
+
readonly web: {
|
|
176
|
+
readonly roles: readonly ["textbox", "button"];
|
|
177
|
+
readonly keyboard: readonly ["Tab", "Enter", "Space"];
|
|
178
|
+
readonly focus: "native";
|
|
179
|
+
};
|
|
180
|
+
readonly native: {
|
|
181
|
+
readonly roles: readonly ["text", "button"];
|
|
182
|
+
readonly states: readonly ["disabled"];
|
|
183
|
+
readonly actions: readonly ["focus", "setText", "toggleReveal"];
|
|
184
|
+
};
|
|
185
|
+
readonly scenarios: readonly ["toggle-never-changes-the-controlled-value", "toggle-and-value-are-independent-controlled-axes", "toggle-accessible-name-describes-the-action-not-the-current-state", "toggle-icon-mirrors-the-same-action-not-state-judgment", "toggle-is-its-own-tab-stop-after-the-field-like-searchfields-clear-button", "autofill-hint-is-a-required-product-decision-not-a-renderer-guess", "strength-meter-is-out-of-scope-product-policy"];
|
|
186
|
+
};
|
|
187
|
+
//# sourceMappingURL=password-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-field.d.ts","sourceRoot":"","sources":["../src/password-field.ts"],"names":[],"mappings":"AAWA;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,KAAK,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;;;OAQG;IACH,YAAY,EAAE,yBAAyB,CAAC;CACzC,CAAC,CAAC;AAQH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,uBAAuB,GAClC,IAAI,CAON;AAED,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,MAAM,mCAAmC,GAAG,CAChD,IAAI,EAAE,gCAAgC,KACnC,MAAM,CAAC;AAEZ,MAAM,MAAM,+BAA+B,GAAG,uBAAuB,GACnE,QAAQ,CAAC;IACP,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,UAAU,CAAC;IAClC,qBAAqB,EAAE,OAAO,CAAC;CAChC,CAAC,CAAC;AAEL,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,2BAA2B,EAAE,mCAAmC,CAAC;CAClE,CAAC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,uBAAuB,EACnC,OAAO,EAAE,2BAA2B,GACnC,+BAA+B,CAwBjC;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAuB5B;;;;WAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCN,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;CAkCG,CAAC"}
|