@lion/ui 0.10.0 → 0.11.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 (118) hide show
  1. package/components/button/src/LionButton.js +5 -1
  2. package/components/button/test-suites/LionButton.suite.js +1 -1
  3. package/components/calendar/src/LionCalendar.js +5 -14
  4. package/components/calendar/test/lion-calendar.test.js +88 -0
  5. package/components/calendar/translations/bg.js +5 -0
  6. package/components/calendar/translations/cs.js +5 -0
  7. package/components/calendar/translations/de.js +5 -0
  8. package/components/calendar/translations/en.js +5 -0
  9. package/components/calendar/translations/es.js +5 -0
  10. package/components/calendar/translations/fr.js +5 -0
  11. package/components/calendar/translations/hu.js +5 -0
  12. package/components/calendar/translations/it.js +5 -0
  13. package/components/calendar/translations/nl.js +5 -0
  14. package/components/calendar/translations/pl.js +5 -0
  15. package/components/calendar/translations/ro.js +5 -0
  16. package/components/calendar/translations/ru.js +5 -0
  17. package/components/calendar/translations/sk.js +5 -0
  18. package/components/calendar/translations/uk.js +3 -0
  19. package/components/calendar/translations/zh.js +3 -0
  20. package/components/core/src/SlotMixin.js +15 -0
  21. package/components/core/test/SlotMixin.test.js +50 -0
  22. package/components/dialog/src/LionDialog.js +13 -0
  23. package/components/dialog/test/lion-dialog.test.js +56 -0
  24. package/components/form-core/src/choice-group/ChoiceInputMixin.js +3 -1
  25. package/components/input-amount/test/lion-input-amount.test.js +48 -1
  26. package/components/input-amount/test/parsers.test.js +1 -1
  27. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +1 -1
  28. package/components/localize/src/number/parseNumber.js +21 -12
  29. package/components/localize/test/number/parseNumber.test.js +1 -1
  30. package/components/overlays/src/OverlayController.js +17 -1
  31. package/components/overlays/test/OverlayController.test.js +29 -0
  32. package/components/overlays/types/OverlayConfig.ts +4 -2
  33. package/components/select-rich/src/LionSelectRich.js +17 -1
  34. package/components/select-rich/test/lion-select-rich-interaction.test.js +139 -20
  35. package/components/validate-messages/translations/pl.js +4 -4
  36. package/custom-elements.json +38 -13
  37. package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
  38. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  39. package/dist-types/components/calendar/translations/bg.d.ts +3 -0
  40. package/dist-types/components/calendar/translations/cs.d.ts +3 -0
  41. package/dist-types/components/calendar/translations/de.d.ts +3 -0
  42. package/dist-types/components/calendar/translations/en.d.ts +3 -0
  43. package/dist-types/components/calendar/translations/es.d.ts +3 -0
  44. package/dist-types/components/calendar/translations/fr.d.ts +3 -0
  45. package/dist-types/components/calendar/translations/hu.d.ts +3 -0
  46. package/dist-types/components/calendar/translations/it.d.ts +3 -0
  47. package/dist-types/components/calendar/translations/nl.d.ts +3 -0
  48. package/dist-types/components/calendar/translations/pl.d.ts +3 -0
  49. package/dist-types/components/calendar/translations/ro.d.ts +3 -0
  50. package/dist-types/components/calendar/translations/ru.d.ts +3 -0
  51. package/dist-types/components/calendar/translations/sk.d.ts +3 -0
  52. package/dist-types/components/calendar/translations/uk.d.ts +3 -0
  53. package/dist-types/components/calendar/translations/zh.d.ts +3 -0
  54. package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
  55. package/dist-types/components/dialog/src/LionDialog.d.ts +32 -0
  56. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  57. package/dist-types/components/form-core/src/choice-group/ChoiceInputMixin.d.ts.map +1 -1
  58. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts +1 -1
  59. package/dist-types/components/localize/src/number/parseNumber.d.ts.map +1 -1
  60. package/dist-types/components/overlays/src/OverlayController.d.ts +5 -0
  61. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  62. package/dist-types/components/overlays/types/OverlayConfig.d.ts +4 -2
  63. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  64. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +1 -0
  65. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  66. package/dist-types/tsconfig.tsbuildinfo +1 -1
  67. package/docs/components/checkbox-group/overview.md +2 -2
  68. package/docs/components/collapsible/overview.md +1 -1
  69. package/docs/components/combobox/overview.md +1 -1
  70. package/docs/components/combobox/use-cases.md +1 -1
  71. package/docs/components/dialog/api-table.md +5 -3
  72. package/docs/components/dialog/overview.md +1 -1
  73. package/docs/components/dialog/use-cases.md +32 -1
  74. package/docs/components/fieldset/overview.md +5 -5
  75. package/docs/components/form/overview.md +3 -3
  76. package/docs/components/form/use-cases.md +2 -2
  77. package/docs/components/icon/use-cases.md +1 -1
  78. package/docs/components/input/overview.md +1 -1
  79. package/docs/components/input-amount/overview.md +3 -3
  80. package/docs/components/input-date/overview.md +3 -3
  81. package/docs/components/input-datepicker/overview.md +5 -5
  82. package/docs/components/input-email/overview.md +2 -2
  83. package/docs/components/input-file/overview.md +1 -1
  84. package/docs/components/input-iban/overview.md +2 -2
  85. package/docs/components/input-range/overview.md +2 -2
  86. package/docs/components/input-stepper/overview.md +1 -1
  87. package/docs/components/input-tel/overview.md +1 -1
  88. package/docs/components/input-tel/use-cases.md +6 -6
  89. package/docs/components/input-tel-dropdown/overview.md +1 -1
  90. package/docs/components/listbox/use-cases.md +1 -1
  91. package/docs/components/radio-group/overview.md +3 -3
  92. package/docs/components/select/use-cases.md +1 -1
  93. package/docs/components/select-rich/use-cases.md +1 -1
  94. package/docs/components/tabs/examples.md +1 -1
  95. package/docs/fundamentals/index.md +4 -4
  96. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  97. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  98. package/docs/fundamentals/rationales/accessibility.md +1 -1
  99. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  100. package/docs/fundamentals/systems/core/overview.md +1 -1
  101. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  102. package/docs/fundamentals/systems/form/model-value.md +1 -1
  103. package/docs/fundamentals/systems/form/overview.md +28 -28
  104. package/docs/fundamentals/systems/form/validate.md +2 -2
  105. package/docs/fundamentals/systems/localize/overview.md +4 -4
  106. package/docs/fundamentals/systems/overlays/api-table.md +35 -34
  107. package/docs/fundamentals/systems/overlays/configuration.md +32 -2
  108. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  109. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  110. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  111. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  112. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  113. package/docs/guides/how-to/create-a-form.md +10 -9
  114. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  115. package/docs/guides/index.md +8 -8
  116. package/docs/guides/principles/index.md +5 -5
  117. package/docs/guides/principles/styling.md +1 -1
  118. package/package.json +1 -1
