@lion/ui 0.16.1 → 0.17.0-menu-system.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 (247) hide show
  1. package/collapsible.js +1 -0
  2. package/components/button/src/LionButton.js +5 -0
  3. package/components/button/test-suites/LionButton.suite.js +1 -0
  4. package/components/calendar/src/LionCalendar.js +2 -0
  5. package/components/collapsible/src/DisclosureMixin.js +313 -0
  6. package/components/collapsible/types/DisclosureMixinTypes.ts +48 -0
  7. package/components/combobox/src/utils/makeMatchingTextBold.js +3 -1
  8. package/components/combobox/test/lion-combobox.test.js +105 -0
  9. package/components/form-core/src/form-group/FormGroupMixin.js +7 -0
  10. package/components/form-core/src/registration/FormRegistrarMixin.js +11 -0
  11. package/components/form-core/test/FormControlMixin.test.js +1 -0
  12. package/components/form-core/test/validate/Validator.test.js +2 -0
  13. package/components/form-core/test-suites/ValidateMixin.suite.js +2 -0
  14. package/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +1 -0
  15. package/components/input-datepicker/src/LionInputDatepicker.js +6 -2
  16. package/components/input-datepicker/src/localizeNamespaceLoader.js +1 -0
  17. package/components/input-datepicker/test/lion-input-datepicker.test.js +17 -2
  18. package/components/input-datepicker/translations/bg.js +1 -1
  19. package/components/input-datepicker/translations/cs.js +1 -1
  20. package/components/input-datepicker/translations/de.js +1 -1
  21. package/components/input-datepicker/translations/en.js +1 -1
  22. package/components/input-datepicker/translations/es.js +1 -1
  23. package/components/input-datepicker/translations/fr.js +1 -1
  24. package/components/input-datepicker/translations/hu.js +1 -1
  25. package/components/input-datepicker/translations/it.js +1 -1
  26. package/components/input-datepicker/translations/nl.js +1 -1
  27. package/components/input-datepicker/translations/pl.js +1 -1
  28. package/components/input-datepicker/translations/ro.js +1 -1
  29. package/components/input-datepicker/translations/ru.js +1 -1
  30. package/components/input-datepicker/translations/sk.js +1 -1
  31. package/components/input-datepicker/translations/tr.js +1 -1
  32. package/components/input-datepicker/translations/uk.js +1 -1
  33. package/components/input-datepicker/translations/zh.js +1 -1
  34. package/components/input-stepper/src/LionInputStepper.js +15 -7
  35. package/components/input-stepper/test-suites/lion-input-stepper.suite.js +28 -4
  36. package/components/localize/test/date/formatDate.test.js +1 -0
  37. package/components/menu/CHANGELOG.md +37 -0
  38. package/components/menu/README.md +335 -0
  39. package/components/menu/index.js +2 -0
  40. package/components/menu/lion-menu-overlay.js +3 -0
  41. package/components/menu/lion-menu.js +3 -0
  42. package/components/menu/lion-spinbutton.js +3 -0
  43. package/components/menu/lion-toolbar.js +3 -0
  44. package/components/menu/lion-tree.js +3 -0
  45. package/components/menu/package.json +46 -0
  46. package/components/menu/requirements.temp.md +12 -0
  47. package/components/menu/src/InteractiveListMixin.js +796 -0
  48. package/components/menu/src/LionMenu.js +250 -0
  49. package/components/menu/src/LionMenuHybrid.js +46 -0
  50. package/components/menu/src/LionMenuOverlay.js +155 -0
  51. package/components/menu/src/LionSpinbutton.js +213 -0
  52. package/components/menu/src/LionToolbar.js +94 -0
  53. package/components/menu/src/LionTree.js +52 -0
  54. package/components/menu/src/MultiLevelListMixin.js +275 -0
  55. package/components/menu/src/utils/isInView.js +26 -0
  56. package/components/menu/src/utils/listItemInteractions.js +104 -0
  57. package/components/menu/src/utils/uuid.js +3 -0
  58. package/components/menu/test/lion-menu.test.js +3 -0
  59. package/components/menu/test-suites/InteractiveListMixin.suite.js +142 -0
  60. package/components/menu/types/InteractiveListMixinTypes.d.ts +82 -0
  61. package/components/navigation-bar/README.md +1 -0
  62. package/components/navigation-bar/src/LionNavigationBar.js +449 -0
  63. package/components/navigation-bar/src/types.js +41 -0
  64. package/components/overlays/NEXT.md +49 -0
  65. package/components/overlays/src/OverlayController.js +165 -115
  66. package/components/overlays/src/OverlayMixin.js +75 -12
  67. package/components/overlays/src/OverlaysManager.js +98 -0
  68. package/components/overlays/src/overlayShadowDomStyle.js +2 -2
  69. package/components/overlays/src/utils/adopt-styles.js +2 -1
  70. package/components/overlays/src/utils/get-focusable-elements.js +4 -0
  71. package/components/overlays/src/utils/is-equal-config.js +4 -1
  72. package/components/overlays/test/OverlayController.test.js +117 -6
  73. package/components/overlays/types/OverlayConfig.ts +19 -4
  74. package/components/select-rich/test/lion-select-rich.test.js +3 -0
  75. package/custom-elements.json +15785 -5337
  76. package/dist-types/components/button/src/LionButton.d.ts +1 -0
  77. package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
  78. package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
  79. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  80. package/dist-types/components/collapsible/src/DisclosureMixin.d.ts +3 -0
  81. package/dist-types/components/collapsible/src/DisclosureMixin.d.ts.map +1 -0
  82. package/dist-types/components/collapsible/types/DisclosureMixinTypes.d.ts +24 -0
  83. package/dist-types/components/collapsible/types/DisclosureMixinTypes.d.ts.map +1 -0
  84. package/dist-types/components/combobox/src/utils/makeMatchingTextBold.d.ts.map +1 -1
  85. package/dist-types/components/dialog/src/LionDialog.d.ts +2 -1
  86. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  87. package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts.map +1 -1
  88. package/dist-types/components/form-core/src/registration/FormRegistrarMixin.d.ts.map +1 -1
  89. package/dist-types/components/form-core/test-suites/ValidateMixin.suite.d.ts.map +1 -1
  90. package/dist-types/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.d.ts.map +1 -1
  91. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  92. package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts.map +1 -1
  93. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +4 -2
  94. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
  95. package/dist-types/components/input-stepper/test-suites/lion-input-stepper.suite.d.ts.map +1 -1
  96. package/dist-types/components/menu/index.d.ts +3 -0
  97. package/dist-types/components/menu/index.d.ts.map +1 -0
  98. package/dist-types/components/menu/lion-menu-overlay.d.ts +2 -0
  99. package/dist-types/components/menu/lion-menu-overlay.d.ts.map +1 -0
  100. package/dist-types/components/menu/lion-menu.d.ts +2 -0
  101. package/dist-types/components/menu/lion-menu.d.ts.map +1 -0
  102. package/dist-types/components/menu/lion-spinbutton.d.ts +2 -0
  103. package/dist-types/components/menu/lion-spinbutton.d.ts.map +1 -0
  104. package/dist-types/components/menu/lion-toolbar.d.ts +2 -0
  105. package/dist-types/components/menu/lion-toolbar.d.ts.map +1 -0
  106. package/dist-types/components/menu/lion-tree.d.ts +2 -0
  107. package/dist-types/components/menu/lion-tree.d.ts.map +1 -0
  108. package/dist-types/components/menu/src/InteractiveListMixin.d.ts +611 -0
  109. package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +1 -0
  110. package/dist-types/components/menu/src/LionMenu.d.ts +563 -0
  111. package/dist-types/components/menu/src/LionMenu.d.ts.map +1 -0
  112. package/dist-types/components/menu/src/LionMenuHybrid.d.ts +19 -0
  113. package/dist-types/components/menu/src/LionMenuHybrid.d.ts.map +1 -0
  114. package/dist-types/components/menu/src/LionMenuOverlay.d.ts +1006 -0
  115. package/dist-types/components/menu/src/LionMenuOverlay.d.ts.map +1 -0
  116. package/dist-types/components/menu/src/LionSpinbutton.d.ts +58 -0
  117. package/dist-types/components/menu/src/LionSpinbutton.d.ts.map +1 -0
  118. package/dist-types/components/menu/src/LionToolbar.d.ts +8 -0
  119. package/dist-types/components/menu/src/LionToolbar.d.ts.map +1 -0
  120. package/dist-types/components/menu/src/LionTree.d.ts +445 -0
  121. package/dist-types/components/menu/src/LionTree.d.ts.map +1 -0
  122. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts +493 -0
  123. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts.map +1 -0
  124. package/dist-types/components/menu/src/utils/isInView.d.ts +9 -0
  125. package/dist-types/components/menu/src/utils/isInView.d.ts.map +1 -0
  126. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +42 -0
  127. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +1 -0
  128. package/dist-types/components/menu/src/utils/uuid.d.ts +2 -0
  129. package/dist-types/components/menu/src/utils/uuid.d.ts.map +1 -0
  130. package/dist-types/components/menu/test/lion-menu.test.d.ts +2 -0
  131. package/dist-types/components/menu/test/lion-menu.test.d.ts.map +1 -0
  132. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +13 -0
  133. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -0
  134. package/dist-types/components/navigation-bar/src/LionNavigationBar.d.ts +226 -0
  135. package/dist-types/components/navigation-bar/src/LionNavigationBar.d.ts.map +1 -0
  136. package/dist-types/components/navigation-bar/src/types.d.ts +85 -0
  137. package/dist-types/components/navigation-bar/src/types.d.ts.map +1 -0
  138. package/dist-types/components/overlays/src/OverlayController.d.ts +7 -13
  139. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  140. package/dist-types/components/overlays/src/OverlayMixin.d.ts +3 -0
  141. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  142. package/dist-types/components/overlays/src/OverlaysManager.d.ts +31 -0
  143. package/dist-types/components/overlays/src/OverlaysManager.d.ts.map +1 -1
  144. package/dist-types/components/overlays/src/utils/get-focusable-elements.d.ts.map +1 -1
  145. package/dist-types/components/overlays/src/utils/is-equal-config.d.ts.map +1 -1
  146. package/dist-types/components/overlays/types/OverlayConfig.d.ts +6 -2
  147. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  148. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +11 -2
  149. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  150. package/dist-types/exports/define/lion-item.d.ts +2 -0
  151. package/dist-types/exports/define/lion-item.d.ts.map +1 -0
  152. package/dist-types/exports/define/lion-menu-overlay.d.ts +2 -0
  153. package/dist-types/exports/define/lion-menu-overlay.d.ts.map +1 -0
  154. package/dist-types/exports/define/lion-menu.d.ts +2 -0
  155. package/dist-types/exports/define/lion-menu.d.ts.map +1 -0
  156. package/dist-types/exports/define/lion-navigation-bar.d.ts +2 -0
  157. package/dist-types/exports/define/lion-navigation-bar.d.ts.map +1 -0
  158. package/dist-types/exports/define/lion-toolbar.d.ts +2 -0
  159. package/dist-types/exports/define/lion-toolbar.d.ts.map +1 -0
  160. package/dist-types/exports/define/lion-tree.d.ts +2 -0
  161. package/dist-types/exports/define/lion-tree.d.ts.map +1 -0
  162. package/dist-types/exports/menu.d.ts +7 -0
  163. package/dist-types/exports/menu.d.ts.map +1 -0
  164. package/dist-types/exports/navigation-bar.d.ts +2 -0
  165. package/dist-types/exports/navigation-bar.d.ts.map +1 -0
  166. package/dist-types/exports/overlays.d.ts +2 -0
  167. package/dist-types/exports/types/overlays.d.ts +1 -0
  168. package/dist-types/exports/types/overlays.d.ts.map +1 -1
  169. package/dist-types/tsconfig.tsbuildinfo +1 -1
  170. package/docs/components/checkbox-group/overview.md +2 -2
  171. package/docs/components/collapsible/overview.md +1 -1
  172. package/docs/components/combobox/overview.md +1 -1
  173. package/docs/components/combobox/use-cases.md +1 -1
  174. package/docs/components/dialog/overview.md +1 -1
  175. package/docs/components/fieldset/overview.md +5 -5
  176. package/docs/components/form/overview.md +3 -3
  177. package/docs/components/form/use-cases.md +2 -2
  178. package/docs/components/icon/use-cases.md +1 -1
  179. package/docs/components/index.md +1 -1
  180. package/docs/components/input/overview.md +1 -1
  181. package/docs/components/input-amount/overview.md +3 -3
  182. package/docs/components/input-date/overview.md +3 -3
  183. package/docs/components/input-datepicker/overview.md +5 -5
  184. package/docs/components/input-datepicker/use-cases.md +16 -1
  185. package/docs/components/input-email/overview.md +2 -2
  186. package/docs/components/input-file/overview.md +1 -1
  187. package/docs/components/input-iban/overview.md +2 -2
  188. package/docs/components/input-range/api-table.md +1 -1
  189. package/docs/components/input-range/overview.md +2 -2
  190. package/docs/components/input-stepper/api-table.md +2 -2
  191. package/docs/components/input-stepper/overview.md +1 -1
  192. package/docs/components/input-stepper/use-cases.md +26 -0
  193. package/docs/components/input-tel/overview.md +1 -1
  194. package/docs/components/input-tel/use-cases.md +6 -6
  195. package/docs/components/input-tel-dropdown/overview.md +1 -1
  196. package/docs/components/listbox/use-cases.md +1 -1
  197. package/docs/components/menu/api-table.md +450 -0
  198. package/docs/components/menu/applyMenuDemoStyles.js +173 -0
  199. package/docs/components/menu/extensions.md +216 -0
  200. package/docs/components/menu/index.md +13 -0
  201. package/docs/components/menu/overview.md +422 -0
  202. package/docs/components/radio-group/overview.md +3 -3
  203. package/docs/components/select/use-cases.md +1 -1
  204. package/docs/components/select-rich/api-table.md +1 -1
  205. package/docs/components/select-rich/use-cases.md +2 -2
  206. package/docs/components/tabs/examples.md +1 -1
  207. package/docs/fundamentals/index.md +4 -4
  208. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  209. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  210. package/docs/fundamentals/rationales/accessibility.md +1 -1
  211. package/docs/fundamentals/systems/core/SlotMixin.md +18 -18
  212. package/docs/fundamentals/systems/core/overview.md +1 -1
  213. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  214. package/docs/fundamentals/systems/form/interaction-states.md +0 -1
  215. package/docs/fundamentals/systems/form/model-value.md +1 -1
  216. package/docs/fundamentals/systems/form/overview.md +28 -28
  217. package/docs/fundamentals/systems/form/validate.md +2 -2
  218. package/docs/fundamentals/systems/localize/overview.md +4 -4
  219. package/docs/fundamentals/systems/localize/text.md +1 -1
  220. package/docs/fundamentals/systems/overlays/api-table.md +92 -45
  221. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  222. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  223. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  224. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  225. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  226. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  227. package/docs/guides/how-to/create-a-form.md +4 -4
  228. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  229. package/docs/guides/how-to/index.md +14 -0
  230. package/docs/guides/index.md +8 -8
  231. package/docs/guides/principles/index.md +5 -5
  232. package/docs/guides/principles/styling.md +1 -2
  233. package/exports/collapsible.js +1 -0
  234. package/exports/define/lion-item.js +3 -0
  235. package/exports/define/lion-menu-overlay.js +3 -0
  236. package/exports/define/lion-menu.js +3 -0
  237. package/exports/define/lion-navigation-bar.js +3 -0
  238. package/exports/define/lion-toolbar.js +3 -0
  239. package/exports/define/lion-tree.js +3 -0
  240. package/exports/menu.js +6 -0
  241. package/exports/navigation-bar.js +1 -0
  242. package/exports/overlays.js +2 -0
  243. package/exports/types/overlays.ts +1 -0
  244. package/menu.js +6 -0
  245. package/navigation-bar.js +1 -0
  246. package/overlays.js +2 -0
  247. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ import { LionMenu } from './LionMenu.js';
