@limetech/lime-elements 39.45.3 → 40.0.1

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 (100) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/cjs/lime-elements.cjs.js +1 -1
  3. package/dist/cjs/limel-action-bar_3.cjs.entry.js +1 -1
  4. package/dist/cjs/limel-ai-avatar.cjs.entry.js +1 -1
  5. package/dist/cjs/limel-callout.cjs.entry.js +1 -1
  6. package/dist/cjs/limel-chart.cjs.entry.js +1 -1
  7. package/dist/cjs/limel-chip-set.cjs.entry.js +1 -1
  8. package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
  9. package/dist/cjs/limel-code-diff.cjs.entry.js +1 -1
  10. package/dist/cjs/limel-code-editor.cjs.entry.js +1 -1
  11. package/dist/cjs/limel-collapsible-section.cjs.entry.js +1 -1
  12. package/dist/cjs/limel-drag-handle.cjs.entry.js +1 -1
  13. package/dist/cjs/limel-email-viewer.cjs.entry.js +1 -1
  14. package/dist/cjs/limel-file-viewer.cjs.entry.js +1 -1
  15. package/dist/cjs/limel-file.cjs.entry.js +1 -1
  16. package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +1 -1
  17. package/dist/cjs/limel-list-item.cjs.entry.js +2 -2
  18. package/dist/cjs/limel-picker.cjs.entry.js +1 -1
  19. package/dist/cjs/limel-profile-picture.cjs.entry.js +1 -1
  20. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +40 -40
  21. package/dist/cjs/limel-slider.cjs.entry.js +140 -16
  22. package/dist/cjs/limel-snackbar.cjs.entry.js +1 -1
  23. package/dist/cjs/limel-table.cjs.entry.js +1 -1
  24. package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +1 -1
  25. package/dist/cjs/loader.cjs.js +1 -1
  26. package/dist/cjs/{translations-Bnw00bkf.js → translations-Do_D3pxu.js} +39 -2
  27. package/dist/collection/components/list-item/list-item.css +18 -2
  28. package/dist/collection/components/slider/slider.css +95 -0
  29. package/dist/collection/components/slider/slider.js +174 -21
  30. package/dist/collection/global/translations.js +15 -2
  31. package/dist/collection/translations/da.js +3 -0
  32. package/dist/collection/translations/de.js +3 -0
  33. package/dist/collection/translations/en.js +3 -0
  34. package/dist/collection/translations/fi.js +3 -0
  35. package/dist/collection/translations/fr.js +3 -0
  36. package/dist/collection/translations/nl.js +3 -0
  37. package/dist/collection/translations/no.js +3 -0
  38. package/dist/collection/translations/sv.js +3 -0
  39. package/dist/esm/lime-elements.js +1 -1
  40. package/dist/esm/limel-action-bar_3.entry.js +1 -1
  41. package/dist/esm/limel-ai-avatar.entry.js +1 -1
  42. package/dist/esm/limel-callout.entry.js +1 -1
  43. package/dist/esm/limel-chart.entry.js +1 -1
  44. package/dist/esm/limel-chip-set.entry.js +1 -1
  45. package/dist/esm/limel-chip_2.entry.js +1 -1
  46. package/dist/esm/limel-code-diff.entry.js +1 -1
  47. package/dist/esm/limel-code-editor.entry.js +1 -1
  48. package/dist/esm/limel-collapsible-section.entry.js +1 -1
  49. package/dist/esm/limel-drag-handle.entry.js +1 -1
  50. package/dist/esm/limel-email-viewer.entry.js +1 -1
  51. package/dist/esm/limel-file-viewer.entry.js +1 -1
  52. package/dist/esm/limel-file.entry.js +1 -1
  53. package/dist/esm/limel-flatpickr-adapter.entry.js +1 -1
  54. package/dist/esm/limel-list-item.entry.js +2 -2
  55. package/dist/esm/limel-picker.entry.js +1 -1
  56. package/dist/esm/limel-profile-picture.entry.js +1 -1
  57. package/dist/esm/limel-prosemirror-adapter.entry.js +40 -40
  58. package/dist/esm/limel-slider.entry.js +140 -16
  59. package/dist/esm/limel-snackbar.entry.js +1 -1
  60. package/dist/esm/limel-table.entry.js +1 -1
  61. package/dist/esm/limel-text-editor-link-menu.entry.js +1 -1
  62. package/dist/esm/loader.js +1 -1
  63. package/dist/esm/{translations-Cb9R_zmF.js → translations-DgqE77nk.js} +39 -2
  64. package/dist/lime-elements/lime-elements.esm.js +1 -1
  65. package/dist/lime-elements/{p-84526b93.entry.js → p-3e316685.entry.js} +1 -1
  66. package/dist/lime-elements/{p-c8116a01.entry.js → p-565f688a.entry.js} +1 -1
  67. package/dist/lime-elements/{p-aa080a8f.entry.js → p-5dde5d72.entry.js} +1 -1
  68. package/dist/lime-elements/{p-31713caf.entry.js → p-5e432a44.entry.js} +1 -1
  69. package/dist/lime-elements/{p-4a09fb2e.entry.js → p-62a25deb.entry.js} +1 -1
  70. package/dist/lime-elements/{p-8aa4a38e.entry.js → p-72a1d432.entry.js} +1 -1
  71. package/dist/lime-elements/{p-9a04a686.entry.js → p-83bdbaf7.entry.js} +1 -1
  72. package/dist/lime-elements/{p-2f223e46.entry.js → p-8b3b7505.entry.js} +1 -1
  73. package/dist/lime-elements/{p-ca0b86ee.entry.js → p-8c3798ee.entry.js} +1 -1
  74. package/dist/lime-elements/{p-8aeeeb53.entry.js → p-8cf79fa1.entry.js} +1 -1
  75. package/dist/lime-elements/{p-7487d65d.entry.js → p-9ed9d0de.entry.js} +1 -1
  76. package/dist/lime-elements/p-DgqE77nk.js +1 -0
  77. package/dist/lime-elements/{p-2284caa7.entry.js → p-a6500046.entry.js} +1 -1
  78. package/dist/lime-elements/{p-e545c9e8.entry.js → p-b04c6602.entry.js} +1 -1
  79. package/dist/lime-elements/{p-d97df2cd.entry.js → p-b4b43404.entry.js} +1 -1
  80. package/dist/lime-elements/p-b6528083.entry.js +1 -0
  81. package/dist/lime-elements/{p-6722e6e2.entry.js → p-cafebc7d.entry.js} +1 -1
  82. package/dist/lime-elements/{p-2e5d5e79.entry.js → p-dd116bb1.entry.js} +1 -1
  83. package/dist/lime-elements/{p-d7bb4310.entry.js → p-dda63b8b.entry.js} +1 -1
  84. package/dist/lime-elements/{p-7f7e2180.entry.js → p-e8783848.entry.js} +1 -1
  85. package/dist/lime-elements/{p-19c81ded.entry.js → p-eeddce38.entry.js} +1 -1
  86. package/dist/lime-elements/{p-9ae7d11b.entry.js → p-f53d8bda.entry.js} +1 -1
  87. package/dist/lime-elements/{p-03fd2181.entry.js → p-fa4edcf5.entry.js} +1 -1
  88. package/dist/types/components/slider/slider.d.ts +67 -4
  89. package/dist/types/components.d.ts +25 -10
  90. package/dist/types/translations/da.d.ts +3 -0
  91. package/dist/types/translations/de.d.ts +3 -0
  92. package/dist/types/translations/en.d.ts +3 -0
  93. package/dist/types/translations/fi.d.ts +3 -0
  94. package/dist/types/translations/fr.d.ts +3 -0
  95. package/dist/types/translations/nl.d.ts +3 -0
  96. package/dist/types/translations/no.d.ts +3 -0
  97. package/dist/types/translations/sv.d.ts +3 -0
  98. package/package.json +8 -8
  99. package/dist/lime-elements/p-Cb9R_zmF.js +0 -1
  100. package/dist/lime-elements/p-ee58f0b4.entry.js +0 -1
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var da = {
4
+ 'clear-value': 'Ryd værdi',
5
+ 'clear-value-of': 'Ryd værdien for { label }',
6
+ 'value-not-set': 'Værdi ikke angivet',
4
7
  remove: 'Fjern',
5
8
  save: 'Gem',
6
9
  cancel: 'Annullér',
@@ -119,6 +122,9 @@ Du kan fortsætte med at blokere billeder (e-mailen kan se ufuldstændig ud) ell
119
122
  };