@@ -149,7 +149,11 @@ export class LionButton extends DisabledWithTabIndexMixin(LitElement) {
149
149
  super.updated(changedProperties);
150
150
 
151
151
  if (changedProperties.has('disabled')) {
152
- this.setAttribute('aria-disabled', `${this.disabled}`); // create mixin if we need it in more places
152
+ if (this.disabled) {
153
+ this.setAttribute('aria-disabled', 'true');
154
+ } else if (this.getAttribute('aria-disabled') !== null) {
155
+ this.removeAttribute('aria-disabled');
156
+ }
153
157
  }
154
158
  }
155
159
 
@@ -40,7 +40,7 @@ export function LionButtonSuite({ klass = LionButton } = {}) {
40
40
  el.disabled = false;
41
41
  await el.updateComplete;
42
42
  expect(el.getAttribute('tabindex')).to.equal('0');
43
- expect(el.getAttribute('aria-disabled')).to.equal('false');
43
+ expect(el.getAttribute('aria-disabled')).to.not.exist;
44
44
  expect(el.hasAttribute('disabled')).to.equal(false);
45
45
 
46
46
  el.disabled = true;
@@ -658,25 +658,16 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
658
658
  day.ariaCurrent = day.today ? 'date' : undefined;
659
659
  day.disabledInfo = '';
660
660
 
661
+ if (day.disabled) {
662
+ day.disabledInfo = `${this.msgLit(`lion-calendar:defaultDisabledDate`)}`;
663
+ }
661
664
  if (this.minDate && normalizeDateTime(day.date) < normalizeDateTime(this.minDate)) {
662
665
  day.disabled = true;
663
- // TODO: turn this into a translated string
664
- day.disabledInfo = `This date is unavailable. Earliest date to select is ${
665
- this.__getSelectableDateRange().earliestSelectableDate
666
- }. Please select another date.`;
666
+ day.disabledInfo = `${this.msgLit(`lion-calendar:beforeDisabledDate`, { params: this.__getSelectableDateRange().earliestSelectableDate })}`;
667
667
  }
668
-
669
668
  if (this.maxDate && normalizeDateTime(day.date) > normalizeDateTime(this.maxDate)) {
670
669
  day.disabled = true;
671
- // TODO: turn this into a translated string
672
- day.disabledInfo = `This date is unavailable. Latest date to select is ${
673
- this.__getSelectableDateRange().latestSelectableDate
674
- }. Please select another date.`;
675
- }
676
-
677
- if (day.disabled) {
678
- // TODO: turn this into a translated string
679
- day.disabledInfo = `This date is unavailable. Please select another date`;
670
+ day.disabledInfo = `${this.msgLit(`lion-calendar:afterDisabledDate`, { params: this.__getSelectableDateRange().latestSelectableDate })}`;
680
671
  }
681
672
 
682
673
  return this.dayPreprocessor(day);
@@ -896,6 +896,94 @@ describe('<lion-calendar>', () => {
896
896
  'Next year, December 2001',
897
897
  );
898
898
  });