3
+ import { setChecked, toggleChecked } from './utils/listItemInteractions.js';
4
+
5
+ /**
6
+ * @typedef {import('../types/InteractiveListMixinTypes.js').InteractiveListItemRole} InteractiveListItemRole
7
+ */
8
+
9
+ export class LionToolbar extends LionMenu {
10
+ constructor() {
11
+ super();
12
+
13
+ this._listRole = 'toolbar';
14
+ this.bar = true;
15
+ }
16
+
17
+ /**
18
+ * Allows groups ([role=radiogroup|checkboxgroup]) within one level.
19
+ * In case we deal with [role=checkbox], we treat it as multiple choice group
20
+ * @override InteractiveListMixin
21
+ * @param {number} index
22
+ */
23
+ setCheckedIndex(index) {
24
+ const item = this.listItems[index];
25
+ if (item) {
26
+ // @ts-ignore - InteractiveListItemRole type
27
+ const role = /** @type {InteractiveListItemRole} */ (item.getAttribute('role'));
28
+ let listItemsWithinGroup = this.listItems;
29
+ let multiple = this.multipleChoice;
30
+ if (role === 'radio' || role === 'checkbox') {
31
+ /**
32
+ * If index = 1 (radio 'Red'), closest group will be div[role=radiogroup]
33
+ * @example
34
+ * <lion-toolbar>
35
+ * <div role="radiogroup" aria-label="Text Color">
36
+ * <button role="radio" aria-checked="false">Blue</button>
37
+ * <button role="radio" aria-checked="true">Red</button>
38
+ * <button role="radio" aria-checked="false">Green</button>
39
+ * </div>
40
+ * <div role="separator"></div>
41
+ * <div role="checkboxgroup" aria-label="Text Color">
42
+ * <button role="checkbox" aria-checked="false">Blue</button>
43
+ * <button role="checkbox" aria-checked="true">Red</button>
44
+ * <button role="checkbox" aria-checked="false">Green</button>
45
+ * </div>
46
+ * </lion-toolbar>
47
+ */
48
+ let closestGroup;
49
+ if (role === 'radio') {
50
+ closestGroup = item.closest('[role="radiogroup"]');
51
+ } else if (role === 'checkbox') {
52
+ closestGroup = item.closest('[role="checkboxgroup"]');
53
+ }
54
+
55
+ const group = closestGroup && this.contains(closestGroup) ? closestGroup : this;
56
+ listItemsWithinGroup = this.listItems.filter(listItem => group.contains(listItem));
57
+ multiple = role === 'checkbox';
58
+ }
59
+
60
+ if (!multiple) {
61
+ // Uncheck all
62
+ listItemsWithinGroup.forEach(listItem => {
63
+ setChecked(listItem, false);
64
+ });
65
+ setChecked(this.listItems[index]);
66
+ } else {
67
+ toggleChecked(this.listItems[index]);
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @param {*} newItems
74
+ */
75
+ _initListItems(newItems) {
76
+ super._initListItems(newItems);
77
+
78
+ // @ts-ignore - forEach parameter
79
+ newItems.forEach(item => {
80
+ if (
81
+ item.getAttribute('role') === 'button' ||
82
+ (item.tagName === 'BUTTON' && !item.hasAttribute('role'))
83
+ ) {
84
+ const initialAriaPressed = item.getAttribute('aria-pressed');
85
+ item.setAttribute('aria-pressed', initialAriaPressed || 'false');
86
+
87
+ item.addEventListener('click', () => {
88
+ const ariaPressed = item.getAttribute('aria-pressed') === 'true';
89
+ item.setAttribute('aria-pressed', `${!ariaPressed}`);
90
+ });
91
+ }
92
+ });
93
+ }
94
+ }
@@ -0,0 +1,52 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ import { LitElement } from 'lit';
3
+ import { MultiLevelListMixin } from './MultiLevelListMixin.js';
4
+
5
+ // @ts-ignore - class extension with mixin
6
+ export class LionTree extends MultiLevelListMixin(LitElement) {
7
+ constructor() {
8
+ super();
9
+
10
+ /** @configure InteractiveListMixin */
11
+ this._listRole = 'tree';
12
+ /** @configure InteractiveListMixin */
13
+ this._activateOnTypedChars = true;
14
+ /** @configure DisclosureMixin */
15
+ this.invokerInteraction = 'click';
16
+ }
17
+
18
+ /**
19
+ * @param {*} changedProperties
20
+ */
21
+ updated(changedProperties) {
22
+ super.updated(changedProperties);
23
+
24
+ if (changedProperties.has('level')) {
25
+ if (this.level > 0) {
26
+ this._listNode.setAttribute('role', 'group');
27
+ }
28
+ this.__setAriaLevelForListItems();
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @param {*} newItems
34
+ */
35
+ _initListItems(newItems) {
36
+ // @ts-ignore - parameter types
37
+ super._initListItems(newItems);
38
+
39
+ this.__setAriaLevelForListItems();
40
+ // @ts-ignore - forEach parameters
41
+ newItems.forEach((item, index) => {
42
+ item.setAttribute('aria-posinset', `${index + 1}`);
43
+ item.setAttribute('aria-setsize', `${this.listItems.length}`);
44
+ });
45
+ }
46
+
47
+ __setAriaLevelForListItems() {
48
+ this.listItems.forEach(item => {
49
+ item.setAttribute?.('aria-level', `${this.level + 1}`);
50
+ });
51
+ }
52
+ }
@@ -0,0 +1,275 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ import { html } from 'lit';
3
+ import { dedupeMixin } from '@open-wc/dedupe-mixin';
4
+ // import { DisclosureMixin } from '@lion/ui/collapsible.js';
5
+ import { InteractiveListMixin } from './InteractiveListMixin.js';
6
+
7
+ /**
8
+ * @typedef {import('./InteractiveListMixin.js').LionItem} LionItem
9
+ * @typedef {import('../types/InteractiveListMixinTypes.js').InteractiveListHost} InteractiveList
10
+ */
11
+
12
+ /**
13
+ * All logic that is needed for interactive lists that are allowed to have multiple nested, collapsible levels
14
+ * Applies to [role=menu] and [role=tree]
15
+ */
16
+ // @ts-ignore - mixin implementation
17
+ const MultiLevelListMixinImplementation = superclass =>
18
+ // @ts-ignore - mixin class extension
19
+ class extends InteractiveListMixin(superclass) {
20
+ render() {
21
+ return html`
22
+ <slot name="invoker"></slot>
23
+ <slot name="list"></slot>
24
+ <slot id="list-items-outlet"></slot>
25
+ `;
26
+ }
27
+
28
+ static get properties() {
29
+ return {
30
+ invokerNode: Object,
31
+ behaveAsAccordion: { type: Boolean, attribute: 'behave-as-accordion' },
32
+ level: { type: Number, reflect: true },
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @configure DisclosureMixin
38
+ */
39
+ get _invokerNode() {
40
+ // @ts-ignore - accessing static method and super property
41
+ return this.constructor._getFocusableInvokerEl(this.invokerNode) || super._invokerNode;
42
+ }
43
+
44
+ /**
45
+ * @configure DisclosureMixin
46
+ * This will be compatible with DisclosureMixin and OverlayMixin. It contains the invoked
47
+ * InteractiveList widget (like menu|tree).
48
+ */
49
+ get _contentNode() {
50
+ return this._listNode;
51
+ }
52
+
53
+ constructor() {
54
+ super();
55
+
56
+ /**
57
+ * When an invokerNode is supplied (usually by a parent level menu), it will take precedence
58
+ * over [slot=invoker] and previousElementSibling with [data-invoker] attribute
59
+ * @type {HTMLElement}
60
+ */
61
+ // @ts-ignore - can be undefined initially
62
+ this.invokerNode = undefined;
63
+ /**
64
+ * @configure DisclosureMixin
65
+ */
66
+ this.handleFocus = true;
67
+ /**
68
+ * The parent list if level > 1
69
+ * @type {InteractiveList}
70
+ */
71
+ // @ts-ignore - can be undefined initially
72
+ this.parentList = undefined;
73
+ /**
74
+ * When true, will have a maximum of one submenu open at a time
75
+ */
76
+ this.behaveAsAccordion = false;
77
+ /**
78
+ * The level of nested menus. Will be reflected as attribute for styling purposes
79
+ */
80
+ this.level = 1;
81
+ /**
82
+ * @configure InteractiveListMixin
83
+ */
84
+ this.noPreselect = true;
85
+ /**
86
+ * @configure InteractiveListMixin
87
+ */
88
+ this._activeMode = 'roving-tabindex';
89
+ /**
90
+ * @type {Map<HTMLElement|LionItem,InteractiveList>}
91
+ */
92
+ this._subListMap = new Map();
93
+ }
94
+
95
+ // /**
96
+ // * @param {import('lit-element').PropertyValues } changedProperties
97
+ // */
98
+ // updated(changedProperties) {
99
+ // super.updated(changedProperties);
100
+
101
+ // if (changedProperties.has('invokerNode')) {
102
+ // // TODO: update openableCtrl invokerNode once we make this a ctrl as well
103
+ // }
104
+ // }
105
+
106
+ /**
107
+ * Gets the child InteractiveList, based on item
108
+ * @param {LionItem|HTMLElement} item can be sibling of or parent of InteractiveList
109
+ */
110
+ static _getSubInteractiveList(item) {
111
+ /**
112
+ * Note that, according to W3C spec, the sub level element with [role=menu|menubar|tree]
113
+ * needs to be a sibling of the invoker element with
114
+ * [role=menuitem|menuitemchecbox|menuitemradio|treeitem] (and not the parent)
115
+ *
116
+ * In the example below <lion-menu> is considered as item
117
+ * @example
118
+ * <lion-menuitem>
119
+ * <button slot="invoker"></button>
120
+ * <lion-menu> ... </lion-menu>
121
+ * </lion-menuitem>
122
+ */
123
+ // @ts-ignore - isInteractiveList property check
124
+ const siblingOfInvoker = item.nextElementSibling?.isInteractiveList;
125
+ if (siblingOfInvoker) {
126
+ return item.nextElementSibling;
127
+ }
128
+ /**
129
+ * In the example below div[role=listitem] | [data-item] is considered as item
130
+ * @example
131
+ * <div role="listitem" data-item>
132
+ * <button slot="invoker"></button>
133
+ * <lion-menu> ... </lion-menu>
134
+ * </div>
135
+ */
136
+ const childOfInvoker =
137
+ item.getAttribute?.('role') === 'listitem' || item.hasAttribute?.('data-item');
138
+ if (childOfInvoker) {
139
+ // @ts-ignore - isInteractiveList check
140
+ return Array.from(item.children).find(child => child.isInteractiveList);
141
+ }
142
+ return undefined;
143
+ }
144
+
145
+ /**
146
+ * @param {*} newItems
147
+ */
148
+ _initListItems(newItems) {
149
+ super._initListItems(newItems);
150
+
151
+ const ctor = this.constructor;
152
+ // @ts-ignore - forEach parameter types
153
+ newItems.forEach(item => {
154
+ // @ts-ignore - static method access
155
+ const subList = ctor._getSubInteractiveList(item);
156
+ if (subList) {
157
+ this._subListMap.set(item, subList);
158
+ subList.level = this.level + 1;
159
+ subList.parentList = this;
160
+ subList.invokerNode = item;
161
+ // @ts-ignore - invokerInteraction property
162
+ subList.invokerInteraction = this.invokerInteraction;
163
+ subList._activeMode = this._activeMode;
164
+ }
165
+ });
166
+ }
167
+
168
+ /**
169
+ * @enhance DisclosureMixin
170
+ * @param {Event} [ev]
171
+ */
172
+ toggle(ev) {
173
+ // @ts-ignore - target property
174
+ if (ev && this._listNode.contains(ev.target)) {
175
+ // prevent nested menus (inside invokers) from triggering invoker
176
+ return;
177
+ }
178
+ // @ts-ignore - super.toggle method
179
+ super.toggle();
180
+ }
181
+
182
+ /**
183
+ * @enhance InteractiveListMixin
184
+ * @param {*} ev
185
+ */
186
+ _onListKeyDown(ev) {
187
+ const targetFromsubList = Array.from(this._subListMap).find(
188
+ // @ts-ignore - contains method on InteractiveListHost
189
+ ([, s]) => s === ev.target || s.contains(ev.target),
190
+ );
191
+
192
+ if (targetFromsubList) {
193
+ return;
194
+ }
195
+
196
+ super._onListKeyDown(ev);
197
+
198
+ const subListOfActiveItem = this._subListMap.get(this.activeItem);
199
+ const parentListOfActiveItem = this.parentList;
200
+ const { key } = ev;
201
+
202
+ if (key === 'Escape' && parentListOfActiveItem) {
203
+ // We need to stop here, or else we affect parent menu (handled by OverlayController)
204
+ ev.stopPropagation();
205
+ }
206
+
207
+ switch (key) {
208
+ case 'Enter':
209
+ case ' ':
210
+ // console.debug('activeIndex', this.activeIndex);
211
+ // make it work like a button
212
+ this.activeItem?.click();
213
+ break;
214
+ case 'ArrowDown':
215
+ if (
216
+ this.orientation === 'horizontal' &&
217
+ subListOfActiveItem &&
218
+ this._activeMode !== 'disclosure'
219
+ ) {
220
+ this.activeItem.click();
221
+ }
222
+ break;
223
+ case 'ArrowRight':
224
+ if (this.orientation === 'vertical' && subListOfActiveItem) {
225
+ this.activeItem.click();
226
+ }
227
+ break;
228
+ case 'ArrowUp':
229
+ if (this.orientation === 'horizontal' && parentListOfActiveItem) {
230
+ // @ts-ignore - close method
231
+ this.close();
232
+ // @ts-ignore - _onOverlayShow method
233
+ parentListOfActiveItem._onOverlayShow();
234
+ }
235
+ break;
236
+ case 'ArrowLeft':
237
+ if (this.orientation === 'vertical' && parentListOfActiveItem) {
238
+ // @ts-ignore - close method
239
+ this.close();
240
+ // @ts-ignore - _onOverlayShow method
241
+ parentListOfActiveItem._onOverlayShow();
242
+ }
243
+ break;
244
+ /* no default */
245
+ }
246
+ }
247
+
248
+ // /**
249
+ // * @enhance DisclosureMixin
250
+ // */
251
+ // _setupDisclosure() {
252
+ // if (!this._invokerNode) return;
253
+
254
+ // super._setupDisclosure();
255
+ // }
256
+
257
+ // /**
258
+ // * @enhance DisclosureMixin
259
+ // */
260
+ // _teardownDisclosure() {
261
+ // if (!this._invokerNode) return;
262
+
263
+ // super._teardownDisclosure();
264
+ // }
265
+
266
+ // /**
267
+ // * @enhance DisclosureMixin
268
+ // */
269
+ // async _onOpenedChanged() {
270
+ // if (!this._invokerNode) return;
271
+
272
+ // await super._onOpenedChanged();
273
+ // }
274
+ };
275
+ export const MultiLevelListMixin = dedupeMixin(MultiLevelListMixinImplementation);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Determines whether element is visible within container
3
+ *
4
+ * @param {HTMLElement} container
5
+ * @param {HTMLElement} element
6
+ * @param {Boolean} [partial]
7
+ */
8
+ export function isInView(container, element, partial = false) {
9
+ const cTop = container.scrollTop;
10
+ const cBottom = cTop + container.clientHeight;
11
+ const eTop = element.offsetTop;
12
+ const eBottom = eTop + element.clientHeight;
13
+ const isTotal = eTop >= cTop && eBottom <= cBottom;
14
+ let isPartial;
15
+
16
+ if (partial === true) {
17
+ isPartial = (eTop < cTop && eBottom > cTop) || (eBottom > cBottom && eTop < cBottom);
18
+ } else if (typeof partial === 'number') {
19
+ if (eTop < cTop && eBottom > cTop) {
20
+ isPartial = ((eBottom - cTop) * 100) / element.clientHeight > partial;
21
+ } else if (eBottom > cBottom && eTop < cBottom) {
22
+ isPartial = ((cBottom - eTop) * 100) / element.clientHeight > partial;
23
+ }
24
+ }
25
+ return isTotal || isPartial;
26
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Set of universal interactive list item state methods that are both compatible with
3
+ * 'smart' (LionOption) and 'regular' items
4
+ */
5
+
6
+ /**
7
+ * @param {HTMLElement} item
8
+ */
9
+ export function isDisabled(item) {
10
+ return item.hasAttribute('disabled') || item.getAttribute('aria-disabled') === 'true';
11
+ }
12
+
13
+ /**
14
+ * @param {HTMLElement} item
15
+ */
16
+ export function setDisabled(item, unset = false) {
17
+ if (unset) {
18
+ item.removeAttribute('disabled');
19
+ item.setAttribute('aria-disabled', 'false');
20
+ } else {
21
+ item.setAttribute('disabled', '');
22
+ item.setAttribute('aria-disabled', 'true');
23
+ }
24
+ }
25
+
26
+ /**
27
+ * @param {HTMLElement} item
28
+ */
29
+ export function toggleDisabled(item) {
30
+ setDisabled(item, isDisabled(item));
31
+ }
32
+
33
+ /**
34
+ * @param {HTMLElement} item
35
+ */
36
+ export function isChecked(item) {
37
+ return item.hasAttribute('checked') || item.getAttribute('aria-selected') === 'true';
38
+ }
39
+
40
+ /**
41
+ * @param {HTMLElement} item
42
+ * @param {boolean} [shouldSet=false]
43
+ */
44
+ export function setChecked(item, shouldSet = true) {
45
+ const ariaAttr = item.getAttribute('role') === 'option' ? 'selected' : 'checked';
46
+ if (shouldSet) {
47
+ item.setAttribute('checked', '');
48
+ item.setAttribute(`aria-${ariaAttr}`, 'true');
49
+ } else {
50
+ item.removeAttribute('checked');
51
+ item.setAttribute(`aria-${ariaAttr}`, 'false');
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @param {HTMLElement} item
57
+ */
58
+ export function toggleChecked(item) {
59
+ setChecked(item, !isChecked(item));
60
+ }
61
+
62
+ /**
63
+ * @param {HTMLElement} item
64
+ */
65
+ export function isActive(item) {
66
+ return item.hasAttribute('active');
67
+ }
68
+
69
+ /**
70
+ * @param {HTMLElement} item
71
+ */
72
+ export function setActive(item, unset = false) {
73
+ if (unset) {
74
+ item.removeAttribute('active');
75
+ } else {
76
+ item.setAttribute('active', '');
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {HTMLElement} item
82
+ */
83
+ function getDirectTextContent(item) {
84
+ return Array.from(item.childNodes)
85
+ .filter(n => n.nodeName === '#text')
86
+ .map(n => n.textContent)
87
+ .join('')
88
+ .trim();
89
+ }
90
+
91
+ /**
92
+ * @param {HTMLElement} item
93
+ */
94
+ export function getValue(item) {
95
+ return (
96
+ // @ts-ignore - choiceValue is a custom property
97
+ item.choiceValue ||
98
+ // @ts-ignore - value might not exist on all HTMLElements
99
+ item.value ||
100
+ item.getAttribute('value') ||
101
+ item.getAttribute('data-value') ||
102
+ getDirectTextContent(item)
103
+ );
104
+ }
@@ -0,0 +1,3 @@
1
+ export function uuid() {
2
+ return Math.random().toString(36).substr(2, 10);
3
+ }
@@ -0,0 +1,3 @@
1
+ import { runInteractiveListMixinSuite } from '../test-suites/InteractiveListMixin.suite.js';
2
+
3
+ runInteractiveListMixinSuite();