@limetech/lime-elements 40.4.1 → 40.5.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/component-UyqdiWGc.js +1576 -0
  3. package/dist/cjs/lime-elements.cjs.js +1 -1
  4. package/dist/cjs/limel-action-bar_3.cjs.entry.js +1 -1
  5. package/dist/cjs/limel-ai-avatar.cjs.entry.js +1 -1
  6. package/dist/cjs/{limel-breadcrumbs_8.cjs.entry.js → limel-breadcrumbs_6.cjs.entry.js} +221 -2175
  7. package/dist/cjs/limel-callout.cjs.entry.js +1 -1
  8. package/dist/cjs/limel-chart.cjs.entry.js +1 -1
  9. package/dist/cjs/limel-chip-set.cjs.entry.js +1 -1
  10. package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
  11. package/dist/cjs/limel-code-diff.cjs.entry.js +1 -1
  12. package/dist/cjs/limel-code-editor.cjs.entry.js +1 -1
  13. package/dist/cjs/limel-collapsible-section.cjs.entry.js +1 -1
  14. package/dist/cjs/limel-drag-handle.cjs.entry.js +1 -1
  15. package/dist/cjs/limel-email-viewer.cjs.entry.js +1 -1
  16. package/dist/cjs/limel-file-viewer.cjs.entry.js +1 -1
  17. package/dist/cjs/limel-file.cjs.entry.js +1 -1
  18. package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +1 -1
  19. package/dist/cjs/limel-helper-line_3.cjs.entry.js +454 -0
  20. package/dist/cjs/limel-list-item.cjs.entry.js +1 -1
  21. package/dist/cjs/limel-pagination-jump.cjs.entry.js +182 -0
  22. package/dist/cjs/limel-pagination.cjs.entry.js +165 -126
  23. package/dist/cjs/limel-picker.cjs.entry.js +1 -1
  24. package/dist/cjs/limel-popover_2.cjs.entry.js +33 -25
  25. package/dist/cjs/limel-profile-picture.cjs.entry.js +1 -1
  26. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +1 -1
  27. package/dist/cjs/limel-slider.cjs.entry.js +1 -1
  28. package/dist/cjs/limel-snackbar.cjs.entry.js +1 -1
  29. package/dist/cjs/limel-table.cjs.entry.js +1 -1
  30. package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +1 -1
  31. package/dist/cjs/loader.cjs.js +1 -1
  32. package/dist/cjs/pagination.util-B5zzH8vg.js +135 -0
  33. package/dist/cjs/{translations-C5T2-_rH.js → translations-DFFpR6AZ.js} +32 -0
  34. package/dist/collection/collection-manifest.json +1 -0
  35. package/dist/collection/components/menu/menu.js +44 -22
  36. package/dist/collection/components/pagination/jump/pagination-jump.css +11 -0
  37. package/dist/collection/components/pagination/jump/pagination-jump.js +331 -0
  38. package/dist/collection/components/pagination/pagination.css +8 -4
  39. package/dist/collection/components/pagination/pagination.js +167 -10
  40. package/dist/collection/components/pagination/pagination.util.js +25 -8
  41. package/dist/collection/components/popover/popover.js +33 -25
  42. package/dist/collection/translations/da.js +4 -0
  43. package/dist/collection/translations/de.js +4 -0
  44. package/dist/collection/translations/en.js +4 -0
  45. package/dist/collection/translations/fi.js +4 -0
  46. package/dist/collection/translations/fr.js +4 -0
  47. package/dist/collection/translations/nl.js +4 -0
  48. package/dist/collection/translations/no.js +4 -0
  49. package/dist/collection/translations/sv.js +4 -0
  50. package/dist/esm/component-B2Vy44Z3.js +1570 -0
  51. package/dist/esm/lime-elements.js +1 -1
  52. package/dist/esm/limel-action-bar_3.entry.js +1 -1
  53. package/dist/esm/limel-ai-avatar.entry.js +1 -1
  54. package/dist/esm/{limel-breadcrumbs_8.entry.js → limel-breadcrumbs_6.entry.js} +47 -1999
  55. package/dist/esm/limel-callout.entry.js +1 -1
  56. package/dist/esm/limel-chart.entry.js +1 -1
  57. package/dist/esm/limel-chip-set.entry.js +1 -1
  58. package/dist/esm/limel-chip_2.entry.js +1 -1
  59. package/dist/esm/limel-code-diff.entry.js +1 -1
  60. package/dist/esm/limel-code-editor.entry.js +1 -1
  61. package/dist/esm/limel-collapsible-section.entry.js +1 -1
  62. package/dist/esm/limel-drag-handle.entry.js +1 -1
  63. package/dist/esm/limel-email-viewer.entry.js +1 -1
  64. package/dist/esm/limel-file-viewer.entry.js +1 -1
  65. package/dist/esm/limel-file.entry.js +1 -1
  66. package/dist/esm/limel-flatpickr-adapter.entry.js +1 -1
  67. package/dist/esm/limel-helper-line_3.entry.js +450 -0
  68. package/dist/esm/limel-list-item.entry.js +1 -1
  69. package/dist/esm/limel-pagination-jump.entry.js +180 -0
  70. package/dist/esm/limel-pagination.entry.js +165 -126
  71. package/dist/esm/limel-picker.entry.js +1 -1
  72. package/dist/esm/limel-popover_2.entry.js +33 -25
  73. package/dist/esm/limel-profile-picture.entry.js +1 -1
  74. package/dist/esm/limel-prosemirror-adapter.entry.js +1 -1
  75. package/dist/esm/limel-slider.entry.js +1 -1
  76. package/dist/esm/limel-snackbar.entry.js +1 -1
  77. package/dist/esm/limel-table.entry.js +1 -1
  78. package/dist/esm/limel-text-editor-link-menu.entry.js +1 -1
  79. package/dist/esm/loader.js +1 -1
  80. package/dist/esm/pagination.util-CSC2LJ7P.js +132 -0
  81. package/dist/esm/{translations-Bwm2y2E4.js → translations-DMtX4tTX.js} +32 -0
  82. package/dist/lime-elements/lime-elements.esm.js +1 -1
  83. package/dist/lime-elements/{p-2bbdac10.entry.js → p-11a96a8c.entry.js} +1 -1
  84. package/dist/lime-elements/p-1dd86ad4.entry.js +1 -0
  85. package/dist/lime-elements/p-1e93473a.entry.js +1 -0
  86. package/dist/lime-elements/{p-a41b5ff3.entry.js → p-2e568c56.entry.js} +1 -1
  87. package/dist/lime-elements/{p-de631fc3.entry.js → p-2f57c3fa.entry.js} +1 -1
  88. package/dist/lime-elements/p-3735111e.entry.js +1 -0
  89. package/dist/lime-elements/{p-c823054d.entry.js → p-452b9b01.entry.js} +1 -1
  90. package/dist/lime-elements/{p-f333f12d.entry.js → p-4855dc9b.entry.js} +1 -1
  91. package/dist/lime-elements/{p-a462aeb7.entry.js → p-4902fb3f.entry.js} +1 -1
  92. package/dist/lime-elements/p-507cc8a1.entry.js +159 -0
  93. package/dist/lime-elements/{p-dadd2e17.entry.js → p-531b6a64.entry.js} +1 -1
  94. package/dist/lime-elements/{p-b194a212.entry.js → p-5698e493.entry.js} +1 -1
  95. package/dist/lime-elements/{p-65a9adfc.entry.js → p-67f02b69.entry.js} +1 -1
  96. package/dist/lime-elements/{p-1162dd39.entry.js → p-867681aa.entry.js} +1 -1
  97. package/dist/lime-elements/{p-f72ee01b.entry.js → p-90df3e1b.entry.js} +1 -1
  98. package/dist/lime-elements/{p-22f85b99.entry.js → p-955292eb.entry.js} +1 -1
  99. package/dist/lime-elements/{p-c2ff132c.entry.js → p-9b631363.entry.js} +1 -1
  100. package/dist/lime-elements/{p-8af3e4a1.entry.js → p-9d3437ac.entry.js} +1 -1
  101. package/dist/lime-elements/p-B8hoCfhT.js +1 -0
  102. package/dist/lime-elements/p-BwJGg-WI.js +89 -0
  103. package/dist/lime-elements/p-CSC2LJ7P.js +1 -0
  104. package/dist/lime-elements/p-a1de9bbe.entry.js +1 -0
  105. package/dist/lime-elements/{p-b5484494.entry.js → p-acba5700.entry.js} +1 -1
  106. package/dist/lime-elements/{p-f6361b03.entry.js → p-b8c76487.entry.js} +1 -1
  107. package/dist/lime-elements/{p-ac9c5c88.entry.js → p-cafec335.entry.js} +1 -1
  108. package/dist/lime-elements/{p-48928b4f.entry.js → p-d146feda.entry.js} +1 -1
  109. package/dist/lime-elements/{p-532ea234.entry.js → p-deceda00.entry.js} +1 -1
  110. package/dist/lime-elements/{p-e508771f.entry.js → p-f04f7c8e.entry.js} +1 -1
  111. package/dist/lime-elements/{p-51dc5f0b.entry.js → p-f3c108c9.entry.js} +1 -1
  112. package/dist/lime-elements/{p-24041bbb.entry.js → p-fbcd99a3.entry.js} +1 -1
  113. package/dist/types/components/menu/menu.d.ts +11 -0
  114. package/dist/types/components/pagination/jump/pagination-jump.d.ts +102 -0
  115. package/dist/types/components/pagination/pagination.d.ts +81 -0
  116. package/dist/types/components/pagination/pagination.util.d.ts +12 -2
  117. package/dist/types/components/popover/popover.d.ts +8 -1
  118. package/dist/types/components.d.ts +145 -0
  119. package/dist/types/translations/da.d.ts +4 -0
  120. package/dist/types/translations/de.d.ts +4 -0
  121. package/dist/types/translations/en.d.ts +4 -0
  122. package/dist/types/translations/fi.d.ts +4 -0
  123. package/dist/types/translations/fr.d.ts +4 -0
  124. package/dist/types/translations/nl.d.ts +4 -0
  125. package/dist/types/translations/no.d.ts +4 -0
  126. package/dist/types/translations/sv.d.ts +4 -0
  127. package/package.json +1 -1
  128. package/dist/cjs/limel-helper-line.cjs.entry.js +0 -42
  129. package/dist/esm/limel-helper-line.entry.js +0 -40
  130. package/dist/lime-elements/p-1a478498.entry.js +0 -248
  131. package/dist/lime-elements/p-4c65dee7.entry.js +0 -1
  132. package/dist/lime-elements/p-DAFuhe5e.js +0 -1
  133. package/dist/lime-elements/p-b4988a9b.entry.js +0 -1
  134. package/dist/lime-elements/p-f81dc23a.entry.js +0 -1