899
+
900
+ it('disables a date with disableDates function provide a correct message', async () => {
901
+ /** @param {Date} d */
902
+ const disable15th = d => d.getDate() === 15;
903
+ const el = await fixture(html`
904
+ <lion-calendar
905
+ .selectedDate="${new Date('2000/12/01')}"
906
+ .disableDates=${disable15th}
907
+ ></lion-calendar>
908
+ `);
909
+ const elObj = new CalendarObject(el);
910
+ expect(elObj.getDayObj(15).isDisabled).to.equal(true);
911
+ expect(elObj.getDayObj(15).el).dom.to.equal(`
912
+ <div
913
+ class="calendar__day-button"
914
+ aria-disabled="true"
915
+ role="button"
916
+ tabindex="-1"
917
+ aria-pressed="false"
918
+ past=""
919
+ current-month="">
920
+ <span class="calendar__day-button__text">
921
+ 15
922
+ </span>
923
+ <span class="u-sr-only">
924
+ December 2000 Friday This date is unavailable. Please choose another date.
925
+ </span>
926
+ </div>
927
+ `);
928
+ });
929
+
930
+ it('disables days before "minDate" property provide a correct message', async () => {
931
+ const el = await fixture(html`
932
+ <lion-calendar
933
+ .selectedDate="${new Date('2000/12/31')}"
934
+ .minDate="${new Date('2000/12/09')}"
935
+ >
936
+ </lion-calendar>
937
+ `);
938
+ const elObj = new CalendarObject(el);
939
+ expect(elObj.getDayObj(1).isDisabled).to.equal(true);
940
+ expect(elObj.getDayObj(1).el).dom.to.equal(`
941
+ <div
942
+ class="calendar__day-button"
943
+ aria-disabled="true"
944
+ role="button"
945
+ tabindex="-1"
946
+ aria-pressed="false"
947
+ past=""
948
+ current-month="">
949
+ <span class="calendar__day-button__text">
950
+ 1
951
+ </span>
952
+ <span class="u-sr-only">
953
+ December 2000 Friday This date is unavailable. Earliest available date is 9 December 2000. Please choose another date.
954
+ </span>
955
+ </div>
956
+ `);
957
+ });
958
+
959
+ it('disables days after "maxDate" property provide a correct message', async () => {
960
+ const el = await fixture(html`
961
+ <lion-calendar
962
+ .selectedDate="${new Date('2000/12/31')}"
963
+ .maxDate="${new Date('2000/12/09')}"
964
+ >
965
+ </lion-calendar>
966
+ `);
967
+ const elObj = new CalendarObject(el);
968
+ expect(elObj.getDayObj(30).isDisabled).to.equal(true);
969
+ expect(elObj.getDayObj(30).el).dom.to.equal(`
970
+ <div
971
+ class="calendar__day-button"
972
+ aria-disabled="true"
973
+ role="button"
974
+ tabindex="-1"
975
+ aria-pressed="false"
976
+ past=""
977
+ current-month="">
978
+ <span class="calendar__day-button__text">
979
+ 30
980
+ </span>
981
+ <span class="u-sr-only">
982
+ December 2000 Saturday This date is unavailable. Latest available date is 9 December 2000. Please choose another date.
983
+ </span>
984
+ </div>
985
+ `);
986
+ });
899
987
  });
900
988
  });
901
989
  });
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Предишен месец',
4
4
  nextFullYear: 'Следващ година',
5
5
  previousFullYear: 'Предишен година',
6
+ defaultDisabledDate: 'Тази дата не е налична. Моля, изберете друга дата.',
7
+ beforeDisabledDate:
8
+ 'Тази дата не е налична. Най-ранната налична да е {params}. Моля, изберете друга дата.',
9
+ afterDisabledDate:
10
+ 'Тази дата не е налична. Най-късната налична дата е {params}. Моля, изберете друга дата.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Předchozí měsíc',
4
4
  nextFullYear: 'Příští rok',
