@fusion.dev/helix-headless 0.2.0 → 0.3.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/custom-elements.json +234 -234
- package/dist/accordion/item/accordion-item.js +0 -108
- package/dist/accordion/root/accordion-root.js +0 -33
- package/dist/announcement/root/announcement-root.js +1 -76
- package/dist/app-header/root/app-header-root.js +0 -84
- package/dist/avatar/group/avatar-group.js +0 -77
- package/dist/avatar/root/avatar-root.js +0 -72
- package/dist/badge/root/badge-root.js +0 -67
- package/dist/bar-chart/root/bar-chart-root.js +3 -263
- package/dist/blockquote/root/blockquote-root.js +0 -46
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
- package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
- package/dist/button/root/button-root.js +0 -215
- package/dist/button-group/root/button-group-root.js +0 -30
- package/dist/callout/root/callout-root.js +0 -52
- package/dist/card/root/card-root.js +0 -41
- package/dist/carousel/root/carousel-root.d.ts +2 -2
- package/dist/carousel/root/carousel-root.js +2 -362
- package/dist/chat/composer/chat-composer.js +0 -131
- package/dist/chat/message/chat-message.js +0 -137
- package/dist/chat/root/chat-root.js +0 -193
- package/dist/checkbox/root/checkbox-root.js +0 -113
- package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
- package/dist/code/root/code-root.js +0 -26
- package/dist/code-block/root/code-block-root.js +0 -100
- package/dist/color-input/root/color-input-root.js +0 -217
- package/dist/color-picker/color.js +0 -69
- package/dist/color-picker/root/color-picker-root.js +0 -272
- package/dist/combobox/root/combobox-root.js +0 -428
- package/dist/command/root/command-root.js +0 -177
- package/dist/copy-button/root/copy-button-root.js +0 -134
- package/dist/data-table/root/data-table-root.js +0 -336
- package/dist/data-table/root/order.js +0 -100
- package/dist/date-input/root/date-input-root.js +1 -398
- package/dist/date-picker/root/date-picker-root.js +0 -358
- package/dist/dialog/root/dialog-root.js +0 -189
- package/dist/divider/root/divider-root.js +0 -71
- package/dist/donut-chart/root/donut-chart-root.js +0 -214
- package/dist/drawer/root/drawer-root.js +0 -116
- package/dist/drilldown/item/drilldown-item.js +0 -82
- package/dist/drilldown/root/drilldown-root.js +0 -91
- package/dist/element-events.generated.js +0 -5
- package/dist/empty/root/empty-root.js +0 -35
- package/dist/file-input/root/file-input-root.js +0 -232
- package/dist/format-date/root/format-date-root.js +0 -95
- package/dist/heatmap/root/buckets.js +0 -57
- package/dist/heatmap/root/heatmap-root.js +0 -313
- package/dist/icon/root/icon-root.js +0 -48
- package/dist/icon-button/root/icon-button-root.js +0 -30
- package/dist/image/root/image-root.js +0 -174
- package/dist/index.js +0 -9
- package/dist/input/root/input-root.js +0 -228
- package/dist/kbd/root/kbd-root.js +0 -117
- package/dist/legend/root/legend-root.js +0 -73
- package/dist/line-chart/root/line-chart-root.js +0 -172
- package/dist/list/item/list-item.js +0 -92
- package/dist/list/root/list-root.js +0 -26
- package/dist/listbox/root/listbox-root.js +2 -429
- package/dist/loader/root/loader-root.js +0 -40
- package/dist/megamenu/root/megamenu-root.js +0 -284
- package/dist/menu/item/menu-item.js +1 -221
- package/dist/menu/root/menu-root.js +1 -171
- package/dist/meter/root/meter-root.js +1 -152
- package/dist/navigation/root/navigation-root.js +0 -56
- package/dist/notifications/notification/notification.js +0 -159
- package/dist/notifications/root/notifications-root.js +0 -148
- package/dist/number-input/root/number-input-root.js +1 -263
- package/dist/option/group/option-group.js +0 -26
- package/dist/option/root/option-root.js +0 -157
- package/dist/outline/root/outline-root.js +0 -292
- package/dist/pagination/root/pagination-root.js +0 -361
- package/dist/pagination/size/pagination-size.js +0 -104
- package/dist/pagination/summary/pagination-summary.js +0 -90
- package/dist/pin-input/root/pin-input-root.js +0 -276
- package/dist/popover/root/popover-root.js +0 -103
- package/dist/progress/root/progress-root.js +1 -133
- package/dist/qr-code/root/qr-code-root.js +0 -96
- package/dist/quote/root/quote-root.js +0 -25
- package/dist/radio/group/radio-group.js +0 -81
- package/dist/radio/root/radio-root.js +0 -85
- package/dist/rating/root/rating-root.js +0 -220
- package/dist/scroll-area/root/scroll-area-root.js +0 -100
- package/dist/select/root/select-root.js +2 -218
- package/dist/skeleton/root/skeleton-root.js +0 -20
- package/dist/slider/root/slider-root.js +1 -267
- package/dist/sparkline/root/sparkline-root.js +3 -164
- package/dist/speed-dial/item/speed-dial-item.js +0 -52
- package/dist/speed-dial/root/speed-dial-root.js +1 -116
- package/dist/splitter/root/splitter-root.js +0 -489
- package/dist/stat/group/stat-group.js +0 -18
- package/dist/stat/root/stat-root.js +0 -23
- package/dist/steps/root/steps-root.js +0 -62
- package/dist/steps/step/step.js +0 -131
- package/dist/switch/root/switch-root.js +0 -80
- package/dist/table/cell/table-cell.js +0 -23
- package/dist/table/column/table-column.js +0 -158
- package/dist/table/root/table-root.js +0 -82
- package/dist/table/row/table-row.js +0 -21
- package/dist/tabs/panel/tab-panel.js +0 -49
- package/dist/tabs/root/tabs-root.js +0 -98
- package/dist/tabs/tab/tab.js +0 -59
- package/dist/tag/root/tag-root.js +0 -31
- package/dist/textarea/root/textarea-root.js +0 -162
- package/dist/time-input/root/time-input-root.js +0 -283
- package/dist/time-picker/root/time-picker-root.js +0 -326
- package/dist/timeline/item/timeline-item.js +0 -66
- package/dist/timeline/root/timeline-root.js +0 -41
- package/dist/toast/item/toast-item.js +0 -165
- package/dist/toast/root/toast-root.js +0 -51
- package/dist/toggle/group/toggle-group.js +0 -150
- package/dist/toggle/root/toggle-root.js +0 -78
- package/dist/toolbar/root/toolbar-root.js +0 -116
- package/dist/tooltip/root/tooltip-root.js +0 -427
- package/dist/tour/root/tour-root.js +0 -130
- package/dist/tour/stop/tour-stop.js +1 -127
- package/dist/tree/item/tree-item.js +0 -198
- package/dist/tree/root/tree-root.js +0 -97
- package/dist/trend/root/trend-root.js +0 -53
- package/dist/video/root/video-root.d.ts +8 -12
- package/dist/video/root/video-root.js +5 -756
- package/dist/video-playlist/item/video-playlist-item.js +1 -90
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
- package/dist/video-playlist/root/video-playlist-root.js +0 -113
- package/dist/virtual-list/root/virtual-list-root.js +0 -105
- package/jsx.d.ts +1408 -2576
- package/package.json +6 -6
- package/vue.d.ts +1 -1
|
@@ -9,285 +9,37 @@ import { caretDoubleLeft, caretDoubleRight, caretLeft, caretRight, } from '@fusi
|
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
-
/**
|
|
13
|
-
* Fills `{page}` and `{pages}` in a template a consumer wrote.
|
|
14
|
-
*
|
|
15
|
-
* A template rather than a prefix, because word order is not universal and a
|
|
16
|
-
* `page-label="Page"` that this glued a number onto would produce the English
|
|
17
|
-
* order in every language. `href="/products?page={page}"` is the same mechanism
|
|
18
|
-
* doing a second job, which is why there is one placeholder syntax and not two.
|
|
19
|
-
*
|
|
20
|
-
* `format` is what makes the numbers local as well as the words. It is the
|
|
21
|
-
* identity for an address and an `Intl.NumberFormat` for anything a person
|
|
22
|
-
* reads: a URL is machine-facing and `?page=1,234` is a different page from
|
|
23
|
-
* `?page=1234`, where a label read aloud should say the number the way the
|
|
24
|
-
* reader's locale says it.
|
|
25
|
-
*/
|
|
26
12
|
const fill = (template, page, pages, format = String) => template.replaceAll('{page}', format(page)).replaceAll('{pages}', format(pages));
|
|
27
|
-
/**
|
|
28
|
-
* @summary Moves between pages of a longer result.
|
|
29
|
-
*
|
|
30
|
-
* @description
|
|
31
|
-
* The component is the truncation. Given a page and a total it works out which
|
|
32
|
-
* numbers to show and where the runs break, which is the whole of what a
|
|
33
|
-
* consumer would otherwise write by hand and get subtly wrong; everything else
|
|
34
|
-
* here is markup around that. So this is data-driven rather than composed:
|
|
35
|
-
* `<helix-pagination page="4" pages="12">` rather than twelve slotted children
|
|
36
|
-
* a consumer has to keep in step with their own query.
|
|
37
|
-
*
|
|
38
|
-
* **The width does not change as you page.** The run around the current page is
|
|
39
|
-
* `siblings` either side normally, and grows to absorb a gap slot when it meets
|
|
40
|
-
* a boundary, so the control holds the same number of slots from the first page
|
|
41
|
-
* to the last. Every arrangement that lets the count vary makes the buttons move
|
|
42
|
-
* under the pointer between one click and the next.
|
|
43
|
-
*
|
|
44
|
-
* **A gap is only drawn where it hides more than one page.** An ellipsis
|
|
45
|
-
* standing in for a single number costs the same space as the number and tells
|
|
46
|
-
* the reader less.
|
|
47
|
-
*
|
|
48
|
-
* **And a gap is a control.** It goes to the middle of the run it stands over,
|
|
49
|
-
* which is the binary-search step: any page in a set of any size is a handful of
|
|
50
|
-
* presses, where a jump of a fixed size would need forty of them on a set of a
|
|
51
|
-
* hundred and twenty-eight. A menu listing the hidden pages was the other idea
|
|
52
|
-
* and the arithmetic refuses it, for the reasons on `renderGap`.
|
|
53
|
-
*
|
|
54
|
-
* **`href` is what makes these links.** Set it and every page renders an `<a>`
|
|
55
|
-
* with `{page}` filled in, so the server's output is crawlable, a middle click
|
|
56
|
-
* opens a tab, and the address bar is the state. Left unset they are buttons,
|
|
57
|
-
* which is right for a table paging in place. Either way the click announces
|
|
58
|
-
* `helix-navigate` first, and `preventDefault()` on it stops the navigation as
|
|
59
|
-
* well as the change: that is how a router takes over without the page reloading
|
|
60
|
-
* underneath it.
|
|
61
|
-
*
|
|
62
|
-
* **`helix-navigate` and not `helix-change`**, for the reason the calendar pages
|
|
63
|
-
* with it too. Moving to page four chooses nothing; it moves the view over a
|
|
64
|
-
* result that has not changed. A consumer driving the view has to be able to
|
|
65
|
-
* hear that without being told an answer changed.
|
|
66
|
-
*
|
|
67
|
-
* **The count and the page-size control are their own elements.** A slot can
|
|
68
|
-
* only offer one place: at one end of one row, in one order, on one axis.
|
|
69
|
-
* `Pagination.Summary` and `Pagination.Size` sit beside this one instead, so
|
|
70
|
-
* the direction and the order of a footer are the consumer's, which is what
|
|
71
|
-
* they were always going to want. `total` and `page-size` stay here for the
|
|
72
|
-
* arithmetic below and nothing else.
|
|
73
|
-
*
|
|
74
|
-
* **A single page renders nothing.** A control offering one page offers no
|
|
75
|
-
* navigation, and the row it sits in should close over it rather than hold a
|
|
76
|
-
* space for a thing that cannot be used. Reported through
|
|
77
|
-
* `ElementInternals.states` rather than an attribute on the host, because an
|
|
78
|
-
* attribute written at upgrade is markup a server's output never had and a
|
|
79
|
-
* framework reports the difference; a custom state carries the same fact to a
|
|
80
|
-
* stylesheet and none of that to the DOM.
|
|
81
|
-
*
|
|
82
|
-
* **And a page change is announced.** Moving the page moves `aria-current` onto
|
|
83
|
-
* a different element, which a screen reader does not announce, so a reader who
|
|
84
|
-
* pressed 5 hears the word "5" from the button they were on and nothing at all
|
|
85
|
-
* about having arrived. A polite live region says where they now are. It ships
|
|
86
|
-
* empty and is filled only on a change, because a region populated as it enters
|
|
87
|
-
* the document announces itself on arrival.
|
|
88
|
-
*
|
|
89
|
-
* `page` is what the element holds and `default-page` seeds it. A consumer
|
|
90
|
-
* paging through a router refuses `helix-navigate` and assigns their own, which
|
|
91
|
-
* is how they take the decision.
|
|
92
|
-
*
|
|
93
|
-
* @since 0.1.0
|
|
94
|
-
* @status experimental
|
|
95
|
-
* @category navigation
|
|
96
|
-
* @aliases Pager, Page nav
|
|
97
|
-
* @documentation https://helix.fusion.dev/components/pagination
|
|
98
|
-
*
|
|
99
|
-
* @slot first-icon - The glyph on the jump-to-first control, usually a double caret.
|
|
100
|
-
* @slot last-icon - The glyph on the jump-to-last control, usually a double caret.
|
|
101
|
-
* @slot next-icon - The glyph on the forward-one-page control, usually a caret.
|
|
102
|
-
* @slot previous-icon - The glyph on the back-one-page control, usually a caret.
|
|
103
|
-
*
|
|
104
|
-
* @event {HelixNavigateEvent} helix-navigate - Fired before the page changes, and cancelable. The only way to refuse one.
|
|
105
|
-
*
|
|
106
|
-
* @csspart first - The control that jumps to the first page, under `ends`.
|
|
107
|
-
* @csspart gap - The control standing over pages not shown, which jumps into them.
|
|
108
|
-
* @csspart item - One slot in the run, holding a page or a gap.
|
|
109
|
-
* @csspart last - The control that jumps to the last page, under `ends`.
|
|
110
|
-
* @csspart list - The run of pages.
|
|
111
|
-
* @csspart live - The region that says the page changed. Clipped, not hidden.
|
|
112
|
-
* @csspart mark - The glyph on a first, previous, next or last control, where none is slotted.
|
|
113
|
-
* @csspart next - The control that moves forward one page.
|
|
114
|
-
* @csspart page - The control for one page.
|
|
115
|
-
* @csspart previous - The control that moves back one page.
|
|
116
|
-
* @csspart status - The reading of where you are, in `compact`.
|
|
117
|
-
*/
|
|
118
13
|
let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement {
|
|
119
14
|
constructor() {
|
|
120
15
|
super(...arguments);
|
|
121
|
-
/**
|
|
122
|
-
* How many pages to show at each end, whatever the current one is.
|
|
123
|
-
*/
|
|
124
16
|
this.boundaries = 1;
|
|
125
|
-
/**
|
|
126
|
-
* Where the numbers are replaced by a reading of the two.
|
|
127
|
-
*
|
|
128
|
-
* A different structure rather than a different look, which is why it is
|
|
129
|
-
* here: the run of pages is gone and a single status line stands in its
|
|
130
|
-
* place. This is the shape a narrow screen wants, and the only shape possible
|
|
131
|
-
* where the total is large enough that no window over it is useful.
|
|
132
|
-
*
|
|
133
|
-
* Not reflected, for the reason `ends` is not: it decides what renders, so
|
|
134
|
-
* nothing has to select on it.
|
|
135
|
-
*/
|
|
136
17
|
this.compact = false;
|
|
137
|
-
/**
|
|
138
|
-
* Page to open on, where nothing declared one.
|
|
139
|
-
*/
|
|
140
18
|
this.defaultPage = 1;
|
|
141
|
-
/**
|
|
142
|
-
* Whether to offer a jump to the first and last pages.
|
|
143
|
-
*
|
|
144
|
-
* Off by default. The gaps already reach any page in a few presses and the
|
|
145
|
-
* last page is one of the two the run always shows, so on a short set these
|
|
146
|
-
* are two more controls for a move already on screen. They earn their place
|
|
147
|
-
* where the reader is deep in a long result and "back to the start" is the
|
|
148
|
-
* move they actually want.
|
|
149
|
-
*
|
|
150
|
-
* Not reflected. It adds two elements rather than changing how any of them
|
|
151
|
-
* look, so no rule selects on it, and the house rule is that reflection
|
|
152
|
-
* exists for selectors: an unread reflected attribute is one more thing
|
|
153
|
-
* written onto a host at upgrade that a server's output never had.
|
|
154
|
-
*/
|
|
155
19
|
this.ends = false;
|
|
156
|
-
/**
|
|
157
|
-
* Accessible name for the control that jumps to the first page.
|
|
158
|
-
*/
|
|
159
20
|
this.firstLabel = 'First page';
|
|
160
|
-
/**
|
|
161
|
-
* Accessible name for a gap, with `{page}` filled in as where it goes.
|
|
162
|
-
*/
|
|
163
21
|
this.gapLabel = 'Skip to page {page}';
|
|
164
|
-
/**
|
|
165
|
-
* Address for a page, with `{page}` standing in for its number.
|
|
166
|
-
*
|
|
167
|
-
* Absent renders buttons. Present renders links, which is what a paged result
|
|
168
|
-
* with its own URL should be: a crawler follows them, a middle click opens
|
|
169
|
-
* one, and the back button works without the consumer wiring anything.
|
|
170
|
-
*/
|
|
171
22
|
this.href = '';
|
|
172
|
-
/**
|
|
173
|
-
* Accessible name for the navigation landmark.
|
|
174
|
-
*/
|
|
175
23
|
this.label = 'Pagination';
|
|
176
|
-
/**
|
|
177
|
-
* Accessible name for the control that jumps to the last page.
|
|
178
|
-
*/
|
|
179
24
|
this.lastLabel = 'Last page';
|
|
180
|
-
/**
|
|
181
|
-
* Locale for the page numbers. Absent uses the runtime's own.
|
|
182
|
-
*
|
|
183
|
-
* The same property the calendar takes, doing the same job one layer down:
|
|
184
|
-
* the templates make the *words* the consumer's, and this makes the *numbers*
|
|
185
|
-
* theirs. A page number is a number like any other, so `1234` is `1,234` in
|
|
186
|
-
* `en-US`, `1.234` in `de-DE` and `١٢٣٤` in `ar-EG`, and a component that
|
|
187
|
-
* lets a consumer translate "Page {page} of {pages}" and then prints Latin
|
|
188
|
-
* digits into it has done half the job.
|
|
189
|
-
*
|
|
190
|
-
* Not applied to `href`. That one is machine-facing, and `?page=1,234` is a
|
|
191
|
-
* different address from `?page=1234`.
|
|
192
|
-
*/
|
|
193
25
|
this.locale = '';
|
|
194
|
-
/**
|
|
195
|
-
* Accessible name for the control that moves forward.
|
|
196
|
-
*/
|
|
197
26
|
this.nextLabel = 'Next page';
|
|
198
|
-
/**
|
|
199
|
-
* Accessible name for one page, with `{page}` and `{pages}` filled in.
|
|
200
|
-
*/
|
|
201
27
|
this.pageLabel = 'Page {page}';
|
|
202
|
-
/**
|
|
203
|
-
* How many pages there are in total.
|
|
204
|
-
*
|
|
205
|
-
* Clamped to at least one wherever it is read, so a result that has not
|
|
206
|
-
* loaded yet renders a single page rather than an empty control that then
|
|
207
|
-
* appears and pushes the layout down.
|
|
208
|
-
*
|
|
209
|
-
* Left unset where `total` and `page-size` are both given, since the two of
|
|
210
|
-
* them already say it. A fallback rather than an override: writing this wins,
|
|
211
|
-
* so there is never a question of which of two sources is current.
|
|
212
|
-
*/
|
|
213
28
|
this.pages = 0;
|
|
214
|
-
/**
|
|
215
|
-
* How many items are on a page.
|
|
216
|
-
*
|
|
217
|
-
* Only arithmetic: this does not slice anything, because the component never
|
|
218
|
-
* sees the items. It is what turns `total` into a page count, and nothing
|
|
219
|
-
* else.
|
|
220
|
-
*/
|
|
221
29
|
this.pageSize = 0;
|
|
222
|
-
/**
|
|
223
|
-
* Accessible name for the control that moves back.
|
|
224
|
-
*/
|
|
225
30
|
this.previousLabel = 'Previous page';
|
|
226
|
-
/**
|
|
227
|
-
* How many pages to show either side of the current one.
|
|
228
|
-
*/
|
|
229
31
|
this.siblings = 1;
|
|
230
|
-
/**
|
|
231
|
-
* The reading shown in `compact`, with `{page}` and `{pages}` filled in.
|
|
232
|
-
*
|
|
233
|
-
* No noun: the landmark this sits in is already named "Pagination", so
|
|
234
|
-
* "Page 7 of 24" says it twice to a screen reader and takes a third of the
|
|
235
|
-
* width from a control whose whole reason for existing is that the row is
|
|
236
|
-
* narrow. The numbers are the reading.
|
|
237
|
-
*/
|
|
238
32
|
this.statusLabel = '{page} of {pages}';
|
|
239
|
-
/**
|
|
240
|
-
* How many items there are across every page.
|
|
241
|
-
*
|
|
242
|
-
* What `pages` falls back to together with `page-size`. Unset is the common
|
|
243
|
-
* case, where a consumer knows the page total and not the item total.
|
|
244
|
-
*
|
|
245
|
-
* Nothing is rendered from it. `Pagination.Summary` is what reads a total out
|
|
246
|
-
* to a reader, and takes its own.
|
|
247
|
-
*/
|
|
248
33
|
this.total = 0;
|
|
249
|
-
/**
|
|
250
|
-
* What the live region says, and nothing until the page has moved once.
|
|
251
|
-
*
|
|
252
|
-
* `pageLabel` rather than a template of its own, so the announcement is the
|
|
253
|
-
* same sentence the control the reader just pressed already carried, and
|
|
254
|
-
* there is not a ninth string to translate for the sake of one of them.
|
|
255
|
-
*/
|
|
256
34
|
this.announcement = '';
|
|
257
|
-
/** The page last announced, so the first render announces nothing. */
|
|
258
35
|
this.announced = undefined;
|
|
259
|
-
/** The page on show, whoever set it, and `undefined` until somebody has. */
|
|
260
36
|
this.pageState = undefined;
|
|
261
|
-
/**
|
|
262
|
-
* The click handler, which is also what stops a link from following itself.
|
|
263
|
-
*
|
|
264
|
-
* A refused change has to refuse the navigation with it, or a router that
|
|
265
|
-
* cancels the event still loses the document underneath it. `preventDefault`
|
|
266
|
-
* on a button's click does nothing and costs nothing, so this is one handler
|
|
267
|
-
* for both shapes rather than two that have to stay in step.
|
|
268
|
-
*/
|
|
269
37
|
this.go = (next, event) => {
|
|
270
38
|
if (!this.request(next)) {
|
|
271
39
|
event.preventDefault();
|
|
272
40
|
}
|
|
273
41
|
};
|
|
274
42
|
}
|
|
275
|
-
/**
|
|
276
|
-
* The page on show, one-based.
|
|
277
|
-
*
|
|
278
|
-
* An accessor pair, the same shape as the calendar's `month`. Reading gives
|
|
279
|
-
* the page on show; writing sets it, and `default-page` seeds one the element
|
|
280
|
-
* keeps until somebody does.
|
|
281
|
-
*
|
|
282
|
-
* **Clamped on the way out, because "the page on show" is what it says it
|
|
283
|
-
* gives.** Returning whatever was written means `default-page="99"` on a
|
|
284
|
-
* ten-page result renders page 10, marks page 10 with `aria-current`, and
|
|
285
|
-
* reports 99, so a consumer syncing their query from this reads a page that
|
|
286
|
-
* does not exist while the control in front of them says otherwise. Not a
|
|
287
|
-
* far-fetched value: it is a bookmarked link after the result shrank.
|
|
288
|
-
*
|
|
289
|
-
* Rounded here too, so `2.7` reads as the 2 it renders.
|
|
290
|
-
*/
|
|
291
43
|
get page() {
|
|
292
44
|
const asked = this.pageState ?? this.defaultPage;
|
|
293
45
|
return Math.min(this.count, Math.max(1, Math.floor(asked) || 1));
|
|
@@ -297,19 +49,9 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
297
49
|
this.pageState = next;
|
|
298
50
|
this.requestUpdate('page', previous);
|
|
299
51
|
}
|
|
300
|
-
/** Held across every component asking for the same locale. */
|
|
301
52
|
get numbers() {
|
|
302
53
|
return formatNumbers(this.locale);
|
|
303
54
|
}
|
|
304
|
-
/**
|
|
305
|
-
* How many pages, clamped once, so every reader agrees on it.
|
|
306
|
-
*
|
|
307
|
-
* `pages` first, because writing it is the explicit answer. Then `total` over
|
|
308
|
-
* `pageSize`, which is the same fact stated the other way round and is what a
|
|
309
|
-
* consumer holding a result set already has. Then one, so a result that has
|
|
310
|
-
* not loaded renders a single page rather than an empty control that appears
|
|
311
|
-
* later and pushes the layout down.
|
|
312
|
-
*/
|
|
313
55
|
get count() {
|
|
314
56
|
if (this.pages > 0) {
|
|
315
57
|
return Math.max(1, Math.floor(this.pages));
|
|
@@ -319,23 +61,9 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
319
61
|
}
|
|
320
62
|
return 1;
|
|
321
63
|
}
|
|
322
|
-
/** The page on show, which `page` has already clamped. */
|
|
323
64
|
get current() {
|
|
324
65
|
return this.page;
|
|
325
66
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Which pages to show, and where the runs break.
|
|
328
|
-
*
|
|
329
|
-
* `most` is the number of slots the control holds at its widest: both
|
|
330
|
-
* boundaries, the run around the current page, and a gap on each side. Below
|
|
331
|
-
* that there is nothing to hide, and the whole range is shown.
|
|
332
|
-
*
|
|
333
|
-
* Above it, the two gaps are decided independently, and a gap standing over
|
|
334
|
-
* exactly one page is not drawn: `boundaries + 2` is the first page whose
|
|
335
|
-
* predecessor is not a boundary, so a run starting there is contiguous with
|
|
336
|
-
* what precedes it. Where one side has no gap the run on that side extends to
|
|
337
|
-
* `most` slots, which is what holds the width still.
|
|
338
|
-
*/
|
|
339
67
|
get slots() {
|
|
340
68
|
const total = this.count;
|
|
341
69
|
const current = this.current;
|
|
@@ -349,8 +77,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
349
77
|
}
|
|
350
78
|
const before = current - siblings > boundaries + 2;
|
|
351
79
|
const after = current + siblings < total - boundaries - 1;
|
|
352
|
-
/* A gap carries the run it stands over rather than being a bare marker,
|
|
353
|
-
because it is a control now and a control has to say where it goes. */
|
|
354
80
|
const gap = (from, to) => ({ from, to });
|
|
355
81
|
if (!before) {
|
|
356
82
|
const head = most - boundaries - 1;
|
|
@@ -372,13 +98,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
372
98
|
...range(total - boundaries + 1, total),
|
|
373
99
|
];
|
|
374
100
|
}
|
|
375
|
-
/**
|
|
376
|
-
* Asks to move, and reports whether the request stood.
|
|
377
|
-
*
|
|
378
|
-
* Announced before it is applied, so `preventDefault()` is a refusal and
|
|
379
|
-
* nothing moves. A consumer who holds the page refuses it and assigns their
|
|
380
|
-
* own.
|
|
381
|
-
*/
|
|
382
101
|
request(next) {
|
|
383
102
|
if (next === this.current || next < 1 || next > this.count) {
|
|
384
103
|
return false;
|
|
@@ -396,28 +115,12 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
396
115
|
}
|
|
397
116
|
willUpdate(changed) {
|
|
398
117
|
super.willUpdate(changed);
|
|
399
|
-
/* On the host through internals, so the landmark is the element itself. A
|
|
400
|
-
page may hold more than one navigation and landmarks are how a
|
|
401
|
-
screen-reader user chooses between them, which is why this carries a
|
|
402
|
-
default name at all: an author's own `aria-label` is an attribute and
|
|
403
|
-
beats internals, so naming it here costs them nothing. */
|
|
404
118
|
hostAria(this.internals, { label: this.label, role: 'navigation' });
|
|
405
|
-
/* Through `toggleState`, which carries the feature test: an engine without
|
|
406
|
-
`CustomStateSet` gets a control that renders its one page instead of
|
|
407
|
-
disappearing, which is degraded rather than broken. */
|
|
408
119
|
this.toggleState('empty', this.count <= 1);
|
|
409
120
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Says where the reader now is, once they have moved.
|
|
412
|
-
*
|
|
413
|
-
* In `updated` rather than in `request`, so it covers both modes: a controlled
|
|
414
|
-
* consumer assigns `page` and never goes through the request path at all, and
|
|
415
|
-
* a reader who got there that way deserves the same sentence.
|
|
416
|
-
*/
|
|
417
121
|
updated(changed) {
|
|
418
122
|
super.updated(changed);
|
|
419
123
|
if (this.announced === undefined) {
|
|
420
|
-
// The first render establishes where we started. Arriving is not moving.
|
|
421
124
|
this.announced = this.page;
|
|
422
125
|
return;
|
|
423
126
|
}
|
|
@@ -426,13 +129,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
426
129
|
this.announcement = fill(this.pageLabel, this.page, this.count, this.numbers);
|
|
427
130
|
}
|
|
428
131
|
}
|
|
429
|
-
/**
|
|
430
|
-
* One page, as a link or a button.
|
|
431
|
-
*
|
|
432
|
-
* `aria-current="page"` is what says which one you are on, and it stays a
|
|
433
|
-
* control rather than becoming text: a reader who tabs into the run and finds
|
|
434
|
-
* the current page missing from it has lost the only landmark in the row.
|
|
435
|
-
*/
|
|
436
132
|
renderPage(page) {
|
|
437
133
|
const current = page === this.current;
|
|
438
134
|
const shown = this.numbers(page);
|
|
@@ -458,27 +154,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
458
154
|
</button>`}
|
|
459
155
|
</li>`;
|
|
460
156
|
}
|
|
461
|
-
/**
|
|
462
|
-
* A gap, which is a control and not a mark.
|
|
463
|
-
*
|
|
464
|
-
* **It goes to the middle of what it hides**, and the label says so. That is
|
|
465
|
-
* the binary-search step, so an arbitrary page in a set of any size is a
|
|
466
|
-
* handful of presses: 128 pages is `1 … 96 97 98 … 128`, and the leading gap
|
|
467
|
-
* lands on 48, whose gaps land on 24 and 88, and so on. A jump of a fixed size
|
|
468
|
-
* would need forty presses on that set.
|
|
469
|
-
*
|
|
470
|
-
* **A menu over these pages was the other idea and it does not survive the
|
|
471
|
-
* arithmetic.** The run a gap covers is unbounded by construction: two pages
|
|
472
|
-
* on a twelve-page set, ninety-four on a hundred-and-twenty-eight-page one. A
|
|
473
|
-
* menu is pleasant at the first and is a scroll of ninety-four items at the
|
|
474
|
-
* second, which is worse than the ellipsis it replaced, and there is no total
|
|
475
|
-
* at which it stops being possible. It would also make `Pagination` depend on
|
|
476
|
-
* `Menu` and `Popover` for a case a button already covers.
|
|
477
|
-
*
|
|
478
|
-
* So it is announced rather than hidden: a control a reader can reach has to
|
|
479
|
-
* be in the tree, and the label naming its destination is the whole
|
|
480
|
-
* affordance.
|
|
481
|
-
*/
|
|
482
157
|
renderGap(hidden) {
|
|
483
158
|
const to = Math.round((hidden.from + hidden.to) / 2);
|
|
484
159
|
const name = fill(this.gapLabel, to, this.count, this.numbers);
|
|
@@ -501,15 +176,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
501
176
|
</button>`}
|
|
502
177
|
</li>`;
|
|
503
178
|
}
|
|
504
|
-
/**
|
|
505
|
-
* Everything a step needs, computed once, so the markup below is only markup.
|
|
506
|
-
*
|
|
507
|
-
* Split out because the part name has to be a literal. A part name is read
|
|
508
|
-
* from the source, so a computed one is a part nothing can see: nothing pairs it with its `@csspart` and a later rename takes
|
|
509
|
-
* the promise silently. With four controls and two shapes each that is eight
|
|
510
|
-
* literals, and eight copies of the arithmetic behind them would be the
|
|
511
|
-
* actual duplication. This is the arithmetic, written once.
|
|
512
|
-
*/
|
|
513
179
|
step(to, spent, icon, name) {
|
|
514
180
|
return {
|
|
515
181
|
address: spent ? nothing : fill(this.href, to, this.count),
|
|
@@ -522,26 +188,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
522
188
|
spent,
|
|
523
189
|
};
|
|
524
190
|
}
|
|
525
|
-
/**
|
|
526
|
-
* The four controls, inert at the end each points at.
|
|
527
|
-
*
|
|
528
|
-
* Inert rather than absent: a control that disappears at the first page takes
|
|
529
|
-
* the row's width with it and moves everything else under the pointer. In
|
|
530
|
-
* link mode the inert one is an `<a>` with no `href`, which is not a link and
|
|
531
|
-
* is not focusable, and is the platform's own way of saying there is nowhere
|
|
532
|
-
* to go.
|
|
533
|
-
*
|
|
534
|
-
* **Each glyph is a slot fallback, and each method is a seam**, which answer
|
|
535
|
-
* two different people: the slot is how a page swaps a caret for its own icon
|
|
536
|
-
* set, and `protected` is how the tier above swaps all four without
|
|
537
|
-
* reimplementing a template that renders eight controls across two shapes.
|
|
538
|
-
* `Select` and `Listbox` carry the same pairing on one mark each.
|
|
539
|
-
*
|
|
540
|
-
* **Four slot names rather than one**, even though the four controls draw
|
|
541
|
-
* from the same shared `mark` part. A single `<slot name="icon">` is assigned
|
|
542
|
-
* once and the other three occurrences of it get nothing, so a consumer
|
|
543
|
-
* replacing the caret would silently lose three of their four marks.
|
|
544
|
-
*/
|
|
545
191
|
renderFirst() {
|
|
546
192
|
const { address, click, glyph, name, off, spent } = this.step(1, this.current <= 1, caretDoubleLeft, this.firstLabel);
|
|
547
193
|
const mark = html `<slot name="first-icon">${glyph}</slot>`;
|
|
@@ -611,13 +257,6 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
611
257
|
</ol>
|
|
612
258
|
${this.renderNext()}${this.ends ? this.renderLast() : nothing}${this.renderLive()}`;
|
|
613
259
|
}
|
|
614
|
-
/**
|
|
615
|
-
* The region that says the page moved.
|
|
616
|
-
*
|
|
617
|
-
* Always rendered and empty until it has something to say. A live region
|
|
618
|
-
* populated at the moment it enters the document announces itself on arrival,
|
|
619
|
-
* which for a component that upgrades on load is every page load.
|
|
620
|
-
*/
|
|
621
260
|
renderLive() {
|
|
622
261
|
return html `<span part="live" role="status" aria-live="polite">${this.announcement}</span>`;
|
|
623
262
|
}
|
|
@@ -8,87 +8,17 @@ import { customElement, formatNumbers, HelixBeforeChangeEvent, HelixElement, req
|
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
11
|
-
/* Side-effect imports, because this renders a select and an option in its own
|
|
12
|
-
shadow root and a custom element registers on import. The paths are relative,
|
|
13
|
-
so this registers the base pair and the styled file's identical imports
|
|
14
|
-
register the styled pair; `tier` picks which names the template asks for. */
|
|
15
11
|
import '../../option/index.js';
|
|
16
12
|
import '../../select/index.js';
|
|
17
|
-
/**
|
|
18
|
-
* @summary Chooses how many items a page holds.
|
|
19
|
-
*
|
|
20
|
-
* @description
|
|
21
|
-
* Its own element beside `Pagination` rather than a slot inside it, for the
|
|
22
|
-
* reason `Pagination.Summary` is: a footer's direction and order are the
|
|
23
|
-
* consumer's, and a control that can only sit at one end of one row has taken
|
|
24
|
-
* that decision for them.
|
|
25
|
-
*
|
|
26
|
-
* **`Select`, not a native `<select>`.** Two dropdowns identical closed and
|
|
27
|
-
* different open meet each other in the same footer as a table's filters. The
|
|
28
|
-
* caret is `Select`'s rather than a color literal baked into a data URI,
|
|
29
|
-
* because a background image cannot read `currentColor`, and the label is a
|
|
30
|
-
* real `<label for>` against the trigger's own id.
|
|
31
|
-
*
|
|
32
|
-
* **The label sits on the row, not above it.** `Select`'s host is a column, and
|
|
33
|
-
* an outer-tree rule beats a `:host` rule, so turning it into a row needs no
|
|
34
|
-
* property on `Select` and nothing written into the consumer's markup.
|
|
35
|
-
*
|
|
36
|
-
* **An empty `sizes` renders nothing**, rather than falling back to the
|
|
37
|
-
* built-in list, which would be a component overruling a consumer who said
|
|
38
|
-
* "none". Same rule as `Pagination` over a single page, and reported the same
|
|
39
|
-
* way, through `ElementInternals.states` rather than an attribute the server
|
|
40
|
-
* never wrote.
|
|
41
|
-
*
|
|
42
|
-
* `value` is the controlled property and `default-value` seeds one this owns.
|
|
43
|
-
* Changing it announces `helix-before-change`; re-querying and moving back to
|
|
44
|
-
* page one is the consumer's, because only they know whether the reader's
|
|
45
|
-
* position should survive it.
|
|
46
|
-
*
|
|
47
|
-
* @since 0.1.0
|
|
48
|
-
* @status experimental
|
|
49
|
-
* @aliases Page size, Rows per page
|
|
50
|
-
* @documentation https://helix.fusion.dev/components/pagination
|
|
51
|
-
*
|
|
52
|
-
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before the size changes, and cancelable. The only way to refuse one.
|
|
53
|
-
*
|
|
54
|
-
* @csspart control - The select.
|
|
55
|
-
* @csspart label - The words beside the number, forwarded from the select.
|
|
56
|
-
* @csspart trigger - The closed control, forwarded from the select.
|
|
57
|
-
* @csspart value - The chosen size inside the trigger, forwarded from the select.
|
|
58
|
-
*/
|
|
59
13
|
let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends HelixElement {
|
|
60
14
|
constructor() {
|
|
61
15
|
super(...arguments);
|
|
62
|
-
/**
|
|
63
|
-
* Size to start on, where nothing declared one.
|
|
64
|
-
*/
|
|
65
16
|
this.defaultValue = 0;
|
|
66
|
-
/**
|
|
67
|
-
* The words beside the control.
|
|
68
|
-
*/
|
|
69
17
|
this.label = 'Per page';
|
|
70
|
-
/**
|
|
71
|
-
* Locale for the numbers.
|
|
72
|
-
*
|
|
73
|
-
* Absent uses the runtime's own.
|
|
74
|
-
*/
|
|
75
18
|
this.locale = '';
|
|
76
|
-
/**
|
|
77
|
-
* The sizes offered, comma separated.
|
|
78
|
-
*
|
|
79
|
-
* A string rather than an array, so the whole control is expressible in
|
|
80
|
-
* markup and survives a server render. Anything that is not a positive number
|
|
81
|
-
* is dropped rather than rendered as an option that cannot mean anything.
|
|
82
|
-
*/
|
|
83
19
|
this.sizes = '10,25,50,100';
|
|
84
|
-
/** The size, whoever set it, and `undefined` until somebody has. */
|
|
85
20
|
this.valueState = undefined;
|
|
86
21
|
this.handleChange = (event) => {
|
|
87
|
-
/* Stopped, then re-announced under this element's own name. `Select`'s
|
|
88
|
-
`helix-before-change` bubbles and is composed, so left alone it would cross this
|
|
89
|
-
shadow boundary and be retargeted to look like this element's: a consumer
|
|
90
|
-
would get two events per change, one of them carrying `Select`'s idea of
|
|
91
|
-
what a name and a value are. */
|
|
92
22
|
event.stopPropagation();
|
|
93
23
|
const next = Number.parseInt(event.detail?.value ?? '', 10);
|
|
94
24
|
if (!Number.isFinite(next) || next === this.current) {
|
|
@@ -104,13 +34,6 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
|
|
|
104
34
|
host: this,
|
|
105
35
|
next,
|
|
106
36
|
});
|
|
107
|
-
/* Put back where the consumer refused it or owns it, because the control has
|
|
108
|
-
already moved: a select changes itself and then tells you, which is the
|
|
109
|
-
one case `requestChange` cannot announce ahead of. Lit skips a
|
|
110
|
-
property binding whose value has not changed, so a refusal leaves the
|
|
111
|
-
select holding the reader's choice and nothing else would put it back.
|
|
112
|
-
Deferred, so the read happens after the render a successful change asked
|
|
113
|
-
for rather than racing it. */
|
|
114
37
|
this.updateComplete.then(() => {
|
|
115
38
|
const control = this.renderRoot?.querySelector('[part="control"]');
|
|
116
39
|
if (control && control.value !== String(this.current)) {
|
|
@@ -119,13 +42,6 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
|
|
|
119
42
|
});
|
|
120
43
|
};
|
|
121
44
|
}
|
|
122
|
-
/**
|
|
123
|
-
* The size in force.
|
|
124
|
-
*
|
|
125
|
-
* Reading gives the real one; writing declares it the consumer's, and
|
|
126
|
-
* `default-value` seeds one this element owns. Same accessor pair as
|
|
127
|
-
* `Pagination.page` and the calendar's `month`.
|
|
128
|
-
*/
|
|
129
45
|
get value() {
|
|
130
46
|
return this.valueState ?? this.defaultValue;
|
|
131
47
|
}
|
|
@@ -134,40 +50,23 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
|
|
|
134
50
|
this.valueState = next;
|
|
135
51
|
this.requestUpdate('value', previous);
|
|
136
52
|
}
|
|
137
|
-
/** Held across every component asking for the same locale. */
|
|
138
53
|
get numbers() {
|
|
139
54
|
return formatNumbers(this.locale);
|
|
140
55
|
}
|
|
141
|
-
/** The sizes, parsed once, with anything that is not a size dropped. */
|
|
142
56
|
get options() {
|
|
143
57
|
return this.sizes
|
|
144
58
|
.split(',')
|
|
145
59
|
.map((one) => Number.parseInt(one.trim(), 10))
|
|
146
60
|
.filter((one) => Number.isFinite(one) && one > 0);
|
|
147
61
|
}
|
|
148
|
-
/** The one in force, which is the first offered where nothing named one. */
|
|
149
62
|
get current() {
|
|
150
63
|
const options = this.options;
|
|
151
64
|
return options.includes(this.value) ? this.value : (options[0] ?? 0);
|
|
152
65
|
}
|
|
153
66
|
willUpdate(changed) {
|
|
154
67
|
super.willUpdate(changed);
|
|
155
|
-
/* Through `toggleState`, like `Pagination`'s: an engine without
|
|
156
|
-
`CustomStateSet` gets an empty select rather than no element. */
|
|
157
68
|
this.toggleState('empty', this.options.length === 0);
|
|
158
69
|
}
|
|
159
|
-
/**
|
|
160
|
-
* The control itself, which is the whole of this part, behind a seam the styled tier can move.
|
|
161
|
-
*
|
|
162
|
-
* A method rather than a fragment inline, so a tier above can render its own,
|
|
163
|
-
* wrap this one, pass it a property the base does not know about, or leave it
|
|
164
|
-
* out, without reimplementing the whole template to do any of them.
|
|
165
|
-
*
|
|
166
|
-
* **The tags are interpolated, so each tier renders its own.** Writing
|
|
167
|
-
* `<helix-select>` out would make this tier compose an element it does not
|
|
168
|
-
* ship and cannot test against, there being no `helix-select` here. `tier`
|
|
169
|
-
* resolves the name instead, and the styled subclass changes it in one line.
|
|
170
|
-
*/
|
|
171
70
|
renderSelect() {
|
|
172
71
|
const select = this.tag('select');
|
|
173
72
|
const option = this.tag('option');
|
|
@@ -185,9 +84,6 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
|
|
|
185
84
|
if (this.options.length === 0) {
|
|
186
85
|
return nothing;
|
|
187
86
|
}
|
|
188
|
-
/* `Select` renders the label and gives the trigger the id it points at, so
|
|
189
|
-
the association is the platform's and is already covered by that
|
|
190
|
-
component's own specs. Nothing here reconstructs it. */
|
|
191
87
|
return html `${this.renderSelect()}`;
|
|
192
88
|
}
|
|
193
89
|
};
|