@@ -15,6 +15,10 @@ const FALLBACK_LANGUAGE = 'en';
15
15
  * looking at, and how far into it they have got; and hovering a page shows
16
16
  * exactly which items it holds.
17
17
  *
18
+ * Where there are more pages than fit, the ones left out are stood for by a
19
+ * `···`. That is a button: it opens a field for going straight to any page in
20
+ * the set, so no page is more than one move away however long the set is.
21
+ *
18
22
  * @exampleComponent limel-example-pagination-basic
19
23
  * @exampleComponent limel-example-pagination-single-page
20
24
  * @exampleComponent limel-example-pagination-page-size
@@ -63,12 +67,15 @@ export class Pagination {
63
67
  this.idPrefix = createRandomString();
64
68
  /** The value each prop was last complained about with. */
65
69
  this.warnedAbout = new Map();
70
+ /** The language `numberFormat` settled on, which may not be the prop. */
71
+ this.settledLanguage = FALLBACK_LANGUAGE;
66
72
  /** Settled once per render, and the only thing `render` reads. */
67
73
  this.view = {
68
74
  page: FIRST_PAGE,
69
75
  pageCount: null,
70
76
  pageSize: DEFAULT_PAGE_SIZE,
71
77
  totalItems: null,
78
+ slots: [],
72
79
  };
73
80
  /**
74
81
  * A gap marker, or a page button.
@@ -79,11 +86,38 @@ export class Pagination {
79
86
  */
80
87
  this.renderSlot = (slot, index) => {
81
88
  if (slot.kind === 'gap') {
82
- return (h("span", { key: `gap-${index}`, class: "gap", "aria-hidden": "true" }, "\u00B7\u00B7\u00B7"));
89
+ return this.renderGap(index);
83
90
  }
84
91
  const isCurrent = slot.page === this.currentPage;
85
92
  return (h("button", { key: `page-${index}`, id: this.slotId(index), class: "page", "aria-current": isCurrent ? 'page' : null, "aria-disabled": this.loading ? 'true' : null, "aria-label": this.pageLabel(slot.page, isCurrent), "data-page": slot.page, onClick: this.selectPage }, this.formatNumber(slot.page), this.renderSpinner(isCurrent), this.renderTooltip(slot.page, index)));
86
93
  };
94
+ this.openJump = (event) => {
95
+ if (this.loading) {
96
+ return;
97
+ }
98
+ const { index } = event.currentTarget.dataset;
99
+ this.openGap = Number(index);
100
+ };
101
+ this.closeJump = () => {
102
+ this.openGap = undefined;
103
+ };
104
+ /**
105
+ * The control is told what to show, so a jump only asks. Focus goes back
106
+ * to the marker the field came out of, which is still on screen and will
107
+ * still be somewhere sensible if the consumer declines.
108
+ *
109
+ * @param event - the page the field asks for
110
+ */
111
+ this.handleJump = (event) => {
112
+ event.stopPropagation();
113
+ const page = event.detail;
114
+ this.focusGap(this.openGap);
115
+ this.openGap = undefined;
116
+ if (this.goTo(page)) {
117
+ this.awaitedJump = page;
118
+ this.jumpedFrom = this.view.page;
119
+ }
120
+ };
87
121
  // Bound once: a handler built per render is a new identity each time, so
88
122
  // every button would have its listener swapped on every page change.
89
123
  this.step = (event) => {
@@ -95,9 +129,6 @@ export class Pagination {
95
129
  this.goTo(this.currentPage + (direction === 'next' ? 1 : -1));
96
130
  };
97
131
  this.selectPage = (event) => {
98
- if (this.loading) {
99
- return;
100
- }
101
132
  const { page } = event.currentTarget.dataset;
102
133
  this.goTo(Number(page));
103
134
  };
@@ -127,7 +158,9 @@ export class Pagination {
127
158
  pageCount: pageCount,
128
159
  pageSize: pageSize,
129
160
  totalItems: totalItems,
161
+ slots: this.slotsFor(page, pageCount),
130
162
  };
163
+ this.closeJumpWithoutAGap();
131
164
  this.readOut(page);
132
165
  this.reconcile(page, usable);
133
166
  this.shownPage = page;
@@ -144,6 +177,10 @@ export class Pagination {
144
177
  return Math.max(FIRST_PAGE, Math.ceil(total / pageSize));
145
178
  }
146
179
  componentDidRender() {
180
+ this.reportCorrection();
181
+ this.followJumpedPage();
182
+ }
183
+ reportCorrection() {
147
184
  const page = this.pendingCorrection;
148
185
  if (page === undefined) {
149
186
  return;
@@ -250,10 +287,23 @@ export class Pagination {
250
287
  handleLanguageChange() {
251
288
  this.numberFormat = undefined;
252
289
  }
290
+ /**
291
+ * A gap is remembered by the position it sits in, and the positions are
292
+ * worked out afresh every render. A page set from outside can put a page
293
+ * number where the open gap was, taking the popover down without it ever
294
+ * closing — and leaving it to spring open by itself the next time a gap
295
+ * lands there.
296
+ */
297
+ closeJumpWithoutAGap() {
298
+ var _a;
299
+ const open = this.openGap;
300
+ if (open !== undefined && ((_a = this.view.slots[open]) === null || _a === void 0 ? void 0 : _a.kind) !== 'gap') {
301
+ this.openGap = undefined;
302
+ }
303
+ }
253
304
  render() {
254
- const { page, pageCount } = this.view;
255
- const slots = this.slotsFor(page, pageCount);
256
- return (h(Host, { key: '40d6123ac887f519fc31690fce7fcb46e73f2199' }, h("nav", { key: '183d264e761d168c74b8e2bfa8d04f3ee391c937', "aria-label": this.translate('pagination.label'), "aria-busy": this.loading ? 'true' : null }, this.renderArrow('previous'), slots.map((slot, index) => this.renderSlot(slot, index)), this.renderArrow('next')), h("span", { key: 'e59f62c2644a9a4a513ab3a3455854084434aabc', class: "live-region", "aria-live": "polite" }, this.liveMessage)));
305
+ const { slots } = this.view;
306
+ return (h(Host, { key: 'fac46260ef2a4e1e8d4aad962c90477831b6497f' }, h("nav", { key: '239f0ba745b9a385064c4a2c91a572e70a9e6525', "aria-label": this.translate('pagination.label'), "aria-busy": this.loading ? 'true' : null }, this.renderArrow('previous'), slots.map((slot, index) => this.renderSlot(slot, index)), this.renderArrow('next')), h("span", { key: '82fa3c9fb3fa8063be64d8373158d3d0e93138c1', class: "live-region", "aria-live": "polite" }, this.liveMessage)));
257
307
  }
258
308
  /**
259
309
  * Without a count we know where the user is but not how far the set goes,
@@ -270,6 +320,92 @@ export class Pagination {
270
320
  }
271
321
  return getPageSlots(page, pageCount);
272
322
  }
323
+ /**
324
+ * A gap is a button, not a marker: the pages it stands for are still
325
+ * reachable, and typing one is the only way to reach any of them in a
326
+ * single move.
327
+ *
328
+ * Both gaps open the same field, and it accepts any page in the set.
329
+ * Scoping each one to the pages it happens to hide would make two
330
+ * identical markers behave differently, with nothing on screen to say
331
+ * which is which.
332
+ *
333
+ * @param index - where the gap sits in the window
334
+ * @returns the rendered gap
335
+ */
336
+ renderGap(index) {
337
+ const { pageCount } = this.view;
338
+ // A gap stands for pages that were left out, which cannot have
339
+ // happened before the count arrived: until then `slotsFor` renders
340
+ // the page the user is on and nothing else.
341
+ if (pageCount === null) {
342
+ return;
343
+ }
344
+ return (h("limel-popover", { key: `gap-${index}`, open: this.openGap === index, openDirection: "bottom", onClose: this.closeJump }, h("button", { slot: "trigger", class: "gap", id: this.slotId(index), "aria-label": this.translate('pagination.jump-to-a-page'), "aria-disabled": this.loading ? 'true' : null, "data-index": index, onClick: this.openJump }, "\u00B7\u00B7\u00B7"), h("limel-pagination-jump", { open: this.openGap === index, page: this.view.page, pageCount: pageCount, loading: this.loading, language: this.languageForNumbers, onJump: this.handleJump })));
345
+ }
346
+ focusGap(index) {
347
+ var _a;
348
+ const gap = (_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`[id="${this.slotId(index)}"]`);
349
+ gap === null || gap === void 0 ? void 0 : gap.focus();
350
+ this.jumpFocus = gap;
351
+ }
352
+ /**
353
+ * Put the keyboard on the page a jump asked for, once the consumer has
354
+ * shown it.
355
+ *
356
+ * Committing tears down the popover the field was in, so between asking
357
+ * and arriving focus is on nothing. If the user has since put it somewhere
358
+ * of their own, arriving is no longer ours to report.
359
+ */
360
+ followJumpedPage() {
361
+ if (this.awaitedJump === undefined) {
362
+ return;
363
+ }
364
+ if (!this.ownsFocus()) {
365
+ this.forgetJump();
366
+ return;
367
+ }
368
+ if (this.view.page !== this.awaitedJump) {
369
+ // Still showing the page the jump was asked from is what waiting
370
+ // looks like. Anywhere else is an answer too — the consumer chose
371
+ // to show something else — and a later arrival at the page we
372
+ // asked for is then no longer ours to follow.
373
+ if (this.view.page !== this.jumpedFrom) {
374
+ this.forgetJump();
375
+ }
376
+ return;
377
+ }
378
+ this.forgetJump();
379
+ this.focusCurrentPage();
380
+ }
381
+ forgetJump() {
382
+ this.awaitedJump = undefined;
383
+ this.jumpedFrom = undefined;
384
+ }
385
+ /**
386
+ * Whether the keyboard is still where the jump put it. Somewhere else
387
+ * inside this component means the user moved it and it is theirs.
388
+ *
389
+ * The marker itself can also go: jumping from the left gap to page 3
390
+ * turns that slot into a page number, and the button holding focus is
391
+ * taken away with it. A browser answers that by focusing the body rather
392
+ * than nothing, so the marker having left the page is what we look for.
393
+ */
394
+ ownsFocus() {
395
+ var _a, _b;
396
+ const inside = (_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement;
397
+ if (inside === this.jumpFocus) {
398
+ return true;
399
+ }
400
+ return (!((_b = this.jumpFocus) === null || _b === void 0 ? void 0 : _b.isConnected) &&
401
+ document.activeElement === document.body);
402
+ }
403
+ focusCurrentPage() {
404
+ var _a;
405
+ const button = (_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('[aria-current="page"]');
406
+ button === null || button === void 0 ? void 0 : button.focus();
407
+ this.jumpFocus = undefined;
408
+ }
273
409
  /**
274
410
  * A spinner around the page the user is heading for, while it loads.
275
411
  *
@@ -332,13 +468,19 @@ export class Pagination {
332
468
  return pageCount === null || page >= pageCount;
333
469
  }
334
470
  /**
471
+ * The one path every move the user asks for goes through, and so the one
472
+ * place that refuses while a page is still being fetched.
473
+ *
335
474
  * @param page - the page to ask for, 1-based
475
+ * @returns whether it was asked for
336
476
  */
337
477
  goTo(page) {
338
- if (page === this.currentPage) {
339
- return;
478
+ this.awaitedJump = undefined;
479
+ if (this.loading || page === this.currentPage) {
480
+ return false;
340
481
  }
341
482
  this.emitGoToPage(page, 'user');
483
+ return true;
342
484
  }
343
485
  /** The page on screen: the one asked for, within the range that exists. */
344
486
  get currentPage() {
@@ -474,6 +616,17 @@ export class Pagination {
474
616
  (_a = this.numberFormat) !== null && _a !== void 0 ? _a : (this.numberFormat = this.createNumberFormat());
475
617
  return this.numberFormat.format(value);
476
618
  }
619
+ /**
620
+ * What the jump form is given, so that the one warning about a language
621
+ * this browser cannot write numbers in describes both of us. A tag it
622
+ * accepts but does not support, `zz`, resolves to the viewer's own locale
623
+ * rather than throwing, so asking the formatter is the only way to know.
624
+ */
625
+ get languageForNumbers() {
626
+ var _a;
627
+ (_a = this.numberFormat) !== null && _a !== void 0 ? _a : (this.numberFormat = this.createNumberFormat());
628
+ return this.settledLanguage;
629
+ }
477
630
  /**
478
631
  * `Intl.NumberFormat` throws on a language it cannot parse, and this is
479
632
  * called from `render`, so an unusable one would leave the control drawing
@@ -494,6 +647,7 @@ export class Pagination {
494
647
  const resolved = format.resolvedOptions().locale;
495
648
  if (getPrimarySubtag(resolved) === getPrimarySubtag(language)) {
496
649
  this.warnedAbout.delete('language');
650
+ this.settledLanguage = language;
497
651
  return format;
498
652
  }
499
653
  }
@@ -504,6 +658,7 @@ export class Pagination {
504
658
  // Where the translations land for the same unusable value, rather than
505
659
  // the viewer's own locale — English labels beside system digit
506
660
  // grouping would read differently on every machine.
661
+ this.settledLanguage = FALLBACK_LANGUAGE;
507
662
  return new Intl.NumberFormat(FALLBACK_LANGUAGE);
508
663
  }
509
664
  static get is() { return "limel-pagination"; }
@@ -631,7 +786,8 @@ export class Pagination {
631
786
  }
632
787
  static get states() {
633
788
  return {
634
- "liveMessage": {}
789
+ "liveMessage": {},
790
+ "openGap": {}
635
791
  };
636
792
  }
637
793
  static get events() {
@@ -659,6 +815,7 @@ export class Pagination {
659
815
  }
660
816
  }];
661
817
  }
818
+ static get elementRef() { return "host"; }
662
819
  static get watchers() {
663
820
  return [{
664
821
  "propName": "language",
@@ -8,6 +8,30 @@ export const VISIBLE_SLOTS = 7;
8
8
  /** How many pages to keep either side of the current one. */
9
9
  const SIBLINGS = 1;
10
10
  const FIRST_PAGE = 1;
11
+ /**
12
+ * The page a typed value asks for, or `null` when it asks for nothing.
13
+ *
14
+ * Out of range is not an error. The set has a first and a last page, both
15
+ * written under the field, and the nearer of them is what someone typing past
16
+ * the end meant.
17
+ *
18
+ * @param value - what was typed into the field
19
+ * @param pageCount - how many pages there are, or `null` when not yet known
20
+ * @returns a page within the set, or `null` when there is no number to use
21
+ */
22
+ export function pageFromInput(value, pageCount) {
23
+ const typed = Number.parseInt(value, 10);
24
+ if (!Number.isSafeInteger(typed)) {
25
+ return null;
26
+ }
27
+ const atLeastFirst = Math.max(typed, FIRST_PAGE);
28
+ // Each bound on its own. Folding them into one `Math.min` against
29
+ // `pageCount ?? typed` handed the typed value back as its own upper
30
+ // bound, so without a count `0` stayed `0` rather than becoming page 1.
31
+ return pageCount === null
32
+ ? atLeastFirst
33
+ : Math.min(atLeastFirst, pageCount);
34
+ }
11
35
  /**
12
36
  * Build the list of positions to render for a given page count.
13
37
  *
@@ -101,13 +125,6 @@ function withGaps(pages) {
101
125
  if (skipped === 1) {
102
126
  return [{ kind: 'page', page: page - 1 }, slot];
103
127
  }
104
- return [
105
- {
106
- kind: 'gap',
107
- from: pages[index - 1] + 1,
108
- to: page - 1,
109
- },
110
- slot,
111
- ];
128
+ return [{ kind: 'gap' }, slot];
112
129
  });
113
130
  }
@@ -77,20 +77,12 @@ export class Popover {
77
77
  focusTriggerElement(trigger);
78
78
  };
79
79
  this.setTriggerAttributes = (element) => {
80
- const attributes = {
81
- 'aria-haspopup': true,
82
- 'aria-expanded': this.open,
83
- 'aria-controls': this.portalId,
84
- role: 'button',
85
- };
86
- for (const [key, value] of Object.entries(attributes)) {
87
- if (value) {
88
- element.setAttribute(key, String(value));
89
- }
90
- else {
91
- element.removeAttribute(key);
92
- }
93
- }
80
+ element.setAttribute('aria-haspopup', 'true');
81
+ // `aria-expanded` is needed even when the popover is closed. Without
82
+ // it, the trigger reads as an ordinary button to a screen reader.
83
+ element.setAttribute('aria-expanded', String(this.open));
84
+ element.setAttribute('aria-controls', this.portalId);
85
+ element.setAttribute('role', 'button');
94
86
  };
95
87
  this.portalId = createRandomString();
96
88
  this.globalClickListener = this.globalClickListener.bind(this);
@@ -102,9 +94,12 @@ export class Popover {
102
94
  setTimeout(this.focusTrigger, 0);
103
95
  }
104
96
  }
105
- componentWillLoad() {
97
+ connectedCallback() {
106
98
  this.setupGlobalHandlers();
107
99
  }
100
+ disconnectedCallback() {
101
+ this.teardownGlobalHandlers();
102
+ }
108
103
  componentDidRender() {
109
104
  if (!this.triggerSlot) {
110
105
  return;
@@ -113,21 +108,34 @@ export class Popover {
113
108
  this.triggerSlot.assignedElements().forEach(this.setTriggerAttributes);
114
109
  }
115
110
  setupGlobalHandlers() {
116
- if (this.open) {
117
- document.addEventListener('click', this.globalClickListener, {
118
- capture: true,
119
- });
120
- document.addEventListener('keydown', this.handleGlobalKeyPress);
121
- }
122
- else {
123
- document.removeEventListener('click', this.globalClickListener);
124
- document.removeEventListener('keydown', this.handleGlobalKeyPress);
111
+ // Stencil keeps firing `@Watch` on a detached instance, so `open` can
112
+ // still flip after `disconnectedCallback` has run. Listening then
113
+ // would swallow every click on the page, with no disconnect left to
114
+ // undo it.
115
+ if (!this.open || !this.host.isConnected) {
116
+ this.teardownGlobalHandlers();
117
+ return;
125
118
  }
119
+ document.addEventListener('click', this.globalClickListener, {
120
+ capture: true,
121
+ });
122
+ document.addEventListener('keydown', this.handleGlobalKeyPress);
123
+ }
124
+ /**
125
+ * The capture flag has to be given again: `removeEventListener` only
126
+ * matches a listener registered with the same one, so leaving it out here
127
+ * left the click listener on `document` for good.
128
+ */
129
+ teardownGlobalHandlers() {
130
+ document.removeEventListener('click', this.globalClickListener, {
131
+ capture: true,
132
+ });
133
+ document.removeEventListener('keydown', this.handleGlobalKeyPress);
126
134
  }
127
135
  render() {
128
136
  const cssProperties = this.getCssProperties();
129
137
  const popoverZIndex = getComputedStyle(this.host).getPropertyValue('--popover-z-index');
130
- return (h("div", { key: '096e54a6fdc88f6571884bf0a642c67f144d3bbf', class: "trigger-anchor" }, h("slot", { key: 'ddc46b8755f6463dc2320198aefc50ef759f9de4', name: "trigger", ref: this.setTriggerRef }), h("limel-portal", { key: '89cce45b2218a2a5087f30f4d524d2c1b2041de2', visible: this.open, containerId: this.portalId, containerStyle: { 'z-index': popoverZIndex }, openDirection: this.openDirection }, h("limel-popover-surface", { key: 'a1d1edfb24fdbef9109adef03f8d77e6cced87c7', contentCollection: this.host.children, style: cssProperties }))));
138
+ return (h("div", { key: 'd34ccb8ce24c315432db83e6e1d998c4d230255f', class: "trigger-anchor" }, h("slot", { key: 'f2ab0bc064a439cecfee0fc75cb160cde227e018', name: "trigger", ref: this.setTriggerRef }), h("limel-portal", { key: '047d3643c80c445c8b97f1c30a6b7a8c236beaa9', visible: this.open, containerId: this.portalId, containerStyle: { 'z-index': popoverZIndex }, openDirection: this.openDirection }, h("limel-popover-surface", { key: '91c6f19807aeda2d37652bce452f6bd3cd6bcc9f', contentCollection: this.host.children, style: cssProperties }))));
131
139
  }
132
140
  globalClickListener(event) {
133
141
  const element = event.target;
@@ -127,4 +127,8 @@ Du kan fortsætte med at blokere billeder (e-mailen kan se ufuldstændig ud) ell
127
127
  'pagination.go-to-page': 'Gå til side { page }',
128
128
  'pagination.current-page': 'Side { page }, nuværende side',
129
129
  'pagination.items-range': '{ from }–{ to } (af { total })',
130
+ 'pagination.jump-to-a-page': 'Hop til en side',
131
+ 'pagination.page-number': 'Sidenummer',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Gå',
130
134
  };
@@ -127,4 +127,8 @@ Sie können Bilder weiterhin blockieren (die E-Mail kann unvollständig aussehen
127
127
  'pagination.go-to-page': 'Zu Seite { page } wechseln',
128
128
  'pagination.current-page': 'Seite { page }, aktuelle Seite',
129
129
  'pagination.items-range': '{ from }–{ to } (von { total })',
130
+ 'pagination.jump-to-a-page': 'Zu einer Seite springen',
131
+ 'pagination.page-number': 'Seitenzahl',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Los',
130
134
  };
@@ -127,4 +127,8 @@ You can keep images blocked (the email may look incomplete), or load them if you
127
127
  'pagination.go-to-page': 'Go to page { page }',
128
128
  'pagination.current-page': 'Page { page }, current page',
129
129
  'pagination.items-range': '{ from }–{ to } (of { total })',
130
+ 'pagination.jump-to-a-page': 'Jump to a page',
131
+ 'pagination.page-number': 'Page number',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Go',
130
134
  };
@@ -127,4 +127,8 @@ Voit pitää kuvat estettyinä (sähköposti voi näyttää puutteelliselta) tai
127
127
  'pagination.go-to-page': 'Siirry sivulle { page }',
128
128
  'pagination.current-page': 'Sivu { page }, nykyinen sivu',
129
129
  'pagination.items-range': '{ from }–{ to } (yhteensä { total })',
130
+ 'pagination.jump-to-a-page': 'Siirry tietylle sivulle',
131
+ 'pagination.page-number': 'Sivunumero',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Siirry',
130
134
  };
@@ -127,4 +127,8 @@ Vous pouvez laisser les images bloquées (l'e-mail peut sembler incomplet) ou le
127
127
  'pagination.go-to-page': 'Aller à la page { page }',
128
128
  'pagination.current-page': 'Page { page }, page actuelle',
129
129
  'pagination.items-range': '{ from }–{ to } (sur { total })',
130
+ 'pagination.jump-to-a-page': 'Aller à une page',
131
+ 'pagination.page-number': 'Numéro de page',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Aller',
130
134
  };
@@ -127,4 +127,8 @@ Je kunt afbeeldingen geblokkeerd houden (de e-mail kan er onvolledig uitzien) of
127
127
  'pagination.go-to-page': 'Ga naar pagina { page }',
128
128
  'pagination.current-page': 'Pagina { page }, huidige pagina',
129
129
  'pagination.items-range': '{ from }–{ to } (van { total })',
130
+ 'pagination.jump-to-a-page': 'Naar een pagina springen',
131
+ 'pagination.page-number': 'Paginanummer',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Ga',
130
134
  };
@@ -127,4 +127,8 @@ Du kan fortsette å blokkere bilder (e-posten kan se ufullstendig ut), eller las
127
127
  'pagination.go-to-page': 'Gå til side { page }',
128
128
  'pagination.current-page': 'Side { page }, gjeldende side',
129
129
  'pagination.items-range': '{ from }–{ to } (av { total })',
130
+ 'pagination.jump-to-a-page': 'Hopp til en side',
131
+ 'pagination.page-number': 'Sidenummer',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Gå',
130
134
  };
@@ -127,4 +127,8 @@ Du kan fortsätta blockera bilder (e-posten kan se ofullständig ut) eller ladda
127
127
  'pagination.go-to-page': 'Gå till sida { page }',
128
128
  'pagination.current-page': 'Sida { page }, aktuell sida',
129
129
  'pagination.items-range': '{ from }–{ to } (av { total })',
130
+ 'pagination.jump-to-a-page': 'Hoppa till en sida',
131
+ 'pagination.page-number': 'Sidnummer',
132
+ 'pagination.page-range': '{ first }–{ last }',
133
+ 'pagination.go': 'Gå',
130
134
  };