5
5
  previousFullYear: 'Předchozí rok',
6
+ defaultDisabledDate: 'Toto datum není k dispozici. Vyberte jiné datum.',
7
+ beforeDisabledDate:
8
+ 'Toto datum není k dispozici. Nejbližší dostupné datum je {params}. Vyberte jiné datum.',
9
+ afterDisabledDate:
10
+ 'Toto datum není k dispozici. Poslední dostupné datum je {params}. Vyberte jiné datum.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Vorheriger Monat',
4
4
  nextFullYear: 'Nächstes Jahr',
5
5
  previousFullYear: 'Vorheriges Jahr',
6
+ defaultDisabledDate: 'Dieses Datum ist nicht verfügbar. Bitte wählen Sie ein anderes Datum aus.',
7
+ beforeDisabledDate:
8
+ 'Dieses Datum ist nicht verfügbar. Das früheste verfügbare Datum ist {params}. Bitte wählen Sie ein anderes Datum aus.',
9
+ afterDisabledDate:
10
+ 'Dieses Datum ist nicht verfügbar. Das späteste verfügbare Datum ist {params}. Bitte wählen Sie ein anderes Datum aus.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Previous month',
4
4
  nextFullYear: 'Next year',
5
5
  previousFullYear: 'Previous year',
6
+ defaultDisabledDate: 'This date is unavailable. Please choose another date.',
7
+ beforeDisabledDate:
8
+ 'This date is unavailable. Earliest available date is {params}. Please choose another date.',
9
+ afterDisabledDate:
10
+ 'This date is unavailable. Latest available date is {params}. Please choose another date.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Mes anterior',
4
4
  nextFullYear: 'Año siguiente',
5
5
  previousFullYear: 'Año anterior',
6
+ defaultDisabledDate: 'Esta fecha no está disponible. Elija otra.',
7
+ beforeDisabledDate:
8
+ 'Esta fecha no está disponible. La fecha más próxima disponible es {params}. Elija otra.',
9
+ afterDisabledDate:
10
+ 'Esta fecha no está disponible. La fecha más lejana disponible es {params}. Elija otra.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Mois précédent',
4
4
  nextFullYear: 'An prochain',
5
5
  previousFullYear: 'An précédent',
6
+ defaultDisabledDate: "Cette date n'est pas disponible. Veuillez choisir une autre date.",
7
+ beforeDisabledDate:
8
+ "Cette date n'est pas disponible. La prochaine date disponible est la suivante : {params}. Veuillez choisir une autre date.",
9
+ afterDisabledDate:
10
+ "Cette date n'est pas disponible. La dernière date disponible est la suivante : {params}. Veuillez choisir une autre date.",
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Előző hónap',
4
4
  nextFullYear: 'Következő év',
5
5
  previousFullYear: 'Előző év',
6
+ defaultDisabledDate: 'Ez a dátum nem választható. Válasszon másik dátumot.',
7
+ beforeDisabledDate:
8
+ 'Ez a dátum nem választható. A legkorábbi elérhető dátum {params}. Válasszon másik dátumot.',
9
+ afterDisabledDate:
10
+ 'Ez a dátum nem választható. A legkésőbbi elérhető dátum {params}. Válasszon másik dátumot.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Mese precedente',
4
4
  nextFullYear: 'Anno successivo',
5
5
  previousFullYear: 'Anno precedente',
6
+ defaultDisabledDate: "Questa data non è disponibile. Scegline un'altra.",
7
+ beforeDisabledDate:
8
+ "Questa data non è disponibile. La prima data disponibile è {params}. Scegline un'altra.",
9
+ afterDisabledDate:
10
+ "Questa data non è disponibile. L'ultima data disponibile è {params}. Scegline un'altra.",
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Vorige maand',
4
4
  nextFullYear: 'Volgend jaar',
5
5
  previousFullYear: 'Vorig jaar',
6
+ defaultDisabledDate: 'Deze datum is niet beschikbaar. Kies een andere datum.',
7
+ beforeDisabledDate:
8
+ 'Deze datum is niet beschikbaar. Vroegste beschikbare datum is {params}. Kies een andere datum.',
9
+ afterDisabledDate:
10
+ 'Deze datum is niet beschikbaar. Laatste beschikbare datum is {params}. Kies een andere datum.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Poprzedni miesiąc',
4
4
  nextFullYear: 'Następny rok',
5
5
  previousFullYear: 'Poprzedni rok',