120
123
 
121
124
  var de = {
125
+ 'clear-value': 'Wert löschen',
126
+ 'clear-value-of': 'Wert von { label } löschen',
127
+ 'value-not-set': 'Wert nicht festgelegt',
122
128
  remove: 'Entfernen',
123
129
  save: 'Speichern',
124
130
  cancel: 'Abbrechen',
@@ -237,6 +243,9 @@ Sie können Bilder weiterhin blockieren (die E-Mail kann unvollständig aussehen
237
243
  };
238
244
 
239
245
  var en = {
246
+ 'clear-value': 'Clear value',
247
+ 'clear-value-of': 'Clear value of { label }',
248
+ 'value-not-set': 'Value not set',
240
249
  remove: 'Remove',
241
250
  save: 'Save',
242
251
  cancel: 'Cancel',
@@ -355,6 +364,9 @@ You can keep images blocked (the email may look incomplete), or load them if you
355
364
  };
356
365
 
357
366
  var fi = {
367
+ 'clear-value': 'Tyhjennä arvo',
368
+ 'clear-value-of': 'Tyhjennä kentän { label } arvo',
369
+ 'value-not-set': 'Arvoa ei asetettu',
358
370
  remove: 'Poista',
359
371
  save: 'Tallenna',
360
372
  cancel: 'Peruuta',
@@ -473,6 +485,9 @@ Voit pitää kuvat estettyinä (sähköposti voi näyttää puutteelliselta) tai
473
485
  };
474
486
 
475
487
  var fr = {
488
+ 'clear-value': 'Effacer la valeur',
489
+ 'clear-value-of': 'Effacer la valeur de { label }',
490
+ 'value-not-set': 'Valeur non définie',
476
491
  remove: 'Supprimer',
477
492
  save: 'Enregistrer',
478
493
  cancel: 'Annuler',
@@ -591,6 +606,9 @@ Vous pouvez laisser les images bloquées (l'e-mail peut sembler incomplet) ou le
591
606
  };
592
607
 
593
608
  var nl = {
609
+ 'clear-value': 'Waarde wissen',
610
+ 'clear-value-of': 'Waarde van { label } wissen',
611
+ 'value-not-set': 'Waarde niet ingesteld',
594
612
  remove: 'Verwijder',
595
613
  save: 'Opslaan',
596
614
  cancel: 'Annuleren',
@@ -709,6 +727,9 @@ Je kunt afbeeldingen geblokkeerd houden (de e-mail kan er onvolledig uitzien) of
709
727
  };
710
728
 
711
729
  var no = {
730
+ 'clear-value': 'Tøm verdi',
731
+ 'clear-value-of': 'Tøm verdien for { label }',
732
+ 'value-not-set': 'Verdi ikke angitt',
712
733
  remove: 'Fjerne',
713
734
  save: 'Lagre',
714
735
  cancel: 'Avbryt',
@@ -827,6 +848,9 @@ Du kan fortsette å blokkere bilder (e-posten kan se ufullstendig ut), eller las
827
848
  };
828
849
 
829
850
  var sv = {
851
+ 'clear-value': 'Rensa värde',
852
+ 'clear-value-of': 'Rensa värdet för { label }',
853
+ 'value-not-set': 'Värde inte angivet',
830
854
  remove: 'Ta bort',
831
855
  save: 'Spara',
832
856
  cancel: 'Avbryt',
@@ -951,18 +975,31 @@ const allTranslations = {
951
975
  fi: fi,
952
976
  fr: fr,
953
977
  no: no,
978
+ nb: no, // Norwegian Bokmål shares the Norwegian (`no`) translations
954
979
  nl: nl,
955
980
  sv: sv,
956
981
  };
957
982
  const REGEX = /\{\s*(\w+)\s*\}/g;
958
983
  class Translations {
959
984
  get(key, language = 'en', params) {
960
- const translation = allTranslations[language][key];
985
+ var _a, _b;
986
+ // Fall back to English when the requested language has no translations.
987
+ // The `language` props are typed, but a custom element takes whatever
988
+ // string an attribute carries, so an unknown language must never make a
989
+ // component throw.
990
+ const languageTranslations = (_a = allTranslations[language]) !== null && _a !== void 0 ? _a : allTranslations.en;
991
+ // Fall back per key as well: a mapped language whose file is missing
992
+ // this one key would otherwise render the key itself as UI text.
993
+ const translation = (_b = languageTranslations[key]) !== null && _b !== void 0 ? _b : allTranslations.en[key];
961
994
  if (!translation) {
962
995
  return key;
963
996
  }
964
997
  return translation.replaceAll(REGEX, (match, mergeCodeKey) => {
965
- return params[mergeCodeKey] || match;
998
+ var _a;
999
+ // Nullish, not falsy: `0` and `false` are values a caller can
1000
+ // legitimately merge in, and `||` would leave the merge code
1001
+ // itself in the string instead.
1002
+ return String((_a = params === null || params === void 0 ? void 0 : params[mergeCodeKey]) !== null && _a !== void 0 ? _a : match);
966
1003
  });
967
1004
  }
968
1005
  }
@@ -5,6 +5,7 @@
5
5
  * @prop --limel-list-item-menu-order: Defines the order of the menu, within the list item's flexbox. Defaults to `3`.
6
6
  * @prop --list-item-image-border-radius: Defines the border radius of the list item image. Defaults to `50%`.
7
7
  * @prop --list-item-image-object-fit: Defines the object-fit property of the list item image. Defaults to `cover`.
8
+ * @prop --limel-list-item-image-size: Defines the size of the list item image. Defaults to `2rem`.
8
9
  */
9
10
  /**
10
11
  * Note! This file is forwarded via `src/style/exports.scss`
@@ -110,6 +111,7 @@
110
111
  * @prop --limel-list-item-menu-order: Defines the order of the menu, within the list item's flexbox. Defaults to `3`.
111
112
  * @prop --list-item-image-border-radius: Defines the border radius of the list item image. Defaults to `50%`.
112
113
  * @prop --list-item-image-object-fit: Defines the object-fit property of the list item image. Defaults to `cover`.
114
+ * @prop --limel-list-item-image-size: Defines the size of the list item image. Defaults to `2rem`.
113
115
  */
114
116
  /**
115
117
  * :::important
@@ -145,6 +147,7 @@
145
147
  * @prop --limel-list-item-menu-order: Defines the order of the menu, within the list item's flexbox. Defaults to `3`.
146
148
  * @prop --list-item-image-border-radius: Defines the border radius of the list item image. Defaults to `50%`.
147
149
  * @prop --list-item-image-object-fit: Defines the object-fit property of the list item image. Defaults to `cover`.
150
+ * @prop --limel-list-item-image-size: Defines the size of the list item image. Defaults to `2rem`.
148
151
  */
149
152
  *,
150
153
  *:before,
@@ -409,6 +412,7 @@ limel-dynamic-label {
409
412
  }
410
413
 
411
414
  limel-list-item {
415
+ --limel-list-item-image-size: 2rem;
412
416
  min-height: 2.5rem;
413
417
  }
414
418
  limel-list-item:focus {
@@ -523,8 +527,8 @@ limel-list-item img {
523
527
  flex-shrink: 0;
524
528
  object-fit: var(--list-item-image-object-fit, cover);
525
529
  border-radius: var(--list-item-image-border-radius, 50%);
526
- width: 2rem;
527
- height: 2rem;
530
+ width: var(--limel-list-item-image-size);
531
+ height: var(--limel-list-item-image-size);
528
532
  box-shadow: 0 0 0 1px rgb(var(--contrast-800), 0.5);
529
533
  }
530
534
  limel-list-item limel-menu {
@@ -532,6 +536,18 @@ limel-list-item limel-menu {
532
536
  order: var(--limel-list-item-menu-order, 3);
533
537
  }
534
538
 
539
+ :has(> limel-list-item > img) > limel-list-item:not(:has(> img)) > limel-icon {
540
+ margin-left: 0;
541
+ min-width: var(--limel-list-item-image-size);
542
+ max-width: var(--limel-list-item-image-size);
543
+ min-height: var(--limel-list-item-image-size);
544
+ max-height: var(--limel-list-item-image-size);
545
+ box-shadow: 0 0 0 1px rgb(var(--contrast-800), 0.5);
546
+ }
547
+ :has(> limel-list-item > img) > limel-list-item:not(:has(> img)) > limel-icon:not([badge]) {
548
+ padding: 0.25rem;
549
+ }
550
+
535
551
  limel-list-item[role=menuitem] limel-list-item .label {
536
552
  font-size: var(--limel-theme-default-small-font-size);
537
553
  }
@@ -241,8 +241,10 @@
241
241
  border-radius: 1.25rem;
242
242
  padding: 0 0.375rem;
243
243
  height: 1.25rem;
244
+ min-width: 1.25rem;
244
245
  display: flex;
245
246
  align-items: center;
247
+ justify-content: center;
246
248
  white-space: nowrap;
247
249
  color: rgb(var(--color-white));
248
250
  font-size: 0.6875rem;
@@ -383,6 +385,99 @@ input[type=range] {
383
385
  color: var(--limel-notched-outline-label-color, rgb(var(--contrast-1200)));
384
386
  }
385
387
 
388
+ :host(.has-clear-button) div[slot=content] {
389
+ padding-right: calc(1rem + 0.25rem);
390
+ }
391
+
392
+ :host(.is-unset) .track .active {
393
+ width: 0;
394
+ }
395
+ :host(.is-unset) .thumb {
396
+ left: 50%;
397
+ }
398
+
399
+ button.clear-button {
400
+ appearance: none;
401
+ background: none;
402
+ border: none;
403
+ padding: 0;
404
+ margin: 0;
405
+ font: inherit;
406
+ color: inherit;
407
+ text-align: inherit;
408
+ }
409
+ button.clear-button:focus {
410
+ outline: none;
411
+ }
412
+ button.clear-button:focus-visible {
413
+ outline: none;
414
+ box-shadow: var(--shadow-depth-8-focused);
415
+ }
416
+ button.clear-button:not([disabled]) {
417
+ transition: color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);
418
+ cursor: pointer;
419
+ color: rgb(var(--contrast-1100));
420
+ background-color: rgba(var(--contrast-700), 0.6);
421
+ }
422
+ button.clear-button:not([disabled]):hover, button.clear-button:not([disabled]):focus, button.clear-button:not([disabled]):focus-visible {
423
+ will-change: color, background-color, box-shadow, transform;
424
+ }
425
+ button.clear-button:not([disabled]):hover, button.clear-button:not([disabled]):focus-visible {
426
+ transform: translate3d(0, 0.01rem, 0);
427
+ color: rgb(var(--contrast-100));
428
+ background-color: rgb(var(--contrast-1000));
429
+ }
430
+ button.clear-button:not([disabled]):hover {
431
+ box-shadow: var(--button-shadow-hovered);
432
+ }
433
+ button.clear-button:not([disabled]):active {
434
+ --limel-clickable-transform-timing-function: cubic-bezier(
435
+ 0.83,
436
+ -0.15,
437
+ 0.49,
438
+ 1.16
439
+ );
440
+ transform: translate3d(0, 0.05rem, 0);
441
+ box-shadow: var(--button-shadow-pressed);
442
+ }
443
+ button.clear-button:not([disabled]):hover, button.clear-button:not([disabled]):active {
444
+ --limel-clickable-transition-speed: 0.2s;
445
+ --limel-clickable-transform-speed: 0.16s;
446
+ }
447
+ button.clear-button:not([disabled]):focus-visible {
448
+ background-color: rgb(var(--contrast-1000));
449
+ }
450
+ button.clear-button {
451
+ position: absolute;
452
+ inset: 0.75rem 0 0 auto;
453
+ display: flex;
454
+ align-items: center;
455
+ justify-content: center;
456
+ height: 1rem;
457
+ width: 1rem;
458
+ border-radius: 50%;
459
+ }
460
+ button.clear-button svg {
461
+ width: calc(1rem - 0.25rem);
462
+ height: calc(1rem - 0.25rem);
463
+ fill: currentColor;
464
+ }
465
+ button.clear-button:before {
466
+ content: "";
467
+ position: absolute;
468
+ inset: 0 auto 0 0;
469
+ margin: auto;
470
+ height: 0.125rem;
471
+ width: 0.25rem;
472
+ background-color: rgba(var(--contrast-700), 0.6);
473
+ transform: translate(-100%, 0);
474
+ }
475
+ button.clear-button[disabled] {
476
+ color: rgb(var(--contrast-1000));
477
+ background-color: rgb(var(--contrast-600));
478
+ cursor: not-allowed;
479
+ }
480
+
386
481
  :host(limel-slider:focus),
387
482
  :host(limel-slider:focus-visible),
388
483
  :host(limel-slider:focus-within) {
@@ -1,12 +1,25 @@
1
1
  import { h, Host, } from "@stencil/core";
2
2
  import { getPercentageClass } from "./get-percentage-class";
3
3
  import { createRandomString } from "../../util/random-string";
4
+ import translate from "../../global/translations";
4
5
  const DEFAULT_FACTOR = 1;
5
6
  const DEFAULT_MAX_VALUE = 100;
6
7
  const DEFAULT_MIN_VALUE = 0;
7
8
  const MAX_VISIBLE_STEP_DOTS = 20;
9
+ /**
10
+ * Whether the slider holds a value at all. `Number.isFinite` is typed
11
+ * `(number: unknown) => boolean`, so it tests the right thing but narrows
12
+ * nothing; this does, which lets the unset check double as proof that what
13
+ * remains is a number the arithmetic can use. `null`, `undefined` and `NaN`
14
+ * all mean unset.
15
+ * @param value - the slider's `value` prop, which may not be a number.
16
+ */
17
+ const isSetValue = (value) => {
18
+ return Number.isFinite(value);
19
+ };
8
20
  /**
9
21
  * @exampleComponent limel-example-slider-basic
22
+ * @exampleComponent limel-example-slider-unset
10
23
  * @exampleComponent limel-example-slider-multiplier
11
24
  * @exampleComponent limel-example-slider-multiplier-percentage-colors
12
25
  * @exampleComponent limel-example-slider-unit
@@ -36,6 +49,9 @@ export class Slider {
36
49
  this.factor = DEFAULT_FACTOR;
37
50
  /**
38
51
  * Set to `true` to indicate that the slider is required.
52
+ * A required slider does not offer the clear button, since a required
53
+ * value cannot be unset. It can still be initialized unset (with a
54
+ * non-finite `value`) to prompt the user to make a choice.
39
55
  */
40
56
  this.required = false;
41
57
  /**
@@ -52,6 +68,10 @@ export class Slider {
52
68
  * Unit to display next to the value
53
69
  */
54
70
  this.unit = '';
71
+ /**
72
+ * Defines the language for translations of the accessible labels.
73
+ */
74
+ this.language = 'en';
55
75
  /**
56
76
  * The maximum value allowed
57
77
  */
@@ -60,6 +80,37 @@ export class Slider {
60
80
  * The minimum value allowed
61
81
  */
62
82
  this.valuemin = DEFAULT_MIN_VALUE;
83
+ /**
84
+ * `true` while the value on display is one the consumer has been told
85
+ * about — either it came from the `value` prop, or it has been emitted on
86
+ * `change`. It is `false` from the moment the slider becomes unset until a
87
+ * value is committed again.
88
+ *
89
+ * The native input always holds a value, so while unset it rests at the
90
+ * midpoint. A press that lands on that midpoint, or a drag that returns to
91
+ * it before releasing, leaves the input's value untouched and fires neither
92
+ * `input` nor `change`. Without this flag such an interaction is swallowed:
93
+ * the slider either stays unset, or shows a number the consumer never
94
+ * received.
95
+ */
96
+ this.valueIsCommitted = false;
97
+ this.syncStateFromValue = () => {
98
+ const value = this.value;
99
+ if (!isSetValue(value)) {
100
+ this.enterUnsetState();
101
+ return;
102
+ }
103
+ this.isUnset = false;
104
+ this.valueIsCommitted = true;
105
+ this.displayValue = this.multiplyByFactor(value);
106
+ this.setPercentageClass(value);
107
+ };
108
+ this.enterUnsetState = () => {
109
+ this.isUnset = true;
110
+ this.valueIsCommitted = false;
111
+ this.displayValue = this.getRestingDisplayValue();
112
+ this.percentageClass = undefined;
113
+ };
63
114
  this.renderStepDots = (min, max) => {
64
115
  if (!this.step) {
65
116
  return;
@@ -77,24 +128,87 @@ export class Slider {
77
128
  }
78
129
  return (h("limel-helper-line", { helperText: this.helperText, helperTextId: this.helperTextId, invalid: this.invalid }));
79
130
  };
131
+ /**
132
+ * A required slider must hold a value, so it offers no way to clear it,
133
+ * and a readonly one cannot be edited at all. The styling needs the same
134
+ * answer to reserve room for the button, so both read it from here rather
135
+ * than each deriving it — the stylesheet cannot see a parsed prop, only
136
+ * the attribute string, and `required="false"` is a present attribute.
137
+ */
138
+ this.hasClearButton = () => {
139
+ return !this.readonly && !this.required;
140
+ };
141
+ this.renderClearButton = () => {
142
+ if (!this.hasClearButton()) {
143
+ return;
144
+ }
145
+ const label = this.label
146
+ ? translate.get('clear-value-of', this.language, {
147
+ label: this.label,
148
+ })
149
+ : translate.get('clear-value', this.language);
150
+ return (h("button", { type: "button", id: this.clearButtonId, class: "clear-button", "aria-label": label, onClick: this.handleClear, disabled: this.isUnset || this.disabled }, h("svg", { viewBox: "0 0 32 32", "aria-hidden": "true", focusable: "false" }, h("path", { d: "M7.219 5.781L5.78 7.22 14.563 16 5.78 24.781 7.22 26.22 16 17.437l8.781 8.782 1.438-1.438L17.437 16l8.782-8.781L24.78 5.78 16 14.563z" })), h("limel-tooltip", { elementId: this.clearButtonId, label: label })));
151
+ };
80
152
  this.handleInput = (event) => {
81
153
  event.stopPropagation();
82
154
  const input = event.target;
83
155
  const value = Number(input.value);
156
+ this.isUnset = false;
84
157
  this.displayValue = value;
85
158
  this.setPercentageClass(value / this.factor);
86
159
  };
87
160
  this.handleChange = (event) => {
88
161
  event.stopPropagation();
89
162
  const input = event.target;
90
- let value = Number(input.value);
163
+ this.commitValue(Number(input.value));
164
+ };
165
+ this.handleClick = (event) => {
166
+ if (this.valueIsCommitted) {
167
+ return;
168
+ }
169
+ // The interaction is over and the value on display still hasn't
170
+ // reached the consumer, so the native input never fired a `change`.
171
+ // Commit what it holds. The click is left to bubble, so consumers
172
+ // listening for clicks still see it.
173
+ const input = event.target;
174
+ this.commitValue(Number(input.value));
175
+ };
176
+ /**
177
+ * Leaves the unset state and emits the value, keeping the rendered state
178
+ * and the emitted value in step so the two cannot diverge.
179
+ * @param displayValue - the value held by the native input, already
180
+ * multiplied by `factor`.
181
+ */
182
+ this.commitValue = (displayValue) => {
91
183
  const step = this.multiplyByFactor(this.step);
92
- if (!this.isMultipleOfStep(value, step)) {
93
- value = this.roundToStep(value, step);
184
+ const min = this.multiplyByFactor(this.valuemin);
185
+ let value = displayValue;
186
+ // Steps are counted from `valuemin`, not from zero — a range of 1–5 in
187
+ // steps of 2 stops at 1, 3 and 5. Rounding against zero would push
188
+ // every one of those to the next even number, past `valuemax`.
189
+ if (!this.isMultipleOfStep(value - min, step)) {
190
+ value = min + this.roundToStep(value - min, step);
94
191
  }
192
+ this.valueIsCommitted = true;
193
+ this.isUnset = false;
194
+ this.displayValue = value;
195
+ this.setPercentageClass(value / this.factor);
95
196
  this.change.emit(value / this.factor);
96
197
  };
198
+ this.handleClear = (event) => {
199
+ var _a;
200
+ event.stopPropagation();
201
+ this.enterUnsetState();
202
+ this.change.emit(null);
203
+ // Move focus to the slider itself so keyboard users can immediately
204
+ // set a new value, and so assistive tech announces the now-unset state
205
+ // instead of focus falling to the body when the button self-disables.
206
+ (_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.focus();
207
+ };
97
208
  this.getContainerClassList = () => {
209
+ return Object.assign({ 'is-unset': this.isUnset, 'has-clear-button': this.hasClearButton() }, this.getPercentageClassList());
210
+ };
211
+ this.getPercentageClassList = () => {
98
212
  if (!this.percentageClass) {
99
213
  return {};
100
214
  }
@@ -105,12 +219,18 @@ export class Slider {
105
219
  this.multiplyByFactor = (value) => {
106
220
  return Math.round(value * this.factor);
107
221
  };
108
- this.getValue = () => {
109
- let value = this.value;
110
- if (!Number.isFinite(value)) {
111
- value = this.valuemin;
112
- }
113
- return value;
222
+ /**
223
+ * The display value the thumb rests at while unset: the midpoint of the
224
+ * range, so both arrow-key directions are live (unlike anchoring at the
225
+ * minimum). Aligned to the step — native range inputs default to a step of
226
+ * 1 — so it matches a real input value and the first key press doesn't jump.
227
+ */
228
+ this.getRestingDisplayValue = () => {
229
+ const min = this.multiplyByFactor(this.valuemin);
230
+ const max = this.multiplyByFactor(this.valuemax);
231
+ const midpoint = (min + max) / 2;
232
+ const step = this.step ? this.multiplyByFactor(this.step) : 1;
233
+ return min + Math.round((midpoint - min) / step) * step;
114
234
  };
115
235
  this.getFraction = () => {
116
236
  const min = this.multiplyByFactor(this.valuemin);
@@ -134,10 +254,10 @@ export class Slider {
134
254
  };
135
255
  this.labelId = createRandomString();
136
256
  this.helperTextId = createRandomString();
257
+ this.clearButtonId = createRandomString();
137
258
  }
138
259
  componentWillLoad() {
139
- this.displayValue = this.multiplyByFactor(this.getValue());
140
- this.setPercentageClass(this.getValue());
260
+ this.syncStateFromValue();
141
261
  }
142
262
  render() {
143
263
  const min = this.multiplyByFactor(this.valuemin);
@@ -150,13 +270,18 @@ export class Slider {
150
270
  if (this.disabled || this.readonly) {
151
271
  inputProps.disabled = true;
152
272
  }
153
- return (h(Host, { key: 'b525c7a5774b9832788a9fb18c3f00798e910d81', class: this.getContainerClassList() }, h("limel-notched-outline", { key: 'e0d97ce8992401fc85b370971a503cb4968bc182', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid, disabled: this.disabled, readonly: this.readonly, hasValue: !!this.value, hasFloatingLabel: true }, h("div", { key: 'fe53bc1695fdc29c42139677d0e0dd76ba17ec9e', slot: "content" }, h("div", { key: '45effbc8e70d5be0fbbbd30fac34d7a9cf7ef2a8', class: "slider", style: { '--slider-fraction': `${fraction}` } }, h("input", Object.assign({ key: '0e7b5da8cca6f95df8528d478619e0e1cc41ee7e', type: "range", min: min, max: max, value: this.displayValue, "aria-labelledby": this.labelId, "aria-describedby": this.helperText
273
+ return (h(Host, { key: 'eecd22982d9180f24f947fd2ab9af9a25e64647e', class: this.getContainerClassList() }, h("limel-notched-outline", { key: '848578a10d64a4af81e296c9f961e9be788484a7', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid, disabled: this.disabled, readonly: this.readonly, hasValue: !this.isUnset, hasFloatingLabel: true }, h("div", { key: '7ad977107b79fc9ca416562d5c7905b4a962053c', slot: "content" }, h("div", { key: 'dcb137a44b5244e63a4c8a95341d3918dc65445c', class: "slider", style: { '--slider-fraction': `${fraction}` } }, h("input", Object.assign({ key: '73af46f062991f3c715d13c0f44b0fb9478dc37c', type: "range", min: min, max: max, value: this.displayValue, "aria-labelledby": this.labelId, "aria-describedby": this.helperText
154
274
  ? this.helperTextId
155
- : undefined, onInput: this.handleInput, onChange: this.handleChange }, inputProps)), h("div", { key: '7a064053a0e89209813c68637991f0e23c6ec877', class: "track" }, h("div", { key: '042a6185e0fa842bcd077cefe0934116827432fa', class: "active" }), this.renderStepDots(min, max)), h("div", { key: '09f11ca04aa46a02f498507acae7d84e37210f14', class: "thumb" }, h("div", { key: 'd1b2513e6694a3bb0bba05a97139cc5fecca62e5', class: "knob" }), h("div", { key: 'cc135f641c78e80c9b57e86369e6cdf2cbf152a1', class: "indicator", "aria-hidden": "true" }, this.displayValue))), h("div", { key: '76baf35402525fc825667f04a7fdea5d1ff551cc', class: "range-labels" }, h("span", { key: 'c7d50a897ae0dcea864b8d98fca72cd1aef19f3b', class: "min" }, this.multiplyByFactor(this.valuemin), this.unit), h("span", { key: '65c5dee93cc75c978eaf9c6dc7be89f734e74913', class: "max" }, this.multiplyByFactor(this.valuemax), this.unit)))), this.renderHelperLine()));
275
+ : undefined, "aria-valuetext": this.isUnset
276
+ ? translate.get('value-not-set', this.language)
277
+ : undefined, ref: (el) => {
278
+ this.inputElement = el;
279
+ }, onInput: this.handleInput, onChange: this.handleChange, onClick: this.handleClick }, inputProps)), h("div", { key: 'e9fb2af7f43cc040c382930d336c8eb903bc0162', class: "track" }, h("div", { key: '2cec88a4b8c99296473bbd9b064bccd0a6450b8f', class: "active" }), this.renderStepDots(min, max)), h("div", { key: '5a090fb0528478450510ff0240e6b5499a177238', class: "thumb" }, h("div", { key: 'fde6792041312183af0328d9fc75d19d2553bf46', class: "knob" }), h("div", { key: 'de717ffa20723870bb27aa22e99fe9fe5ebdd563', class: "indicator", "aria-hidden": "true" }, this.isUnset
280
+ ? '\u2194\uFE0E'
281
+ : this.displayValue))), h("div", { key: 'd0c38d092fecc4bf9690680d8541c8d658056f08', class: "range-labels" }, h("span", { key: '581387615da0cdaf105a568e02212f8bdd22eeb6', class: "min" }, this.multiplyByFactor(this.valuemin), this.unit), h("span", { key: '33c5673d8e56995bffdc35340381f2e87ae0918c', class: "max" }, this.multiplyByFactor(this.valuemax), this.unit)))), this.renderClearButton(), this.renderHelperLine()));
156
282
  }
157
283
  watchValue() {
158
- this.displayValue = this.multiplyByFactor(this.getValue());
159
- this.setPercentageClass(this.getValue());
284
+ this.syncStateFromValue();
160
285
  }
161
286
  static get is() { return "limel-slider"; }
162
287
  static get encapsulation() { return "shadow"; }
@@ -282,7 +407,7 @@ export class Slider {
282
407
  "optional": false,
283
408
  "docs": {
284
409
  "tags": [],
285
- "text": "Set to `true` to indicate that the slider is required."
410
+ "text": "Set to `true` to indicate that the slider is required.\nA required slider does not offer the clear button, since a required\nvalue cannot be unset. It can still be initialized unset (with a\nnon-finite `value`) to prompt the user to make a choice."
286
411
  },
287
412
  "getter": false,
288
413
  "setter": false,
@@ -350,11 +475,38 @@ export class Slider {
350
475
  "attribute": "unit",
351
476
  "defaultValue": "''"
352
477
  },
478
+ "language": {
479
+ "type": "string",
480
+ "mutable": false,
481
+ "complexType": {
482
+ "original": "Languages",
483
+ "resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
484
+ "references": {
485
+ "Languages": {
486
+ "location": "import",
487
+ "path": "../date-picker/date.types",
488
+ "id": "src/components/date-picker/date.types.ts::Languages",
489
+ "referenceLocation": "Languages"
490
+ }
491
+ }
492
+ },
493
+ "required": false,
494
+ "optional": false,
495
+ "docs": {
496
+ "tags": [],
497
+ "text": "Defines the language for translations of the accessible labels."
498
+ },
499
+ "getter": false,
500
+ "setter": false,
501
+ "reflect": true,
502
+ "attribute": "language",
503
+ "defaultValue": "'en'"
504
+ },
353
505
  "value": {
354
506
  "type": "number",
355
507
  "mutable": false,
356
508
  "complexType": {
357
- "original": "number",
509
+ "original": "number | null",
358
510
  "resolved": "number",
359
511
  "references": {}
360
512
  },
@@ -362,7 +514,7 @@ export class Slider {
362
514
  "optional": false,
363
515
  "docs": {
364
516
  "tags": [],
365
- "text": "The value of the input"
517
+ "text": "The value of the input. Set it to `null` to leave the slider unset,\nwhich is also what the `change` event emits once the value is cleared.\nAny other value that is not a finite number \u2014 `undefined`, or `NaN` \u2014\nunsets the slider too."
366
518
  },
367
519
  "getter": false,
368
520
  "setter": false,
@@ -433,7 +585,8 @@ export class Slider {
433
585
  static get states() {
434
586
  return {
435
587
  "percentageClass": {},
436
- "displayValue": {}
588
+ "displayValue": {},
589
+ "isUnset": {}
437
590
  };
438
591
  }
439
592
  static get events() {
@@ -445,10 +598,10 @@ export class Slider {
445
598
  "composed": true,
446
599
  "docs": {
447
600
  "tags": [],
448
- "text": "Emitted when the value has been changed"
601
+ "text": "Emitted when the value has been changed.\nEmits `null` when the value has been cleared and the slider becomes\nunset, so handlers must account for a value that is not a number."
449
602
  },
450
603
  "complexType": {
451
- "original": "number",
604
+ "original": "number | null",
452
605
  "resolved": "number",
453
606
  "references": {}
454
607
  }
@@ -13,18 +13,31 @@ const allTranslations = {
13
13
  fi: fi,
14
14
  fr: fr,
15
15
  no: no,
16
+ nb: no, // Norwegian Bokmål shares the Norwegian (`no`) translations
16
17
  nl: nl,
17
18
  sv: sv,
18
19
  };
19
20
  const REGEX = /\{\s*(\w+)\s*\}/g;
20
21
  export class Translations {
21
22
  get(key, language = 'en', params) {
22
- const translation = allTranslations[language][key];
23
+ var _a, _b;
24
+ // Fall back to English when the requested language has no translations.
25
+ // The `language` props are typed, but a custom element takes whatever
26
+ // string an attribute carries, so an unknown language must never make a
27
+ // component throw.
28
+ const languageTranslations = (_a = allTranslations[language]) !== null && _a !== void 0 ? _a : allTranslations.en;
29
+ // Fall back per key as well: a mapped language whose file is missing
30
+ // this one key would otherwise render the key itself as UI text.
31
+ const translation = (_b = languageTranslations[key]) !== null && _b !== void 0 ? _b : allTranslations.en[key];
23
32
  if (!translation) {
24
33
  return key;
25
34
  }
26
35
  return translation.replaceAll(REGEX, (match, mergeCodeKey) => {
27
- return params[mergeCodeKey] || match;
36
+ var _a;
37
+ // Nullish, not falsy: `0` and `false` are values a caller can
38
+ // legitimately merge in, and `||` would leave the merge code
39
+ // itself in the string instead.
40
+ return String((_a = params === null || params === void 0 ? void 0 : params[mergeCodeKey]) !== null && _a !== void 0 ? _a : match);
28
41
  });
29
42
  }
30
43
  }
@@ -1,4 +1,7 @@
1
1
  export default {
2
+ 'clear-value': 'Ryd værdi',
3
+ 'clear-value-of': 'Ryd værdien for { label }',
4
+ 'value-not-set': 'Værdi ikke angivet',
2
5
  remove: 'Fjern',
3
6
  save: 'Gem',
4
7
  cancel: 'Annullér',
@@ -1,4 +1,7 @@
1
1
  export default {
2
+ 'clear-value': 'Wert löschen',
3
+ 'clear-value-of': 'Wert von { label } löschen',
4
+ 'value-not-set': 'Wert nicht festgelegt',
2
5
  remove: 'Entfernen',
3
6
  save: 'Speichern',
4
7
  cancel: 'Abbrechen',
@@ -1,4 +1,7 @@
1
1
  export default {
2
+ 'clear-value': 'Clear value',
3
+ 'clear-value-of': 'Clear value of { label }',
4
+ 'value-not-set': 'Value not set',
2
5
  remove: 'Remove',
3
6
  save: 'Save',
4
7
  cancel: 'Cancel',
@@ -1,4 +1,7 @@
1
1
  export default {
2
+ 'clear-value': 'Tyhjennä arvo',
3
+ 'clear-value-of': 'Tyhjennä kentän { label } arvo',
4
+ 'value-not-set': 'Arvoa ei asetettu',
2
5
  remove: 'Poista',
3
6
  save: 'Tallenna',
4
7
  cancel: 'Peruuta',