6
+ defaultDisabledDate: 'Ta data jest niedostępna. Wybierz inną datę.',
7
+ beforeDisabledDate:
8
+ 'Ta data jest niedostępna. Najwcześniejsza dostępna data to {params}. Wybierz inną datę.',
9
+ afterDisabledDate:
10
+ 'Ta data jest niedostępna. Najpóźniejsza dostępna data to {params}. Wybierz inną datę.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Luna anterioară',
4
4
  nextFullYear: 'An viitoare',
5
5
  previousFullYear: 'An anterioară',
6
+ defaultDisabledDate: 'Această dată nu este disponibilă. Vă rugăm să alegeți altă dată.',
7
+ beforeDisabledDate:
8
+ 'Această dată nu este disponibilă. Cea mai apropiată dată disponibilă este {params}. Vă rugăm să alegeți altă dată.',
9
+ afterDisabledDate:
10
+ 'Această dată nu este disponibilă. Ultima dată disponibilă este {params}. Vă rugăm să alegeți altă dată.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Предыдущий месяц',
4
4
  nextFullYear: 'Следующий год',
5
5
  previousFullYear: 'Предыдущий год',
6
+ defaultDisabledDate: 'Эта дата недоступна. Выберите другую дату.',
7
+ beforeDisabledDate:
8
+ 'Эта дата недоступна. Самая ранняя доступная дата — {params}. Выберите другую дату.',
9
+ afterDisabledDate:
10
+ 'Эта дата недоступна. Самая поздняя доступная дата — {params}. Выберите другую дату.',
6
11
  };
@@ -3,4 +3,9 @@ export default {
3
3
  previousMonth: 'Predchádzajúci mesiac',
4
4
  nextFullYear: 'Nasledujúci rok',
5
5
  previousFullYear: 'Predchádzajúci rok',
6
+ defaultDisabledDate: 'Tento dátum nie je k dispozícii. Vyberte iný dátum.',
7
+ beforeDisabledDate:
8
+ 'Tento dátum nie je k dispozícii. Najbližší možný dátum je {params}. Vyberte iný dátum.',
9
+ afterDisabledDate:
10
+ 'Tento dátum nie je k dispozícii. Posledný možný dátum je {params}. Vyberte iný dátum.',
6
11
  };
@@ -3,4 +3,7 @@ export default {
3
3
  previousMonth: 'Попередній місяць',
4
4
  nextFullYear: 'Наступний року',
5
5
  previousFullYear: 'Попередній року',
6
+ defaultDisabledDate: 'Ця дата недоступна. Виберіть іншу дату.',
7
+ beforeDisabledDate: 'Ця дата недоступна. Найближча доступна дата {params}. Виберіть іншу дату.',
8
+ afterDisabledDate: 'Ця дата недоступна. Остання доступна дата {params}. Виберіть іншу дату.',
6
9
  };
@@ -3,4 +3,7 @@ export default {
3
3
  previousMonth: '前一个月',
4
4
  nextFullYear: '明一年',
5
5
  previousFullYear: '前一年',
6
+ defaultDisabledDate: '此日期不可选。请选择另一个日期。',
7
+ beforeDisabledDate: '此日期不可选。最早可选日期为 {params}。请选择另一个日期。',
8
+ afterDisabledDate: '此日期不可选。最晚可选日期为 {params}。请选择另一个日期。',
6
9
  };
@@ -182,6 +182,21 @@ const SlotMixinImplementation = superclass =>
182
182
  // Providing all options breaks Safari: we keep host and creationScope
183
183
  const { creationScope, host } = this.renderOptions;
184
184
  render(template, rerenderTarget, { creationScope, host, renderBefore });
185
+
186
+ // Assume we had this config:
187
+ // `'my-slot': () => ({ template: myBool ? html`<div id=a></div>` : html`<span id=b></span>`, renderAsDirectHostChild: true })`
188
+ // If myBool started as true, <div id=a></div> would be rendered in first render above, a slot would be applied,
189
+ // resulting in <div id=a slot=my-slot></div>
190
+ // However, when myBool changes to false, the <span id=b></span> would be rendered as root instead...
191
+ // We need to make sure that this "replaced root" gets the slot applied as well => <span id=b slot=my-slot></span>
192
+ const isRerenderingRootOfTemplate =
193
+ renderAsDirectHostChild &&
194
+ renderBefore.previousElementSibling &&
195
+ !renderBefore.previousElementSibling.slot;
196
+
197
+ if (isRerenderingRootOfTemplate) {
198
+ renderBefore.previousElementSibling.slot = slotName;
199
+ }
185
200
  }
186
201
 
187
202
  /**
@@ -345,6 +345,56 @@ describe('SlotMixin', () => {
345
345
  expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true;
346
346
  });
347
347
 
348
+ it('allows for switching template root in slot as a direct child', async () => {
349
+ const tagName = defineCE(
350
+ // @ts-expect-error
351
+ class extends SlotMixin(LitElement) {
352
+ static properties = { isSwitched: Boolean };
353
+
354
+ constructor() {
355
+ super();
356
+ this.isSwitched = false;
357
+ }
358
+
359
+ get slots() {
360
+ return {
361
+ ...super.slots,
362
+ 'my-root-switcher-node': () => ({
363
+ template: this.isSwitched
364
+ ? html`<div id="is-switched"></div>`
365
+ : html`<span id="is-not-switched"> </span> `,
366
+ renderAsDirectHostChild: true,
367
+ }),
368
+ };
369
+ }
370
+
371
+ render() {
372
+ return html`<slot name="my-root-switcher-node"></slot>`;
373
+ }
374
+
375
+ get _myRootSwitcherNode() {
376
+ return /** @type HTMLSpanElement */ (
377
+ Array.from(this.children).find(elm => elm.slot === 'my-root-switcher-node')
378
+ );
379
+ }
380
+ },
381
+ );
382
+ const el = /** @type {* & SlotHost} */ (await fixture(`<${tagName}></${tagName}>`));
383
+
384
+ expect(el._myRootSwitcherNode.id).to.equal('is-not-switched');
385
+ expect(el.innerHTML).to.equal(
386
+ `<!--_start_slot_my-root-switcher-node_--><!----><span id="is-not-switched" slot="my-root-switcher-node"> </span> <!--_end_slot_my-root-switcher-node_-->`,
387
+ );
388
+
389
+ el.isSwitched = true;
390
+ await el.updateComplete;
391
+
392
+ expect(el._myRootSwitcherNode.id).to.equal('is-switched');
393
+ expect(el.innerHTML).to.equal(
394
+ `<!--_start_slot_my-root-switcher-node_--><!----><div id="is-switched" slot="my-root-switcher-node"></div><!--_end_slot_my-root-switcher-node_-->`,
395
+ );
396
+ });
397
+
348
398
  describe('firstRenderOnConnected (for backwards compatibility)', () => {
349
399
  it('does render on connected when firstRenderOnConnected:true', async () => {
350
400
  // Start with elem that does not render on connectedCallback
@@ -2,6 +2,18 @@ import { html, LitElement } from 'lit';
2
2
  import { OverlayMixin, withModalDialogConfig } from '@lion/ui/overlays.js';
3
3
 
4
4
  export class LionDialog extends OverlayMixin(LitElement) {
5
+ /** @type {any} */
6
+ static get properties() {
7
+ return {
8
+ isAlertDialog: { type: Boolean, attribute: 'is-alert-dialog' },
9
+ };
10
+ }
11
+
12
+ constructor() {
13
+ super();
14
+ this.isAlertDialog = false;
15
+ }
16
+
5
17
  /**
6
18
  * @protected
7
19
  */
@@ -9,6 +21,7 @@ export class LionDialog extends OverlayMixin(LitElement) {
9
21
  _defineOverlayConfig() {
10
22
  return {
11
23
  ...withModalDialogConfig(),
24
+ isAlertDialog: this.isAlertDialog,
12
25
  };
13
26
  }
14
27
 
@@ -170,6 +170,27 @@ describe('lion-dialog', () => {
170
170
  });
171
171
 
172
172
  describe('Accessibility', () => {
173
+ it('passes a11y audit', async () => {
174
+ const el = await fixture(html`
175
+ <lion-dialog>
176
+ <button slot="invoker">Invoker</button>
177
+ <div slot="content" class="dialog" aria-label="Dialog">Hey there</div>
178
+ </lion-dialog>
179
+ `);
180
+ await expect(el).to.be.accessible();
181
+ });
182
+
183
+ it('passes a11y audit when opened', async () => {
184
+ const el = await fixture(html`
185
+ <lion-dialog opened>
186
+ <button slot="invoker">Invoker</button>
187
+ <div slot="content" class="dialog" aria-label="Dialog">Hey there</div>
188
+ </lion-dialog>
189
+ `);
190
+ // error expected since we put role="none" on the dialog itself, which is valid but not recognized by Axe
191
+ await expect(el).to.be.accessible({ ignoredRules: ['aria-allowed-role'] });
192
+ });
193
+
173
194
  it('does not add [aria-expanded] to invoker button', async () => {
174
195
  const el = await fixture(
175
196
  html` <lion-dialog>
@@ -187,6 +208,41 @@ describe('lion-dialog', () => {
187
208
  await aTimeout(0);
188
209
  expect(invokerButton.getAttribute('aria-expanded')).to.equal(null);
189
210
  });
211
+
212
+ it('has role="dialog" by default', async () => {
213
+ const el = await fixture(
214
+ html` <lion-dialog>
215
+ <div slot="content" class="dialog">Hey there</div>
216
+ <button slot="invoker">Popup button</button>
217
+ </lion-dialog>`,
218
+ );
219
+ const contentNode = /** @type {HTMLElement} */ (el.querySelector('[slot="content"]'));
220
+
221
+ expect(contentNode.getAttribute('role')).to.equal('dialog');
222
+ });
223
+
224
+ it('has role="alertdialog" by when "is-alert-dialog" is set', async () => {
225
+ const el = await fixture(
226
+ html` <lion-dialog is-alert-dialog>
227
+ <div slot="content" class="dialog">Hey there</div>
228
+ <button slot="invoker">Popup button</button>
229
+ </lion-dialog>`,
230
+ );
231
+ const contentNode = /** @type {HTMLElement} */ (el.querySelector('[slot="content"]'));
232
+
233
+ expect(contentNode.getAttribute('role')).to.equal('alertdialog');
234
+ });
235
+
236
+ it('passes a11y audit when opened and role="alertdialog"', async () => {
237
+ const el = await fixture(html`
238
+ <lion-dialog opened is-alert-dialog>
239
+ <button slot="invoker">Invoker</button>
240
+ <div slot="content" class="dialog" aria-label="Dialog">Hey there</div>
241
+ </lion-dialog>
242
+ `);
243
+ // error expected since we put role="none" on the dialog itself, which is valid but not recognized by Axe
244
+ await expect(el).to.be.accessible({ ignoredRules: ['aria-allowed-role'] });
245
+ });
190
246
  });
191
247
 
192
248
  describe('Edge cases', () => {
@@ -171,7 +171,9 @@ const ChoiceInputMixinImplementation = superclass =>
171
171
  render() {
172
172
  return html`
173
173
  <slot name="input"></slot>
174
- <div class="choice-field__graphic-container">${this._choiceGraphicTemplate()}</div>
174
+ <div class="choice-field__graphic-container" aria-hidden="true">
175
+ ${this._choiceGraphicTemplate()}
176
+ </div>
175
177
  <div class="choice-field__label">
176
178
  <slot name="label"></slot>
177
179
  </div>
@@ -131,7 +131,7 @@ describe('<lion-input-amount>', () => {
131
131
  expect(_inputNode.value).to.equal('100.12');
132
132
  });
133
133
 
134
- it('adjusts formats with locale when formatOptions.mode is "user-edited"', async () => {
134
+ it('formats with locale when formatOptions.mode is "user-edited" and value has three decimal places', async () => {
135
135
  const el = /** @type {LionInputAmount} */ (
136
136
  await fixture(
137
137
  html`<lion-input-amount
@@ -154,6 +154,14 @@ describe('<lion-input-amount>', () => {
154
154
  expect(el.modelValue).to.equal(123456);
155
155
  expect(el.formattedValue).to.equal('123.456,00');
156
156
 
157
+ // When editing an already existing value, we interpet the separators as they are.
158
+ // However, only when the decimal places are 3 or more.
159
+ mimicUserInput(el, '123.456.00');
160
+ expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
161
+ expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
162
+ expect(el.modelValue).to.equal(123456);
163
+ expect(el.formattedValue).to.equal('123.456,00');
164
+
157
165
  // Formatting should only affect values that should be formatted / parsed as a consequence of user input.
158
166
  // When a user finished editing, the default should be restored.
159
167
  // (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
@@ -162,6 +170,45 @@ describe('<lion-input-amount>', () => {
162
170
  expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
163
171
  });
164
172
 
173
+ it('formats with heuristic when formatOptions.mode is "user-edited" and value has two decimal places', async () => {
174
+ const el = /** @type {LionInputAmount} */ (
175
+ await fixture(
176
+ html`<lion-input-amount
177
+ .modelValue=${64}
178
+ currency="EUR"
179
+ .formatOptions="${{ locale: 'en-GB' }}"
180
+ ></lion-input-amount>`,
181
+ )
182
+ );
183
+ const parserSpy = sinon.spy(el, 'parser');
184
+ const formatterSpy = sinon.spy(el, 'formatter');
185
+
186
+ expect(el.formattedValue).to.equal('64.00');
187
+ // @ts-expect-error [allow-protected] in test
188
+ expect(el._inputNode.value).to.equal('64.00');
189
+
190
+ // When editing an already existing value, we interpret the separators based on decimal places when there's 1 or
191
+ // less separator in total (otherwise we would accidentally multiply or divide by 1000)
192
+ mimicUserInput(el, '64,00');
193
+ expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
194
+ expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
195
+ expect(el.modelValue).to.equal(64);
196
+ expect(el.formattedValue).to.equal('64.00');
197
+
198
+ mimicUserInput(el, '64,0');
199
+ expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
200
+ expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
201
+ expect(el.modelValue).to.equal(64);
202
+ expect(el.formattedValue).to.equal('64.00');
203
+
204
+ // Formatting should only affect values that should be formatted / parsed as a consequence of user input.
205
+ // When a user finished editing, the default should be restored.
206
+ // (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
207
+ el.modelValue = 1234;
208
+ expect(el.formattedValue).to.equal('1,234.00');
209
+ expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
210
+ });
211
+
165
212
  it('sets inputmode attribute to decimal', async () => {
166
213
  const el = /** @type {LionInputAmount} */ (
167
214
  await fixture(`<lion-input-amount></lion-input-amount>`)
@@ -69,6 +69,6 @@ describe('parseAmount()', async () => {
69
69
  expect(parseAmount('123.456,78', { mode: 'auto' })).to.equal(123456.78);
70
70
  expect(
71
71
  parseAmount('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
72
- ).to.equal(123.45678);
72
+ ).to.equal(123456.78);
73
73
  });
74
74
  });
@@ -181,7 +181,7 @@ export class LionInputTelDropdown extends LionInputTel {
181
181
 
182
182
  return {
183
183
  template: templates.dropdown(this._templateDataDropdown),
184
- renderAsDirectHostChild: Boolean,
184
+ renderAsDirectHostChild: true,
185
185
  };
186
186
  },
187
187
  };
@@ -20,25 +20,34 @@ import { getDecimalSeparator } from './getDecimalSeparator.js';
20
20
  * @return {string} unparseable|withLocale|heuristic
21
21
  */
22
22
  function getParseMode(value, { mode = 'auto', viewValueStates } = {}) {
23
- const separators = value.match(/[., ]/g);
23
+ const separatorsRe = /[., ]/g;
24
+ const separators = value.match(separatorsRe) || [];
25
+ // @ts-expect-error [wait-for-platform-types]
26
+ const decimalPlaces = (value.split(separatorsRe) || []).at(-1).length;
27
+ const isCurViewFormattedAndUserIsEditing =
28
+ viewValueStates?.includes('formatted') && mode === 'user-edited';
24
29
 
25
30
  // When a user edits an existing value, we already formatted it with a certain locale.
26
- // For best UX, we stick with this locale
27
- const shouldAlignWithExistingSeparators =
28
- viewValueStates?.includes('formatted') && mode === 'user-edited';
31
+ // For best UX, we stick with this locale. However, we only do this when the user has
32
+ // entered at least 3 decimal places.
33
+ const shouldUseCurLocale = isCurViewFormattedAndUserIsEditing && decimalPlaces > 2;
29
34
 
30
- if (!separators || shouldAlignWithExistingSeparators) {
35
+ const shouldUseLocale =
36
+ shouldUseCurLocale ||
37
+ !separators.length ||
38
+ (mode === 'auto' && separators.length === 1 && decimalPlaces >= 3);
39
+ if (shouldUseLocale) {
31
40
  return 'withLocale';
32
41
  }
33
- if (mode === 'auto' && separators.length === 1) {
34
- const decimalLength = value.split(`${separators}`)[1].length;
35
- if (decimalLength >= 3) {
36
- return 'withLocale';
37
- }
38
- }
39
- if (separators.length === 1 || separators[0] !== separators[separators.length - 1]) {
42
+
43
+ const shouldUseHeuristic =
44
+ (isCurViewFormattedAndUserIsEditing && decimalPlaces <= 2) ||
45
+ separators.length === 1 ||
46
+ separators[0] !== separators[separators.length - 1];
47
+ if (shouldUseHeuristic) {
40
48
  return 'heuristic';
41
49
  }
50
+
42
51
  return 'unparseable';
43
52
  }
44
53
 
@@ -82,7 +82,7 @@ describe('parseNumber()', () => {
82
82
  expect(parseNumber('123.456,78', { mode: 'auto' })).to.equal(123456.78);
83
83
  expect(
84
84
  parseNumber('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
85
- ).to.equal(123.45678);
85
+ ).to.equal(123456.78);
86
86
  });
87
87
 
88
88
  it('detects separators unparseable when there are 2 same ones e.g. 1.234.56', () => {