igniteui-webcomponents 7.3.0-beta.0 → 7.3.0-beta.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 (293) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/animations/player.d.ts +7 -2
  3. package/animations/player.js +1 -2
  4. package/animations/player.js.map +1 -1
  5. package/components/accordion/accordion.js +4 -16
  6. package/components/accordion/accordion.js.map +1 -1
  7. package/components/avatar/avatar.d.ts +0 -1
  8. package/components/avatar/avatar.js +5 -9
  9. package/components/avatar/avatar.js.map +1 -1
  10. package/components/badge/badge.d.ts +1 -3
  11. package/components/badge/badge.js +4 -8
  12. package/components/badge/badge.js.map +1 -1
  13. package/components/banner/banner.d.ts +6 -5
  14. package/components/banner/banner.js +19 -17
  15. package/components/banner/banner.js.map +1 -1
  16. package/components/button/button-base.d.ts +1 -1
  17. package/components/button/button-base.js.map +1 -1
  18. package/components/button-group/button-group.d.ts +0 -1
  19. package/components/button-group/button-group.js +12 -17
  20. package/components/button-group/button-group.js.map +1 -1
  21. package/components/calendar/base.d.ts +3 -13
  22. package/components/calendar/base.js +10 -31
  23. package/components/calendar/base.js.map +1 -1
  24. package/components/calendar/calendar.d.ts +2 -2
  25. package/components/calendar/months-view/months-view.d.ts +1 -1
  26. package/components/calendar/months-view/months-view.js.map +1 -1
  27. package/components/carousel/carousel-indicator.d.ts +1 -1
  28. package/components/carousel/carousel-indicator.js +13 -13
  29. package/components/carousel/carousel-indicator.js.map +1 -1
  30. package/components/carousel/carousel-slide.d.ts +0 -2
  31. package/components/carousel/carousel-slide.js +7 -9
  32. package/components/carousel/carousel-slide.js.map +1 -1
  33. package/components/carousel/carousel.d.ts +52 -35
  34. package/components/carousel/carousel.js +136 -122
  35. package/components/carousel/carousel.js.map +1 -1
  36. package/components/carousel/themes/carousel.base.css.js +1 -1
  37. package/components/carousel/themes/carousel.base.css.js.map +1 -1
  38. package/components/chat/chat-input.d.ts +1 -1
  39. package/components/chat/chat-input.js +21 -21
  40. package/components/chat/chat-input.js.map +1 -1
  41. package/components/chat/chat.d.ts +0 -2
  42. package/components/chat/chat.js +6 -12
  43. package/components/chat/chat.js.map +1 -1
  44. package/components/checkbox/checkbox-base.d.ts +1 -1
  45. package/components/checkbox/checkbox.d.ts +3 -5
  46. package/components/checkbox/checkbox.js +33 -44
  47. package/components/checkbox/checkbox.js.map +1 -1
  48. package/components/checkbox/switch.d.ts +1 -1
  49. package/components/checkbox/switch.js +23 -34
  50. package/components/checkbox/switch.js.map +1 -1
  51. package/components/chip/chip.d.ts +1 -14
  52. package/components/chip/chip.js +12 -33
  53. package/components/chip/chip.js.map +1 -1
  54. package/components/combo/combo-item.d.ts +1 -3
  55. package/components/combo/combo-item.js +5 -12
  56. package/components/combo/combo-item.js.map +1 -1
  57. package/components/combo/combo.d.ts +7 -20
  58. package/components/combo/combo.js +59 -81
  59. package/components/combo/combo.js.map +1 -1
  60. package/components/date-picker/date-picker.base.d.ts +12 -9
  61. package/components/date-picker/date-picker.base.js +24 -31
  62. package/components/date-picker/date-picker.base.js.map +1 -1
  63. package/components/date-picker/date-picker.d.ts +1 -1
  64. package/components/date-picker/date-picker.js +3 -5
  65. package/components/date-picker/date-picker.js.map +1 -1
  66. package/components/date-range-picker/date-range-input.d.ts +2 -4
  67. package/components/date-range-picker/date-range-input.js +6 -37
  68. package/components/date-range-picker/date-range-input.js.map +1 -1
  69. package/components/date-range-picker/date-range-picker.d.ts +2 -5
  70. package/components/date-range-picker/date-range-picker.js +15 -28
  71. package/components/date-range-picker/date-range-picker.js.map +1 -1
  72. package/components/date-range-picker/predefined-ranges-area.d.ts +2 -2
  73. package/components/date-range-picker/predefined-ranges-area.js +4 -6
  74. package/components/date-range-picker/predefined-ranges-area.js.map +1 -1
  75. package/components/date-range-picker/validators.d.ts +1 -1
  76. package/components/date-range-picker/validators.js +5 -18
  77. package/components/date-range-picker/validators.js.map +1 -1
  78. package/components/date-time-input/date-part.d.ts +2 -1
  79. package/components/date-time-input/date-part.js +38 -75
  80. package/components/date-time-input/date-part.js.map +1 -1
  81. package/components/date-time-input/date-time-input.base.d.ts +31 -12
  82. package/components/date-time-input/date-time-input.base.js +43 -11
  83. package/components/date-time-input/date-time-input.base.js.map +1 -1
  84. package/components/date-time-input/date-time-input.d.ts +1 -3
  85. package/components/date-time-input/date-time-input.js +7 -40
  86. package/components/date-time-input/date-time-input.js.map +1 -1
  87. package/components/date-time-input/datetime-mask-parser.d.ts +8 -0
  88. package/components/date-time-input/datetime-mask-parser.js +8 -8
  89. package/components/date-time-input/datetime-mask-parser.js.map +1 -1
  90. package/components/date-time-input/validators.js +3 -14
  91. package/components/date-time-input/validators.js.map +1 -1
  92. package/components/dialog/dialog.d.ts +6 -7
  93. package/components/dialog/dialog.js +21 -26
  94. package/components/dialog/dialog.js.map +1 -1
  95. package/components/divider/divider.d.ts +1 -4
  96. package/components/divider/divider.js +8 -17
  97. package/components/divider/divider.js.map +1 -1
  98. package/components/dropdown/dropdown-group.js +1 -6
  99. package/components/dropdown/dropdown-group.js.map +1 -1
  100. package/components/expansion-panel/expansion-panel.d.ts +15 -5
  101. package/components/expansion-panel/expansion-panel.js +23 -34
  102. package/components/expansion-panel/expansion-panel.js.map +1 -1
  103. package/components/file-input/file-input.d.ts +2 -14
  104. package/components/file-input/file-input.js +5 -23
  105. package/components/file-input/file-input.js.map +1 -1
  106. package/components/highlight/highlight.d.ts +6 -8
  107. package/components/highlight/highlight.js +12 -20
  108. package/components/highlight/highlight.js.map +1 -1
  109. package/components/icon/icon.d.ts +1 -1
  110. package/components/icon/registry/types.d.ts +1 -1
  111. package/components/icon/registry/types.js.map +1 -1
  112. package/components/input/input.d.ts +9 -19
  113. package/components/input/input.js +30 -60
  114. package/components/input/input.js.map +1 -1
  115. package/components/mask-input/mask-input.d.ts +1 -1
  116. package/components/mask-input/mask-input.js +2 -4
  117. package/components/mask-input/mask-input.js.map +1 -1
  118. package/components/nav-drawer/nav-drawer.d.ts +7 -7
  119. package/components/nav-drawer/nav-drawer.js +12 -23
  120. package/components/nav-drawer/nav-drawer.js.map +1 -1
  121. package/components/progress/base.d.ts +4 -7
  122. package/components/progress/base.js +21 -45
  123. package/components/progress/base.js.map +1 -1
  124. package/components/qr-code/qr-code.d.ts +1 -1
  125. package/components/qr-code/qr-code.js +3 -2
  126. package/components/qr-code/qr-code.js.map +1 -1
  127. package/components/qr-code/types.d.ts +1 -1
  128. package/components/qr-code/types.js.map +1 -1
  129. package/components/radio/controller.d.ts +2 -30
  130. package/components/radio/controller.js +8 -94
  131. package/components/radio/controller.js.map +1 -1
  132. package/components/radio/radio.d.ts +5 -5
  133. package/components/radio/radio.js +48 -58
  134. package/components/radio/radio.js.map +1 -1
  135. package/components/radio-group/radio-group.d.ts +1 -2
  136. package/components/radio-group/radio-group.js +2 -9
  137. package/components/radio-group/radio-group.js.map +1 -1
  138. package/components/rating/rating.d.ts +24 -12
  139. package/components/rating/rating.js +79 -73
  140. package/components/rating/rating.js.map +1 -1
  141. package/components/resize-container/default-ghost.js +4 -3
  142. package/components/resize-container/default-ghost.js.map +1 -1
  143. package/components/resize-container/resize-container.d.ts +1 -1
  144. package/components/resize-container/resize-container.js +5 -3
  145. package/components/resize-container/resize-container.js.map +1 -1
  146. package/components/resize-container/resize-controller.js +4 -3
  147. package/components/resize-container/resize-controller.js.map +1 -1
  148. package/components/select/select-group.d.ts +2 -1
  149. package/components/select/select-group.js +7 -11
  150. package/components/select/select-group.js.map +1 -1
  151. package/components/select/select.d.ts +3 -4
  152. package/components/select/select.js +23 -28
  153. package/components/select/select.js.map +1 -1
  154. package/components/slider/range-slider.d.ts +3 -2
  155. package/components/slider/range-slider.js +22 -70
  156. package/components/slider/range-slider.js.map +1 -1
  157. package/components/slider/slider-base.d.ts +9 -6
  158. package/components/slider/slider-base.js +27 -28
  159. package/components/slider/slider-base.js.map +1 -1
  160. package/components/slider/slider.js +9 -5
  161. package/components/slider/slider.js.map +1 -1
  162. package/components/splitter/splitter.d.ts +1 -1
  163. package/components/splitter/splitter.js.map +1 -1
  164. package/components/stepper/step.d.ts +3 -0
  165. package/components/stepper/step.js +9 -5
  166. package/components/stepper/step.js.map +1 -1
  167. package/components/stepper/stepper.d.ts +0 -11
  168. package/components/stepper/stepper.js +21 -86
  169. package/components/stepper/stepper.js.map +1 -1
  170. package/components/tabs/tab.d.ts +1 -1
  171. package/components/tabs/tab.js +6 -5
  172. package/components/tabs/tab.js.map +1 -1
  173. package/components/tabs/tabs.d.ts +1 -5
  174. package/components/tabs/tabs.js +12 -34
  175. package/components/tabs/tabs.js.map +1 -1
  176. package/components/textarea/textarea.d.ts +6 -7
  177. package/components/textarea/textarea.js +3 -10
  178. package/components/textarea/textarea.js.map +1 -1
  179. package/components/theme-provider/theme-provider.d.ts +2 -4
  180. package/components/theme-provider/theme-provider.js +11 -21
  181. package/components/theme-provider/theme-provider.js.map +1 -1
  182. package/components/tile-manager/tile-manager.d.ts +10 -21
  183. package/components/tile-manager/tile-manager.js +43 -74
  184. package/components/tile-manager/tile-manager.js.map +1 -1
  185. package/components/tile-manager/tile.d.ts +10 -18
  186. package/components/tile-manager/tile.js +50 -67
  187. package/components/tile-manager/tile.js.map +1 -1
  188. package/components/tooltip/tooltip.d.ts +4 -7
  189. package/components/tooltip/tooltip.js +20 -28
  190. package/components/tooltip/tooltip.js.map +1 -1
  191. package/components/tree/tree.d.ts +1 -14
  192. package/components/tree/tree.js +5 -23
  193. package/components/tree/tree.js.map +1 -1
  194. package/components/tree/tree.navigation.d.ts +1 -1
  195. package/components/tree/tree.navigation.js.map +1 -1
  196. package/components/validation-container/validation-container.d.ts +1 -2
  197. package/components/validation-container/validation-container.js.map +1 -1
  198. package/components/virtualization/engine.d.ts +40 -38
  199. package/components/virtualization/engine.js.map +1 -1
  200. package/components/virtualization/types.d.ts +6 -5
  201. package/components/virtualization/types.js.map +1 -1
  202. package/components/virtualization/virtualization.d.ts +94 -80
  203. package/components/virtualization/virtualization.js +10 -2
  204. package/components/virtualization/virtualization.js.map +1 -1
  205. package/custom-elements.json +9940 -7738
  206. package/igniteui-webcomponents.css-data.json +1 -1
  207. package/igniteui-webcomponents.html-data.json +1 -1
  208. package/internals/controllers/aria-projection.d.ts +6 -6
  209. package/internals/controllers/aria-projection.js.map +1 -1
  210. package/internals/controllers/context-provider.d.ts +48 -0
  211. package/internals/controllers/context-provider.js +29 -0
  212. package/internals/controllers/context-provider.js.map +1 -0
  213. package/internals/controllers/drag.js +6 -6
  214. package/internals/controllers/drag.js.map +1 -1
  215. package/internals/controllers/gestures.js +3 -0
  216. package/internals/controllers/gestures.js.map +1 -1
  217. package/internals/controllers/group.d.ts +64 -0
  218. package/internals/controllers/group.js +104 -0
  219. package/internals/controllers/group.js.map +1 -0
  220. package/internals/controllers/internals.d.ts +16 -3
  221. package/internals/controllers/internals.js +10 -3
  222. package/internals/controllers/internals.js.map +1 -1
  223. package/internals/controllers/key-bindings.d.ts +6 -0
  224. package/internals/controllers/key-bindings.js +3 -0
  225. package/internals/controllers/key-bindings.js.map +1 -1
  226. package/internals/controllers/roving-focus.d.ts +72 -0
  227. package/internals/controllers/roving-focus.js +61 -0
  228. package/internals/controllers/roving-focus.js.map +1 -0
  229. package/internals/controllers/slot.d.ts +8 -0
  230. package/internals/controllers/slot.js +4 -0
  231. package/internals/controllers/slot.js.map +1 -1
  232. package/internals/controllers/toggle.d.ts +96 -0
  233. package/internals/controllers/toggle.js +65 -0
  234. package/internals/controllers/toggle.js.map +1 -0
  235. package/internals/date/converters.js +2 -5
  236. package/internals/date/converters.js.map +1 -1
  237. package/internals/date/model.d.ts +4 -0
  238. package/internals/date/model.js +6 -0
  239. package/internals/date/model.js.map +1 -1
  240. package/internals/decorators/coerced-property.d.ts +47 -0
  241. package/internals/decorators/coerced-property.js +36 -0
  242. package/internals/decorators/coerced-property.js.map +1 -0
  243. package/internals/i18n/i18n-controller.d.ts +22 -3
  244. package/internals/i18n/i18n-controller.js +28 -6
  245. package/internals/i18n/i18n-controller.js.map +1 -1
  246. package/internals/mixins/alert.d.ts +1 -1
  247. package/internals/mixins/alert.js +5 -3
  248. package/internals/mixins/alert.js.map +1 -1
  249. package/internals/mixins/combo-box.d.ts +3 -12
  250. package/internals/mixins/combo-box.js +6 -32
  251. package/internals/mixins/combo-box.js.map +1 -1
  252. package/internals/mixins/forms/associated.js +18 -0
  253. package/internals/mixins/forms/associated.js.map +1 -1
  254. package/internals/mixins/forms/types.d.ts +20 -1
  255. package/internals/mixins/forms/types.js.map +1 -1
  256. package/internals/mixins/i18n.d.ts +31 -0
  257. package/internals/mixins/i18n.js +36 -0
  258. package/internals/mixins/i18n.js.map +1 -0
  259. package/internals/mixins/mask-behavior.d.ts +6 -5
  260. package/internals/mixins/mask-behavior.js +1 -2
  261. package/internals/mixins/mask-behavior.js.map +1 -1
  262. package/internals/mixins/option.js +4 -2
  263. package/internals/mixins/option.js.map +1 -1
  264. package/internals/templates/masked-input.d.ts +1 -1
  265. package/internals/templates/masked-input.js.map +1 -1
  266. package/internals/templates/slotted-icon.d.ts +32 -0
  267. package/internals/templates/slotted-icon.js +19 -0
  268. package/internals/templates/slotted-icon.js.map +1 -0
  269. package/internals/templates/toggle-shell.d.ts +46 -0
  270. package/internals/templates/toggle-shell.js +38 -0
  271. package/internals/templates/toggle-shell.js.map +1 -0
  272. package/internals/timing.d.ts +32 -0
  273. package/internals/timing.js +20 -0
  274. package/internals/timing.js.map +1 -0
  275. package/internals/utils/arrays.d.ts +2 -2
  276. package/internals/utils/arrays.js.map +1 -1
  277. package/internals/utils/dom.d.ts +22 -0
  278. package/internals/utils/dom.js +15 -1
  279. package/internals/utils/dom.js.map +1 -1
  280. package/internals/utils/math.d.ts +1 -1
  281. package/internals/utils/math.js.map +1 -1
  282. package/internals/utils/types.d.ts +1 -1
  283. package/internals/utils/types.js.map +1 -1
  284. package/internals/validators.d.ts +23 -0
  285. package/internals/validators.js +21 -0
  286. package/internals/validators.js.map +1 -1
  287. package/package.json +1 -1
  288. package/theming/theming-controller.d.ts +3 -3
  289. package/theming/theming-controller.js.map +1 -1
  290. package/web-types.json +2 -2
  291. package/internals/decorators/watch.d.ts +0 -5
  292. package/internals/decorators/watch.js +0 -19
  293. package/internals/decorators/watch.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.js","sourceRoot":"","sources":["../../../src/internals/controllers/group.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport { isEmpty } from '../utils/arrays.js';\nimport { getRoot } from '../utils/dom.js';\n\ntype GroupHost = ReactiveControllerHost & Element;\n\n/** The object a group's identity is bound to - a root node, a container element. */\ntype GroupScope = object;\n\ntype GroupRegistryConfig<T extends GroupHost, S> = {\n /**\n * The group key of a host. Together with the scope it forms the identity of\n * a group. A host with an empty key stays on its own.\n */\n keyOf: (host: T) => string;\n /**\n * Derives the group-wide state that each member receives on sync, from the\n * current members of the group.\n */\n deriveState: (members: T[]) => S;\n /**\n * The scope the group identity is bound to. Defaults to the host's root\n * node (document or shadow root).\n */\n scopeOf?: (host: T) => GroupScope;\n};\n\n/** The membership of one host, attached to it as a reactive controller. */\ninterface GroupMemberController<\n T extends GroupHost,\n> extends ReactiveController {\n /** The hosts of this member's group, in DOM order. A host with no group is on its own. */\n readonly members: T[];\n /**\n * Moves the host to the group of its current scope and key. Does nothing\n * while both stay the same. If not, the group that the host joins and the\n * group that it leaves both update their state.\n */\n updateMembership(): void;\n /** Updates the state of the host group. */\n sync(): void;\n}\n\ninterface GroupRegistry<T extends GroupHost, S> {\n /** Creates and attaches a membership controller to the host. */\n attach(host: T, onSync: (state: S) => void): GroupMemberController<T>;\n /** Returns the hosts of the group of `member`, in DOM order. */\n membersOf(member: T): T[];\n}\n\nfunction byDocumentOrder(a: Node, b: Node): number {\n if (a === b) {\n return 0;\n }\n\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING\n ? -1\n : 1;\n}\n\n/**\n * Creates a registry of groups whose members discover each other through a\n * shared scope and key instead of a common parent element - the way native\n * radio buttons group by their `name` within a form root.\n *\n * Each member holds its own entry for its life-cycle, so that a group read\n * reflects the actual membership and the members that stay behind derive their\n * state again when one leaves - a change a member cannot see on its own.\n *\n * One registry holds one kind of group; create it at module level:\n *\n * @example\n * ```typescript\n * const radioGroups = createGroupRegistry<IgcRadioComponent, boolean>({\n * keyOf: (radio) => radio.name || '',\n * deriveState: (radios) => radios.some((radio) => radio.checked),\n * });\n *\n * // In the component:\n * private readonly _group = radioGroups.attach(this, (state) => { ... });\n * ```\n */\nexport function createGroupRegistry<T extends GroupHost, S>(\n config: GroupRegistryConfig<T, S>\n): GroupRegistry<T, S> {\n const groups = new WeakMap<GroupScope, Map<string, Set<Member>>>();\n const controllers = new WeakMap<T, Member>();\n\n function scopeOf(host: T): GroupScope {\n return config.scopeOf?.(host) ?? getRoot(host);\n }\n\n /** Gives each member of the group the state that the group derives. */\n function syncGroup(group: Iterable<Member>): void {\n const members = Array.from(group);\n const state = config.deriveState(members.map((member) => member.host));\n\n for (const member of members) {\n member.onSync(state);\n }\n }\n\n class Member implements GroupMemberController<T> {\n public readonly host: T;\n public readonly onSync: (state: S) => void;\n\n /** The scope the host is registered under, or null while it is not registered. */\n private _scope: GroupScope | null = null;\n private _key = '';\n\n /** Whether the host still belongs to the group that holds its entry. */\n private get _isCurrent(): boolean {\n return (\n config.keyOf(this.host) === this._key &&\n scopeOf(this.host) === this._scope\n );\n }\n\n private get _group(): Member[] {\n const entries = this._scope\n ? Array.from(groups.get(this._scope)?.get(this._key) ?? [])\n : [];\n\n // A host moves to its new group on its next update, so an entry can be\n // one that has a different key or scope by now.\n const members = entries.filter((member) => member._isCurrent);\n\n return isEmpty(members)\n ? [this]\n : members.sort((a, b) => byDocumentOrder(a.host, b.host));\n }\n\n public get members(): T[] {\n return this._group.map((member) => member.host);\n }\n\n constructor(host: T, onSync: (state: S) => void) {\n this.host = host;\n this.onSync = onSync;\n\n controllers.set(host, this);\n host.addController(this);\n }\n\n public hostConnected(): void {\n this.updateMembership();\n }\n\n public hostDisconnected(): void {\n this._unregister();\n }\n\n public updateMembership(): void {\n const scope = this.host.isConnected ? scopeOf(this.host) : null;\n const key = config.keyOf(this.host);\n\n if (scope === this._scope && key === this._key) {\n return;\n }\n\n this._unregister();\n\n if (scope && key) {\n this._scope = scope;\n this._key = key;\n\n const keys = groups.get(scope) ?? new Map<string, Set<Member>>();\n const group = keys.get(key) ?? new Set<Member>();\n\n keys.set(key, group);\n groups.set(scope, keys);\n group.add(this);\n }\n\n this.sync();\n }\n\n public sync(): void {\n syncGroup(this._group);\n }\n\n private _unregister(): void {\n const { _scope: scope, _key: key } = this;\n const keys = scope ? groups.get(scope) : undefined;\n const group = keys?.get(key);\n\n this._scope = null;\n this._key = '';\n\n if (!group?.delete(this)) {\n return;\n }\n\n if (isEmpty(group)) {\n keys?.delete(key);\n } else {\n // The host is gone, and any state that it held goes with it. What is\n // left of the group derives its state again.\n syncGroup(group);\n }\n }\n }\n\n return {\n attach(host, onSync) {\n return new Member(host, onSync);\n },\n\n membersOf(member) {\n const controller = controllers.get(member);\n\n // Move the member first, so that a read that comes right after a change\n // of its key or scope resolves against the correct group.\n controller?.updateMembership();\n\n return controller?.members ?? [member];\n },\n };\n}\n\nexport type { GroupMemberController, GroupRegistry, GroupRegistryConfig };\n"]}
@@ -1,15 +1,25 @@
1
1
  import type { LitElement, ReactiveController, ReactiveControllerHost } from 'lit';
2
2
  import type { FormValueType } from '../mixins/forms/types.js';
3
+ /** A subset of the ARIA attributes exposed through `ElementInternals`. */
4
+ type ARIAState = {
5
+ [K in keyof ARIAMixin]?: ARIAMixin[K];
6
+ };
3
7
  /** Configuration for the ElementInternalsController. */
4
8
  type ElementInternalsConfig<T extends keyof ARIAMixin = keyof ARIAMixin> = {
5
9
  /** Initial ARIA attributes to set on the element internals. */
6
10
  initialARIA?: Partial<Record<T, ARIAMixin[T]>>;
11
+ /**
12
+ * ARIA attributes derived from host state, recomputed on every host update.
13
+ * Keep the projection cheap - it runs whether or not the properties it reads
14
+ * have changed.
15
+ */
16
+ aria?: () => ARIAState;
7
17
  /**
8
18
  * Whether to also mirror the internals `role` to a `role` content attribute
9
19
  * on the host element.
10
20
  *
11
21
  * Workaround for axe, which reads content attributes only and does not see
12
- * `ElementInternals` ARIA. An author-supplied `role` attribute always wins
22
+ * `ElementInternals` ARIA. An author-supplied `role` attribute always wins -
13
23
  * the controller only writes the attribute when it is absent or was written
14
24
  * by the controller itself.
15
25
  */
@@ -22,6 +32,7 @@ type ElementInternalsConfig<T extends keyof ARIAMixin = keyof ARIAMixin> = {
22
32
  declare class ElementInternalsController implements ReactiveController {
23
33
  private readonly _host;
24
34
  private readonly _internals;
35
+ private readonly _aria?;
25
36
  private readonly _reflectRole;
26
37
  /** The last `role` content attribute value written by this controller. */
27
38
  private _reflectedRole;
@@ -71,11 +82,13 @@ declare class ElementInternalsController implements ReactiveController {
71
82
  constructor(host: ReactiveControllerHost & LitElement, config?: ElementInternalsConfig);
72
83
  /** @internal */
73
84
  hostConnected(): void;
85
+ /** @internal */
86
+ hostUpdate(): void;
74
87
  /**
75
88
  * Mirrors the internals `role` onto a content attribute on the host, when
76
89
  * {@link ElementInternalsConfig.reflectRole} is enabled.
77
90
  *
78
- * Deferred until the host is connected custom elements must not gain
91
+ * Deferred until the host is connected - custom elements must not gain
79
92
  * attributes during construction.
80
93
  */
81
94
  private _reflectRoleAttribute;
@@ -134,7 +147,7 @@ export declare function addInternalsController(host: ReactiveControllerHost & Li
134
147
  /**
135
148
  * Resolves the {@link ElementInternalsController} of the given element, if it has one.
136
149
  *
137
- * Internal cross-component/spec lookup. Not part of the public API lives under
150
+ * Internal cross-component/spec lookup. Not part of the public API - lives under
138
151
  * `#internals` and must not be re-exported from the package entry point. Prefer this
139
152
  * over exposing `public` `@hidden @internal` members on component classes.
140
153
  */
@@ -20,6 +20,7 @@ class ElementInternalsController {
20
20
  this._reflectedRole = null;
21
21
  this._host = host;
22
22
  this._internals = this._host.attachInternals();
23
+ this._aria = config?.aria;
23
24
  this._reflectRole = config?.reflectRole ?? false;
24
25
  if (config?.initialARIA) {
25
26
  this.setARIA(config.initialARIA);
@@ -30,6 +31,11 @@ class ElementInternalsController {
30
31
  hostConnected() {
31
32
  this._reflectRoleAttribute();
32
33
  }
34
+ hostUpdate() {
35
+ if (this._aria) {
36
+ this.setARIA(this._aria.call(this._host));
37
+ }
38
+ }
33
39
  _reflectRoleAttribute() {
34
40
  const host = this._host;
35
41
  if (!(this._reflectRole && host.isConnected)) {
@@ -37,10 +43,11 @@ class ElementInternalsController {
37
43
  }
38
44
  const role = this._internals.role;
39
45
  const current = host.getAttribute('role');
40
- if (role && (current === null || current === this._reflectedRole)) {
41
- host.setAttribute('role', role);
42
- this._reflectedRole = role;
46
+ if (current !== null && current !== this._reflectedRole) {
47
+ return;
43
48
  }
49
+ role ? host.setAttribute('role', role) : host.removeAttribute('role');
50
+ this._reflectedRole = role;
44
51
  }
45
52
  setARIA(state) {
46
53
  Object.assign(this._internals, state);
@@ -1 +1 @@
1
- {"version":3,"file":"internals.js","sourceRoot":"","sources":["../../../src/internals/controllers/internals.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n LitElement,\n ReactiveController,\n ReactiveControllerHost,\n} from 'lit';\nimport type { FormValueType } from '../mixins/forms/types.js';\n\n/** Configuration for the ElementInternalsController. */\ntype ElementInternalsConfig<T extends keyof ARIAMixin = keyof ARIAMixin> = {\n /** Initial ARIA attributes to set on the element internals. */\n initialARIA?: Partial<Record<T, ARIAMixin[T]>>;\n /**\n * Whether to also mirror the internals `role` to a `role` content attribute\n * on the host element.\n *\n * Workaround for axe, which reads content attributes only and does not see\n * `ElementInternals` ARIA. An author-supplied `role` attribute always wins —\n * the controller only writes the attribute when it is absent or was written\n * by the controller itself.\n */\n reflectRole?: boolean;\n};\n\n/**\n * Internal registry resolving a host element to its internals controller.\n *\n * `attachInternals()` throws when called twice on the same element, so a host\n * maps to at most one controller.\n */\nconst registry = new WeakMap<Element, ElementInternalsController>();\n\n/**\n * A Lit ReactiveController to manage `ElementInternals` for a host element.\n * Provides methods to interact with custom element states and ARIA attributes..\n */\nclass ElementInternalsController implements ReactiveController {\n private readonly _host: ReactiveControllerHost & LitElement;\n private readonly _internals: ElementInternals;\n private readonly _reflectRole: boolean;\n\n /** The last `role` content attribute value written by this controller. */\n private _reflectedRole: string | null = null;\n\n /**\n * Gets the closest ancestor `<form>` element or `null`.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true` in order to return the parent form.\n */\n public get form(): HTMLFormElement | null {\n return this._internals.form;\n }\n\n /**\n * Returns a `ValidityState` object which represents the different validity states\n * the element can be in, with respect to constraint validation.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public get validity(): ValidityState {\n return this._internals.validity;\n }\n\n /**\n * Returns a string containing the validation message of this element.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public get validationMessage(): string {\n return this._internals.validationMessage;\n }\n\n /**\n * Returns a boolean value which returns true if the element is a submittable element\n * which is a candidate for constraint validation.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public get willValidate(): boolean {\n return this._internals.willValidate;\n }\n\n /**\n * Returns a read-only array of the `<label>` elements associated with the host element, or `null` if there are no associated labels.\n * The association is determined by the `for` attribute of `<label>` elements or by nesting the host element inside a `<label>`.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true` in order to return associated labels.\n */\n public get labels(): ReadonlyArray<Element> | null {\n const labels = this._internals.labels as NodeListOf<Element> | null;\n return labels && labels.length > 0 ? Array.from(labels) : null;\n }\n\n constructor(\n host: ReactiveControllerHost & LitElement,\n config?: ElementInternalsConfig\n ) {\n this._host = host;\n this._internals = this._host.attachInternals();\n this._reflectRole = config?.reflectRole ?? false;\n\n if (config?.initialARIA) {\n this.setARIA(config.initialARIA);\n }\n\n registry.set(host, this);\n host.addController(this);\n }\n\n /** @internal */\n public hostConnected(): void {\n this._reflectRoleAttribute();\n }\n\n /**\n * Mirrors the internals `role` onto a content attribute on the host, when\n * {@link ElementInternalsConfig.reflectRole} is enabled.\n *\n * Deferred until the host is connected — custom elements must not gain\n * attributes during construction.\n */\n private _reflectRoleAttribute(): void {\n const host = this._host;\n\n if (!(this._reflectRole && host.isConnected)) {\n return;\n }\n\n const role = this._internals.role;\n const current = host.getAttribute('role');\n\n // Write only when the attribute is absent or still holds the value this\n // controller wrote — an attribute changed by the author is theirs to keep.\n if (role && (current === null || current === this._reflectedRole)) {\n host.setAttribute('role', role);\n this._reflectedRole = role;\n }\n }\n\n /** Sets ARIA attributes on the element's internals. */\n public setARIA<T extends keyof ARIAMixin = keyof ARIAMixin>(\n state: Partial<Record<T, ARIAMixin[T]>>\n ): void {\n Object.assign(this._internals, state);\n\n if ('role' in state) {\n this._reflectRoleAttribute();\n }\n }\n\n /**\n * Returns an ARIA attribute set on the element's internals. Internals-based\n * ARIA leaves no trace in the DOM, so this is the only way to read it back.\n */\n public getARIA<T extends keyof ARIAMixin = keyof ARIAMixin>(\n name: T\n ): ARIAMixin[T] {\n return this._internals[name];\n }\n\n /**\n * Adds or removes a custom state from the element's internals.\n * Custom states can be styled via `:state()` selector in CSS.\n */\n public setState(state: string, value: boolean): void {\n value\n ? this._internals.states.add(state)\n : this._internals.states.delete(state);\n }\n\n /**\n * Sets both the state and submission value of internals's target element to value.\n *\n * If value is null, the element won't participate in form submission.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public setFormValue(value: FormValueType, state?: FormValueType): void {\n this._internals.setFormValue(value, state);\n }\n\n /**\n * Sets the internal validity state of the host element as well as the validation\n * message.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public setValidity(flags?: ValidityStateFlags, message?: string): void {\n this._internals.setValidity(flags, message);\n }\n\n /**\n * Checks the internal validity of the host element and fires an `invalid` event if\n * the host element fails validation constraints.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public checkValidity(): boolean {\n return this._internals.checkValidity();\n }\n\n /**\n * Checks the internal validity of the host element and fires an `invalid` event if\n * the host element fails validation constraints.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public reportValidity(): boolean {\n return this._internals.reportValidity();\n }\n}\n\n/** Creates and adds a {@link ElementInternalsController} to a LitElement host. */\nexport function addInternalsController(\n host: ReactiveControllerHost & LitElement,\n config?: ElementInternalsConfig\n): ElementInternalsController {\n return new ElementInternalsController(host, config);\n}\n\n/**\n * Resolves the {@link ElementInternalsController} of the given element, if it has one.\n *\n * Internal cross-component/spec lookup. Not part of the public API — lives under\n * `#internals` and must not be re-exported from the package entry point. Prefer this\n * over exposing `public` `@hidden @internal` members on component classes.\n */\nexport function internalsOf(\n element: Element\n): ElementInternalsController | undefined {\n return registry.get(element);\n}\n\nexport type { ElementInternalsController };\n"]}
1
+ {"version":3,"file":"internals.js","sourceRoot":"","sources":["../../../src/internals/controllers/internals.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n LitElement,\n ReactiveController,\n ReactiveControllerHost,\n} from 'lit';\nimport type { FormValueType } from '../mixins/forms/types.js';\n\n/** A subset of the ARIA attributes exposed through `ElementInternals`. */\ntype ARIAState = { [K in keyof ARIAMixin]?: ARIAMixin[K] };\n\n/** Configuration for the ElementInternalsController. */\ntype ElementInternalsConfig<T extends keyof ARIAMixin = keyof ARIAMixin> = {\n /** Initial ARIA attributes to set on the element internals. */\n initialARIA?: Partial<Record<T, ARIAMixin[T]>>;\n /**\n * ARIA attributes derived from host state, recomputed on every host update.\n * Keep the projection cheap - it runs whether or not the properties it reads\n * have changed.\n */\n aria?: () => ARIAState;\n /**\n * Whether to also mirror the internals `role` to a `role` content attribute\n * on the host element.\n *\n * Workaround for axe, which reads content attributes only and does not see\n * `ElementInternals` ARIA. An author-supplied `role` attribute always wins -\n * the controller only writes the attribute when it is absent or was written\n * by the controller itself.\n */\n reflectRole?: boolean;\n};\n\n/**\n * Internal registry resolving a host element to its internals controller.\n *\n * `attachInternals()` throws when called twice on the same element, so a host\n * maps to at most one controller.\n */\nconst registry = new WeakMap<Element, ElementInternalsController>();\n\n/**\n * A Lit ReactiveController to manage `ElementInternals` for a host element.\n * Provides methods to interact with custom element states and ARIA attributes..\n */\nclass ElementInternalsController implements ReactiveController {\n private readonly _host: ReactiveControllerHost & LitElement;\n private readonly _internals: ElementInternals;\n private readonly _aria?: () => ARIAState;\n private readonly _reflectRole: boolean;\n\n /** The last `role` content attribute value written by this controller. */\n private _reflectedRole: string | null = null;\n\n /**\n * Gets the closest ancestor `<form>` element or `null`.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true` in order to return the parent form.\n */\n public get form(): HTMLFormElement | null {\n return this._internals.form;\n }\n\n /**\n * Returns a `ValidityState` object which represents the different validity states\n * the element can be in, with respect to constraint validation.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public get validity(): ValidityState {\n return this._internals.validity;\n }\n\n /**\n * Returns a string containing the validation message of this element.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public get validationMessage(): string {\n return this._internals.validationMessage;\n }\n\n /**\n * Returns a boolean value which returns true if the element is a submittable element\n * which is a candidate for constraint validation.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public get willValidate(): boolean {\n return this._internals.willValidate;\n }\n\n /**\n * Returns a read-only array of the `<label>` elements associated with the host element, or `null` if there are no associated labels.\n * The association is determined by the `for` attribute of `<label>` elements or by nesting the host element inside a `<label>`.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true` in order to return associated labels.\n */\n public get labels(): ReadonlyArray<Element> | null {\n const labels = this._internals.labels as NodeListOf<Element> | null;\n return labels && labels.length > 0 ? Array.from(labels) : null;\n }\n\n constructor(\n host: ReactiveControllerHost & LitElement,\n config?: ElementInternalsConfig\n ) {\n this._host = host;\n this._internals = this._host.attachInternals();\n this._aria = config?.aria;\n this._reflectRole = config?.reflectRole ?? false;\n\n if (config?.initialARIA) {\n this.setARIA(config.initialARIA);\n }\n\n registry.set(host, this);\n host.addController(this);\n }\n\n /** @internal */\n public hostConnected(): void {\n this._reflectRoleAttribute();\n }\n\n /** @internal */\n public hostUpdate(): void {\n if (this._aria) {\n this.setARIA(this._aria.call(this._host));\n }\n }\n\n /**\n * Mirrors the internals `role` onto a content attribute on the host, when\n * {@link ElementInternalsConfig.reflectRole} is enabled.\n *\n * Deferred until the host is connected - custom elements must not gain\n * attributes during construction.\n */\n private _reflectRoleAttribute(): void {\n const host = this._host;\n\n if (!(this._reflectRole && host.isConnected)) {\n return;\n }\n\n const role = this._internals.role;\n const current = host.getAttribute('role');\n\n // Write only when the attribute is absent or still holds the value this\n // controller wrote - an attribute changed by the author is theirs to keep.\n if (current !== null && current !== this._reflectedRole) {\n return;\n }\n\n // A cleared role takes its attribute with it, or the host would keep\n // semantics that its internals no longer report.\n role ? host.setAttribute('role', role) : host.removeAttribute('role');\n this._reflectedRole = role;\n }\n\n /** Sets ARIA attributes on the element's internals. */\n public setARIA<T extends keyof ARIAMixin = keyof ARIAMixin>(\n state: Partial<Record<T, ARIAMixin[T]>>\n ): void {\n Object.assign(this._internals, state);\n\n if ('role' in state) {\n this._reflectRoleAttribute();\n }\n }\n\n /**\n * Returns an ARIA attribute set on the element's internals. Internals-based\n * ARIA leaves no trace in the DOM, so this is the only way to read it back.\n */\n public getARIA<T extends keyof ARIAMixin = keyof ARIAMixin>(\n name: T\n ): ARIAMixin[T] {\n return this._internals[name];\n }\n\n /**\n * Adds or removes a custom state from the element's internals.\n * Custom states can be styled via `:state()` selector in CSS.\n */\n public setState(state: string, value: boolean): void {\n value\n ? this._internals.states.add(state)\n : this._internals.states.delete(state);\n }\n\n /**\n * Sets both the state and submission value of internals's target element to value.\n *\n * If value is null, the element won't participate in form submission.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public setFormValue(value: FormValueType, state?: FormValueType): void {\n this._internals.setFormValue(value, state);\n }\n\n /**\n * Sets the internal validity state of the host element as well as the validation\n * message.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public setValidity(flags?: ValidityStateFlags, message?: string): void {\n this._internals.setValidity(flags, message);\n }\n\n /**\n * Checks the internal validity of the host element and fires an `invalid` event if\n * the host element fails validation constraints.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public checkValidity(): boolean {\n return this._internals.checkValidity();\n }\n\n /**\n * Checks the internal validity of the host element and fires an `invalid` event if\n * the host element fails validation constraints.\n *\n * @remarks\n * The host element must be form associated, that is, it should have\n * `static formAssociated = true`.\n */\n public reportValidity(): boolean {\n return this._internals.reportValidity();\n }\n}\n\n/** Creates and adds a {@link ElementInternalsController} to a LitElement host. */\nexport function addInternalsController(\n host: ReactiveControllerHost & LitElement,\n config?: ElementInternalsConfig\n): ElementInternalsController {\n return new ElementInternalsController(host, config);\n}\n\n/**\n * Resolves the {@link ElementInternalsController} of the given element, if it has one.\n *\n * Internal cross-component/spec lookup. Not part of the public API - lives under\n * `#internals` and must not be re-exported from the package entry point. Prefer this\n * over exposing `public` `@hidden @internal` members on component classes.\n */\nexport function internalsOf(\n element: Element\n): ElementInternalsController | undefined {\n return registry.get(element);\n}\n\nexport type { ElementInternalsController };\n"]}
@@ -103,6 +103,12 @@ interface KeyBindingOptions {
103
103
  }
104
104
  /** {@link MODIFIER_ENTRIES} as a lookup of modifier name to event property. */
105
105
  export declare const MODIFIER_EVENT_KEYS: Record<string, string>;
106
+ /**
107
+ * Whether the event's key matches `key`, case-insensitively. Prefer this over
108
+ * comparing `event.key` directly - the key names of this module are lowercase,
109
+ * while real events carry the canonical casing (e.g. `Enter`).
110
+ */
111
+ export declare function isKey(event: KeyboardEvent, key: string): boolean;
106
112
  /**
107
113
  * A controller for managing key bindings on a host element. It allows you to register handlers for specific key combinations,
108
114
  * with support for modifier keys and event options such as `preventDefault` and `stopPropagation`.
@@ -31,6 +31,9 @@ export const MODIFIER_EVENT_KEYS = Object.fromEntries(MODIFIER_ENTRIES);
31
31
  function normalizeKeys(keys) {
32
32
  return asArray(keys).map((key) => key.toLowerCase());
33
33
  }
34
+ export function isKey(event, key) {
35
+ return event.key.toLowerCase() === key.toLowerCase();
36
+ }
34
37
  function isKeydown(event) {
35
38
  return event.type === 'keydown';
36
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"key-bindings.js","sourceRoot":"","sources":["../../../src/internals/controllers/key-bindings.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport type { Ref } from 'lit/directives/ref.js';\nimport { createAbortHandle } from '../abort-handler.js';\nimport { asArray, partition } from '../utils/arrays.js';\nimport { isElement } from '../utils/dom.js';\nimport { toMerged } from '../utils/objects.js';\nimport { isFunction } from '../utils/types.js';\n\n//#region Keys and modifiers\n\n/* Common keys */\nexport const arrowLeft = 'ArrowLeft' as const;\nexport const arrowRight = 'ArrowRight' as const;\nexport const arrowUp = 'ArrowUp' as const;\nexport const arrowDown = 'ArrowDown' as const;\nexport const enterKey = 'Enter' as const;\nexport const spaceBar = ' ' as const;\nexport const escapeKey = 'Escape' as const;\nexport const homeKey = 'Home' as const;\nexport const endKey = 'End' as const;\nexport const pageUpKey = 'PageUp' as const;\nexport const pageDownKey = 'PageDown' as const;\nexport const tabKey = 'Tab' as const;\n\n/* Modifiers */\nexport const altKey = 'Alt' as const;\nexport const ctrlKey = 'Control' as const;\nexport const metaKey = 'Meta' as const;\nexport const shiftKey = 'Shift' as const;\n\n//#endregion\n\n//#region Types\n\n/* Types */\ntype KeyBindingHandler = (event: KeyboardEvent) => void;\ntype KeyBindingObserverCleanup = { unsubscribe: () => void };\n\n/**\n * Whether the current event should be ignored by the controller.\n *\n * @param node - The event target\n * @param event - The event object\n *\n * When `true` is returned, the current event is ignored.\n */\ntype KeyBindingSkipCallback = (node: Element, event: KeyboardEvent) => boolean;\n\n/**\n * The event type which will trigger the bound handler.\n */\ntype KeyBindingTrigger = 'keydown' | 'keyup';\n\n/**\n * Configuration object for the controller.\n * @hidden\n */\ninterface KeyBindingControllerOptions {\n /**\n * By default, the controller listens for keypress events in the context of the host element.\n * If you pass a `ref`, you can limit the observation to a certain DOM part of the host scope.\n */\n ref?: Ref;\n /**\n * Option to ignore key press events.\n *\n * If passed an array of CSS selectors, it will ignore key presses originating from elements in the event composed path\n * that match one of the selectors.\n * Otherwise you can pass a {@link KeyBindingSkipCallback} function.\n *\n * Defaults to `['input', 'textarea', 'select']`.\n *\n * @example\n * ```ts\n * {\n * // Skip events originating from elements with `readonly` attribute\n * skip: ['[readonly]']\n * }\n * ...\n * {\n * // Same as above but with a callback\n * skip: (node: Element) => node.hasAttribute('readonly')\n * }\n * ```\n */\n skip?: string[] | KeyBindingSkipCallback;\n /**\n * A set of KeyBindingOptions configuration which is applied to every handler\n * that is added to the controller.\n *\n * Any additional KeyBindingOptions values passed when `set` is called\n * will be merged with `bindingDefaults`.\n */\n bindingDefaults?: KeyBindingOptions;\n}\n\n/**\n * Configuration object for customizing the behavior of\n * the registered handler.\n */\ninterface KeyBindingOptions {\n /**\n * The event type(s) on which the handler will be invoked.\n *\n * Defaults to `keydown` if not set.\n */\n triggers?: KeyBindingTrigger[];\n /**\n * Whether the handler should fire on auto-repeated keydown events (i.e. when a key is held down).\n *\n * Defaults to `false`.\n */\n repeat?: boolean;\n /**\n * Whether to call `preventDefault` on the target event before the handler is invoked.\n */\n preventDefault?: boolean;\n /**\n * Whether to call `stopPropagation` on the target event before the handler is invoked.\n */\n stopPropagation?: boolean;\n}\n\n/** @hidden */\ninterface KeyBinding {\n keys: string[];\n handler: KeyBindingHandler;\n options?: KeyBindingOptions;\n modifiers: string[];\n}\n\n//#endregion\n\n//#region Internal functions and constants\n\n/**\n * Every modifier, paired with the `KeyboardEvent` boolean property it is read\n * from - `ctrlKey` is the property for the `'control'` modifier, not `'controlKey'`.\n *\n * Kept in alphabetical order: combination keys sort their modifiers, and every\n * derived collection below inherits that order for free.\n */\nconst MODIFIER_ENTRIES = [\n ['alt', 'altKey'],\n ['control', 'ctrlKey'],\n ['meta', 'metaKey'],\n ['shift', 'shiftKey'],\n] as const satisfies ReadonlyArray<readonly [string, keyof KeyboardEvent]>;\n\nconst ALL_MODIFIER_VALUES = MODIFIER_ENTRIES.map(([name]) => name);\nconst MODIFIERS = new Set<string>(ALL_MODIFIER_VALUES);\n\n/** {@link MODIFIER_ENTRIES} as a lookup of modifier name to event property. */\nexport const MODIFIER_EVENT_KEYS: Record<string, string> =\n Object.fromEntries(MODIFIER_ENTRIES);\n\nfunction normalizeKeys(keys: string | string[]): string[] {\n return asArray(keys).map((key) => key.toLowerCase());\n}\n\nfunction isKeydown(event: Event): boolean {\n return event.type === 'keydown';\n}\n\nfunction isKeyup(event: Event): boolean {\n return event.type === 'keyup';\n}\n\n/** Sorts `modifiers` alphabetically, by filtering the already sorted source. */\nfunction sortModifiers(modifiers: string[]): string[] {\n return ALL_MODIFIER_VALUES.filter((mod) => modifiers.includes(mod));\n}\n\n/** Returns the modifiers active for `event`, already sorted. */\nfunction getActiveModifiers(event: KeyboardEvent): string[] {\n const active: string[] = [];\n\n for (const [name, property] of MODIFIER_ENTRIES) {\n if (event[property]) {\n active.push(name);\n }\n }\n\n return active;\n}\n\n/**\n * Creates a normalized combination key string from the provided keys and modifiers.\n *\n * The combination key is a string that uniquely identifies a specific combination of keys and modifiers.\n * It is created by sorting the keys and modifiers alphabetically and joining them with a '+' separator.\n *\n * `modifiers` must already be sorted - see {@link sortModifiers} and {@link getActiveModifiers}.\n */\nfunction createCombinationKey(keys: string[], modifiers: string[]): string {\n return modifiers.concat(keys.length > 1 ? keys.toSorted() : keys).join('+');\n}\n\n//#endregion\n\n/**\n * A controller for managing key bindings on a host element. It allows you to register handlers for specific key combinations,\n * with support for modifier keys and event options such as `preventDefault` and `stopPropagation`.\n *\n * The controller listens for keyboard events on the host element (or an optionally specified element) and invokes the appropriate handlers\n * when the registered key combinations are detected.\n * @hidden\n */\nclass KeyBindingController implements ReactiveController {\n //#region Private properties and state\n\n private static readonly _defaultOptions: KeyBindingControllerOptions = {\n skip: ['input', 'textarea', 'select'],\n bindingDefaults: { preventDefault: true },\n };\n\n private readonly _host: ReactiveControllerHost & Element;\n private readonly _ref?: Ref;\n private readonly _abortHandle = createAbortHandle();\n\n private readonly _bindings = new Map<string, KeyBinding>();\n private readonly _allowedKeys = new Set<string>();\n private readonly _pressedKeys = new Set<string>();\n\n private readonly _options: KeyBindingControllerOptions;\n private readonly _skipSelector: string | undefined;\n\n private _observedElement?: Element;\n\n private get _element(): Element {\n if (this._observedElement) {\n return this._observedElement;\n }\n return this._ref?.value || this._host;\n }\n\n //#endregion\n\n constructor(\n host: ReactiveControllerHost & Element,\n options?: KeyBindingControllerOptions\n ) {\n this._host = host;\n this._ref = options?.ref;\n this._options = toMerged(\n KeyBindingController._defaultOptions,\n options ?? {}\n );\n\n if (Array.isArray(this._options.skip)) {\n this._skipSelector = this._options.skip.join(',');\n }\n\n host.addController(this);\n }\n\n //#region Private API\n\n /**\n * Applies the event modifiers specified in the binding options to the provided keyboard event.\n * If `preventDefault` is set, it calls `event.preventDefault()`.\n * If `stopPropagation` is set, it calls `event.stopPropagation()`.\n */\n private _applyEventModifiers(\n binding: KeyBinding,\n event: KeyboardEvent\n ): void {\n if (binding.options?.preventDefault) {\n event.preventDefault();\n }\n\n if (binding.options?.stopPropagation) {\n event.stopPropagation();\n }\n }\n\n /**\n * Determines whether the provided keyboard event matches the specified key binding,\n * taking into account the event type and the binding's trigger options.\n */\n private _bindingMatches(binding: KeyBinding, event: KeyboardEvent): boolean {\n const triggers = binding.options?.triggers ?? ['keydown'];\n\n if (isKeydown(event) && triggers.includes('keydown')) {\n return !event.repeat || Boolean(binding.options?.repeat);\n }\n\n if (isKeyup(event) && triggers.includes('keyup')) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Determines whether the provided event should be ignored based on the controller's configuration and the event's context.\n * The method checks if the event's key is among the allowed keys, if the event originated from within the controller's scope,\n * and if it matches any of the skip conditions defined in the controller's options.\n */\n private _shouldSkip(event: KeyboardEvent, key: string): boolean {\n if (!this._allowedKeys.has(key)) {\n return true;\n }\n\n const path = event.composedPath();\n const element = this._element;\n\n if (!path.some((node) => node === element)) {\n return true;\n }\n\n const selector = this._skipSelector;\n\n if (selector) {\n return path.some((node) => isElement(node) && node.matches(selector));\n }\n\n const skip = this._options.skip;\n\n return isFunction(skip)\n ? skip.call(this._host, event.target as Element, event)\n : false;\n }\n\n //#endregion\n\n //#region Controller lifecycle\n\n /** @internal */\n public hostConnected(): void {\n const { signal } = this._abortHandle;\n this._host.addEventListener('keyup', this, { signal });\n this._host.addEventListener('keydown', this, { signal });\n globalThis.addEventListener('blur', this, { signal });\n }\n\n /** @internal */\n public hostDisconnected(): void {\n this._abortHandle.abort();\n }\n\n //#endregion\n\n //#region Event handling\n\n /**\n * Handles the global blur event to clear the internal state of pressed keys.\n *\n * This is necessary to prevent \"stuck\" keys when the user switches to another application\n * or tab while holding down a key.\n */\n private _handleGlobalBlur(): void {\n this._pressedKeys.clear();\n }\n\n /**\n * Handles keyboard events on the observed element.\n *\n * It checks if the event should be skipped based on the controller's configuration,\n * and if not, it determines if there is a registered handler for the combination of pressed keys and active modifiers.\n *\n * If a matching handler is found, it applies the specified event modifiers and invokes the handler.\n * It also manages the internal state of currently pressed keys to accurately detect key combinations.\n *\n */\n private _handleKeyEvent(event: KeyboardEvent): void {\n const key = event.key.toLowerCase();\n const isModifier = MODIFIERS.has(key);\n\n if (this._shouldSkip(event, key)) {\n // Always clean up on keyup regardless of whether the event is otherwise skipped.\n if (!isModifier && isKeyup(event)) {\n this._pressedKeys.delete(key);\n }\n return;\n }\n\n if (!isModifier) {\n this._pressedKeys.add(key);\n }\n\n const activeModifiers = getActiveModifiers(event);\n\n const combination = createCombinationKey(\n Array.from(this._pressedKeys),\n activeModifiers\n );\n let binding = this._bindings.get(combination);\n\n // When multiple non-modifier keys are simultaneously in _pressedKeys (due to overlapping\n // key presses, e.g. pressing ArrowUp before ArrowDown's keyup fires), the full combination\n // won't match any single-key binding. Fall back to just the current key + modifiers so that\n // single-key bindings continue to fire even when other keys are still \"held\".\n if (!binding && this._pressedKeys.size > 1) {\n binding = this._bindings.get(\n createCombinationKey([key], activeModifiers)\n );\n }\n\n if (binding && this._bindingMatches(binding, event)) {\n this._applyEventModifiers(binding, event);\n binding.handler.call(this._host, event);\n }\n\n if (!isModifier && isKeyup(event)) {\n this._pressedKeys.delete(key);\n }\n }\n\n /** @internal */\n public handleEvent(event: KeyboardEvent | FocusEvent): void {\n switch (event.type) {\n case 'keydown':\n case 'keyup':\n this._handleKeyEvent(event as KeyboardEvent);\n break;\n case 'blur':\n this._handleGlobalBlur();\n break;\n }\n }\n\n //#endregion\n\n //#region Public API\n\n /**\n * Registers a key binding with the specified key(s), handler function, and optional configuration.\n *\n * The `key` parameter can be a single key or an array of keys, and can include modifier keys (e.g., 'ctrl+s', ['shift', 'a']).\n * The `handler` is a function that will be called when the specified key combination is detected.\n * The `bindingOptions` allow you to customize the behavior of the binding, such as which event types trigger the handler,\n * whether it should fire on auto-repeated keydown events, and whether to call `preventDefault` or `stopPropagation`.\n *\n * The method returns the controller instance to allow for method chaining.\n */\n public set(\n key: string | string[],\n handler: KeyBindingHandler,\n bindingOptions?: KeyBindingOptions\n ): this {\n const { keys, modifiers } = parseKeys(key);\n const combination = createCombinationKey(keys, sortModifiers(modifiers));\n const options = toMerged(\n this._options.bindingDefaults!,\n bindingOptions ?? {}\n );\n\n for (const each of [...keys, ...modifiers]) {\n this._allowedKeys.add(each);\n }\n\n this._bindings.set(combination, { keys, handler, options, modifiers });\n\n return this;\n }\n\n /**\n * Registers the provided handler function to be called when either the Enter key or Space bar is pressed.\n *\n * This is a common pattern for activating buttons or interactive elements, and this method provides a convenient way to set up such bindings.\n *\n * The method accepts optional `KeyBindingOptions` which are applied to both the Enter key and Space bar bindings.\n * It returns the controller instance to allow for method chaining.\n *\n */\n public setActivateHandler(\n handler: KeyBindingHandler,\n options?: KeyBindingOptions\n ): this {\n this.set(enterKey, handler, options);\n this.set(spaceBar, handler, options);\n\n return this;\n }\n\n /**\n * Sets the controller to listen for keyboard events on an arbitrary `element` in the page context.\n * All the configuration and event handlers are applied as well.\n *\n * Returns an object with an `unsubscribe` function which should be called when the observing of keyboard\n * events on the `element` should cease.\n */\n public observeElement(element: Element): KeyBindingObserverCleanup {\n element.addEventListener('keydown', this);\n element.addEventListener('keyup', this);\n this._observedElement = element;\n\n return {\n unsubscribe: () => {\n element.removeEventListener('keydown', this);\n element.removeEventListener('keyup', this);\n if (this._observedElement === element) {\n this._observedElement = undefined;\n }\n },\n };\n }\n\n //#endregion\n}\n\n/**\n * Parses the provided key(s) and separates them into modifiers and regular keys.\n *\n * Modifiers are keys like Alt, Ctrl, Meta and Shift which modify the behavior of other keys when pressed in combination.\n * Regular keys are all other keys which trigger the bound handler when pressed.\n *\n * The returned `keys` and `modifiers` are normalized to lowercase for consistency.\n *\n * @internal\n *\n * @param inputKeys - The key or keys to parse, provided as a string or an array of strings.\n * @returns An object containing the separated `keys` and `modifiers`.\n */\nexport function parseKeys(inputKeys: string | string[]): {\n keys: string[];\n modifiers: string[];\n} {\n const [modifiers, keys] = partition(normalizeKeys(inputKeys), (key) =>\n MODIFIERS.has(key)\n );\n return { keys, modifiers };\n}\n\n/**\n * Controller factory function which creates a {@link KeyBindingController} instance and attaches it to the provided host.\n *\n * @param element - The host element to which the controller will be attached.\n * @param options - Optional configuration for the controller.\n * @returns The created {@link KeyBindingController} instance.\n *\n * @example\n * ```ts\n * class MyComponent extends LitElement {\n * private _keyBindings = addKeybindings(this, {\n * skip: ['input', 'textarea'], // Optional: Skip key events originating from these elements\n * bindingDefaults: { preventDefault: true }, // Optional: Default options for all bindings\n * });\n *\n * constructor() {\n * super();\n * this._keyBindings.set('ctrl+s', this._handleSave); // Register a key binding\n * }\n * ```\n */\nexport function addKeybindings(\n element: ReactiveControllerHost & Element,\n options?: KeyBindingControllerOptions\n): KeyBindingController {\n return new KeyBindingController(element, options);\n}\n\nexport type {\n KeyBindingController,\n KeyBindingControllerOptions,\n KeyBindingHandler,\n KeyBindingObserverCleanup,\n KeyBindingOptions,\n KeyBindingSkipCallback,\n KeyBindingTrigger,\n};\n"]}
1
+ {"version":3,"file":"key-bindings.js","sourceRoot":"","sources":["../../../src/internals/controllers/key-bindings.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\nimport type { Ref } from 'lit/directives/ref.js';\nimport { createAbortHandle } from '../abort-handler.js';\nimport { asArray, partition } from '../utils/arrays.js';\nimport { isElement } from '../utils/dom.js';\nimport { toMerged } from '../utils/objects.js';\nimport { isFunction } from '../utils/types.js';\n\n//#region Keys and modifiers\n\n/* Common keys */\nexport const arrowLeft = 'ArrowLeft' as const;\nexport const arrowRight = 'ArrowRight' as const;\nexport const arrowUp = 'ArrowUp' as const;\nexport const arrowDown = 'ArrowDown' as const;\nexport const enterKey = 'Enter' as const;\nexport const spaceBar = ' ' as const;\nexport const escapeKey = 'Escape' as const;\nexport const homeKey = 'Home' as const;\nexport const endKey = 'End' as const;\nexport const pageUpKey = 'PageUp' as const;\nexport const pageDownKey = 'PageDown' as const;\nexport const tabKey = 'Tab' as const;\n\n/* Modifiers */\nexport const altKey = 'Alt' as const;\nexport const ctrlKey = 'Control' as const;\nexport const metaKey = 'Meta' as const;\nexport const shiftKey = 'Shift' as const;\n\n//#endregion\n\n//#region Types\n\n/* Types */\ntype KeyBindingHandler = (event: KeyboardEvent) => void;\ntype KeyBindingObserverCleanup = { unsubscribe: () => void };\n\n/**\n * Whether the current event should be ignored by the controller.\n *\n * @param node - The event target\n * @param event - The event object\n *\n * When `true` is returned, the current event is ignored.\n */\ntype KeyBindingSkipCallback = (node: Element, event: KeyboardEvent) => boolean;\n\n/**\n * The event type which will trigger the bound handler.\n */\ntype KeyBindingTrigger = 'keydown' | 'keyup';\n\n/**\n * Configuration object for the controller.\n * @hidden\n */\ninterface KeyBindingControllerOptions {\n /**\n * By default, the controller listens for keypress events in the context of the host element.\n * If you pass a `ref`, you can limit the observation to a certain DOM part of the host scope.\n */\n ref?: Ref;\n /**\n * Option to ignore key press events.\n *\n * If passed an array of CSS selectors, it will ignore key presses originating from elements in the event composed path\n * that match one of the selectors.\n * Otherwise you can pass a {@link KeyBindingSkipCallback} function.\n *\n * Defaults to `['input', 'textarea', 'select']`.\n *\n * @example\n * ```ts\n * {\n * // Skip events originating from elements with `readonly` attribute\n * skip: ['[readonly]']\n * }\n * ...\n * {\n * // Same as above but with a callback\n * skip: (node: Element) => node.hasAttribute('readonly')\n * }\n * ```\n */\n skip?: string[] | KeyBindingSkipCallback;\n /**\n * A set of KeyBindingOptions configuration which is applied to every handler\n * that is added to the controller.\n *\n * Any additional KeyBindingOptions values passed when `set` is called\n * will be merged with `bindingDefaults`.\n */\n bindingDefaults?: KeyBindingOptions;\n}\n\n/**\n * Configuration object for customizing the behavior of\n * the registered handler.\n */\ninterface KeyBindingOptions {\n /**\n * The event type(s) on which the handler will be invoked.\n *\n * Defaults to `keydown` if not set.\n */\n triggers?: KeyBindingTrigger[];\n /**\n * Whether the handler should fire on auto-repeated keydown events (i.e. when a key is held down).\n *\n * Defaults to `false`.\n */\n repeat?: boolean;\n /**\n * Whether to call `preventDefault` on the target event before the handler is invoked.\n */\n preventDefault?: boolean;\n /**\n * Whether to call `stopPropagation` on the target event before the handler is invoked.\n */\n stopPropagation?: boolean;\n}\n\n/** @hidden */\ninterface KeyBinding {\n keys: string[];\n handler: KeyBindingHandler;\n options?: KeyBindingOptions;\n modifiers: string[];\n}\n\n//#endregion\n\n//#region Internal functions and constants\n\n/**\n * Every modifier, paired with the `KeyboardEvent` boolean property it is read\n * from - `ctrlKey` is the property for the `'control'` modifier, not `'controlKey'`.\n *\n * Kept in alphabetical order: combination keys sort their modifiers, and every\n * derived collection below inherits that order for free.\n */\nconst MODIFIER_ENTRIES = [\n ['alt', 'altKey'],\n ['control', 'ctrlKey'],\n ['meta', 'metaKey'],\n ['shift', 'shiftKey'],\n] as const satisfies ReadonlyArray<readonly [string, keyof KeyboardEvent]>;\n\nconst ALL_MODIFIER_VALUES = MODIFIER_ENTRIES.map(([name]) => name);\nconst MODIFIERS = new Set<string>(ALL_MODIFIER_VALUES);\n\n/** {@link MODIFIER_ENTRIES} as a lookup of modifier name to event property. */\nexport const MODIFIER_EVENT_KEYS: Record<string, string> =\n Object.fromEntries(MODIFIER_ENTRIES);\n\nfunction normalizeKeys(keys: string | string[]): string[] {\n return asArray(keys).map((key) => key.toLowerCase());\n}\n\n/**\n * Whether the event's key matches `key`, case-insensitively. Prefer this over\n * comparing `event.key` directly - the key names of this module are lowercase,\n * while real events carry the canonical casing (e.g. `Enter`).\n */\nexport function isKey(event: KeyboardEvent, key: string): boolean {\n return event.key.toLowerCase() === key.toLowerCase();\n}\n\nfunction isKeydown(event: Event): boolean {\n return event.type === 'keydown';\n}\n\nfunction isKeyup(event: Event): boolean {\n return event.type === 'keyup';\n}\n\n/** Sorts `modifiers` alphabetically, by filtering the already sorted source. */\nfunction sortModifiers(modifiers: string[]): string[] {\n return ALL_MODIFIER_VALUES.filter((mod) => modifiers.includes(mod));\n}\n\n/** Returns the modifiers active for `event`, already sorted. */\nfunction getActiveModifiers(event: KeyboardEvent): string[] {\n const active: string[] = [];\n\n for (const [name, property] of MODIFIER_ENTRIES) {\n if (event[property]) {\n active.push(name);\n }\n }\n\n return active;\n}\n\n/**\n * Creates a normalized combination key string from the provided keys and modifiers.\n *\n * The combination key is a string that uniquely identifies a specific combination of keys and modifiers.\n * It is created by sorting the keys and modifiers alphabetically and joining them with a '+' separator.\n *\n * `modifiers` must already be sorted - see {@link sortModifiers} and {@link getActiveModifiers}.\n */\nfunction createCombinationKey(keys: string[], modifiers: string[]): string {\n return modifiers.concat(keys.length > 1 ? keys.toSorted() : keys).join('+');\n}\n\n//#endregion\n\n/**\n * A controller for managing key bindings on a host element. It allows you to register handlers for specific key combinations,\n * with support for modifier keys and event options such as `preventDefault` and `stopPropagation`.\n *\n * The controller listens for keyboard events on the host element (or an optionally specified element) and invokes the appropriate handlers\n * when the registered key combinations are detected.\n * @hidden\n */\nclass KeyBindingController implements ReactiveController {\n //#region Private properties and state\n\n private static readonly _defaultOptions: KeyBindingControllerOptions = {\n skip: ['input', 'textarea', 'select'],\n bindingDefaults: { preventDefault: true },\n };\n\n private readonly _host: ReactiveControllerHost & Element;\n private readonly _ref?: Ref;\n private readonly _abortHandle = createAbortHandle();\n\n private readonly _bindings = new Map<string, KeyBinding>();\n private readonly _allowedKeys = new Set<string>();\n private readonly _pressedKeys = new Set<string>();\n\n private readonly _options: KeyBindingControllerOptions;\n private readonly _skipSelector: string | undefined;\n\n private _observedElement?: Element;\n\n private get _element(): Element {\n if (this._observedElement) {\n return this._observedElement;\n }\n return this._ref?.value || this._host;\n }\n\n //#endregion\n\n constructor(\n host: ReactiveControllerHost & Element,\n options?: KeyBindingControllerOptions\n ) {\n this._host = host;\n this._ref = options?.ref;\n this._options = toMerged(\n KeyBindingController._defaultOptions,\n options ?? {}\n );\n\n if (Array.isArray(this._options.skip)) {\n this._skipSelector = this._options.skip.join(',');\n }\n\n host.addController(this);\n }\n\n //#region Private API\n\n /**\n * Applies the event modifiers specified in the binding options to the provided keyboard event.\n * If `preventDefault` is set, it calls `event.preventDefault()`.\n * If `stopPropagation` is set, it calls `event.stopPropagation()`.\n */\n private _applyEventModifiers(\n binding: KeyBinding,\n event: KeyboardEvent\n ): void {\n if (binding.options?.preventDefault) {\n event.preventDefault();\n }\n\n if (binding.options?.stopPropagation) {\n event.stopPropagation();\n }\n }\n\n /**\n * Determines whether the provided keyboard event matches the specified key binding,\n * taking into account the event type and the binding's trigger options.\n */\n private _bindingMatches(binding: KeyBinding, event: KeyboardEvent): boolean {\n const triggers = binding.options?.triggers ?? ['keydown'];\n\n if (isKeydown(event) && triggers.includes('keydown')) {\n return !event.repeat || Boolean(binding.options?.repeat);\n }\n\n if (isKeyup(event) && triggers.includes('keyup')) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Determines whether the provided event should be ignored based on the controller's configuration and the event's context.\n * The method checks if the event's key is among the allowed keys, if the event originated from within the controller's scope,\n * and if it matches any of the skip conditions defined in the controller's options.\n */\n private _shouldSkip(event: KeyboardEvent, key: string): boolean {\n if (!this._allowedKeys.has(key)) {\n return true;\n }\n\n const path = event.composedPath();\n const element = this._element;\n\n if (!path.some((node) => node === element)) {\n return true;\n }\n\n const selector = this._skipSelector;\n\n if (selector) {\n return path.some((node) => isElement(node) && node.matches(selector));\n }\n\n const skip = this._options.skip;\n\n return isFunction(skip)\n ? skip.call(this._host, event.target as Element, event)\n : false;\n }\n\n //#endregion\n\n //#region Controller lifecycle\n\n /** @internal */\n public hostConnected(): void {\n const { signal } = this._abortHandle;\n this._host.addEventListener('keyup', this, { signal });\n this._host.addEventListener('keydown', this, { signal });\n globalThis.addEventListener('blur', this, { signal });\n }\n\n /** @internal */\n public hostDisconnected(): void {\n this._abortHandle.abort();\n }\n\n //#endregion\n\n //#region Event handling\n\n /**\n * Handles the global blur event to clear the internal state of pressed keys.\n *\n * This is necessary to prevent \"stuck\" keys when the user switches to another application\n * or tab while holding down a key.\n */\n private _handleGlobalBlur(): void {\n this._pressedKeys.clear();\n }\n\n /**\n * Handles keyboard events on the observed element.\n *\n * It checks if the event should be skipped based on the controller's configuration,\n * and if not, it determines if there is a registered handler for the combination of pressed keys and active modifiers.\n *\n * If a matching handler is found, it applies the specified event modifiers and invokes the handler.\n * It also manages the internal state of currently pressed keys to accurately detect key combinations.\n *\n */\n private _handleKeyEvent(event: KeyboardEvent): void {\n const key = event.key.toLowerCase();\n const isModifier = MODIFIERS.has(key);\n\n if (this._shouldSkip(event, key)) {\n // Always clean up on keyup regardless of whether the event is otherwise skipped.\n if (!isModifier && isKeyup(event)) {\n this._pressedKeys.delete(key);\n }\n return;\n }\n\n if (!isModifier) {\n this._pressedKeys.add(key);\n }\n\n const activeModifiers = getActiveModifiers(event);\n\n const combination = createCombinationKey(\n Array.from(this._pressedKeys),\n activeModifiers\n );\n let binding = this._bindings.get(combination);\n\n // When multiple non-modifier keys are simultaneously in _pressedKeys (due to overlapping\n // key presses, e.g. pressing ArrowUp before ArrowDown's keyup fires), the full combination\n // won't match any single-key binding. Fall back to just the current key + modifiers so that\n // single-key bindings continue to fire even when other keys are still \"held\".\n if (!binding && this._pressedKeys.size > 1) {\n binding = this._bindings.get(\n createCombinationKey([key], activeModifiers)\n );\n }\n\n if (binding && this._bindingMatches(binding, event)) {\n this._applyEventModifiers(binding, event);\n binding.handler.call(this._host, event);\n }\n\n if (!isModifier && isKeyup(event)) {\n this._pressedKeys.delete(key);\n }\n }\n\n /** @internal */\n public handleEvent(event: KeyboardEvent | FocusEvent): void {\n switch (event.type) {\n case 'keydown':\n case 'keyup':\n this._handleKeyEvent(event as KeyboardEvent);\n break;\n case 'blur':\n this._handleGlobalBlur();\n break;\n }\n }\n\n //#endregion\n\n //#region Public API\n\n /**\n * Registers a key binding with the specified key(s), handler function, and optional configuration.\n *\n * The `key` parameter can be a single key or an array of keys, and can include modifier keys (e.g., 'ctrl+s', ['shift', 'a']).\n * The `handler` is a function that will be called when the specified key combination is detected.\n * The `bindingOptions` allow you to customize the behavior of the binding, such as which event types trigger the handler,\n * whether it should fire on auto-repeated keydown events, and whether to call `preventDefault` or `stopPropagation`.\n *\n * The method returns the controller instance to allow for method chaining.\n */\n public set(\n key: string | string[],\n handler: KeyBindingHandler,\n bindingOptions?: KeyBindingOptions\n ): this {\n const { keys, modifiers } = parseKeys(key);\n const combination = createCombinationKey(keys, sortModifiers(modifiers));\n const options = toMerged(\n this._options.bindingDefaults!,\n bindingOptions ?? {}\n );\n\n for (const each of [...keys, ...modifiers]) {\n this._allowedKeys.add(each);\n }\n\n this._bindings.set(combination, { keys, handler, options, modifiers });\n\n return this;\n }\n\n /**\n * Registers the provided handler function to be called when either the Enter key or Space bar is pressed.\n *\n * This is a common pattern for activating buttons or interactive elements, and this method provides a convenient way to set up such bindings.\n *\n * The method accepts optional `KeyBindingOptions` which are applied to both the Enter key and Space bar bindings.\n * It returns the controller instance to allow for method chaining.\n *\n */\n public setActivateHandler(\n handler: KeyBindingHandler,\n options?: KeyBindingOptions\n ): this {\n this.set(enterKey, handler, options);\n this.set(spaceBar, handler, options);\n\n return this;\n }\n\n /**\n * Sets the controller to listen for keyboard events on an arbitrary `element` in the page context.\n * All the configuration and event handlers are applied as well.\n *\n * Returns an object with an `unsubscribe` function which should be called when the observing of keyboard\n * events on the `element` should cease.\n */\n public observeElement(element: Element): KeyBindingObserverCleanup {\n element.addEventListener('keydown', this);\n element.addEventListener('keyup', this);\n this._observedElement = element;\n\n return {\n unsubscribe: () => {\n element.removeEventListener('keydown', this);\n element.removeEventListener('keyup', this);\n if (this._observedElement === element) {\n this._observedElement = undefined;\n }\n },\n };\n }\n\n //#endregion\n}\n\n/**\n * Parses the provided key(s) and separates them into modifiers and regular keys.\n *\n * Modifiers are keys like Alt, Ctrl, Meta and Shift which modify the behavior of other keys when pressed in combination.\n * Regular keys are all other keys which trigger the bound handler when pressed.\n *\n * The returned `keys` and `modifiers` are normalized to lowercase for consistency.\n *\n * @internal\n *\n * @param inputKeys - The key or keys to parse, provided as a string or an array of strings.\n * @returns An object containing the separated `keys` and `modifiers`.\n */\nexport function parseKeys(inputKeys: string | string[]): {\n keys: string[];\n modifiers: string[];\n} {\n const [modifiers, keys] = partition(normalizeKeys(inputKeys), (key) =>\n MODIFIERS.has(key)\n );\n return { keys, modifiers };\n}\n\n/**\n * Controller factory function which creates a {@link KeyBindingController} instance and attaches it to the provided host.\n *\n * @param element - The host element to which the controller will be attached.\n * @param options - Optional configuration for the controller.\n * @returns The created {@link KeyBindingController} instance.\n *\n * @example\n * ```ts\n * class MyComponent extends LitElement {\n * private _keyBindings = addKeybindings(this, {\n * skip: ['input', 'textarea'], // Optional: Skip key events originating from these elements\n * bindingDefaults: { preventDefault: true }, // Optional: Default options for all bindings\n * });\n *\n * constructor() {\n * super();\n * this._keyBindings.set('ctrl+s', this._handleSave); // Register a key binding\n * }\n * ```\n */\nexport function addKeybindings(\n element: ReactiveControllerHost & Element,\n options?: KeyBindingControllerOptions\n): KeyBindingController {\n return new KeyBindingController(element, options);\n}\n\nexport type {\n KeyBindingController,\n KeyBindingControllerOptions,\n KeyBindingHandler,\n KeyBindingObserverCleanup,\n KeyBindingOptions,\n KeyBindingSkipCallback,\n KeyBindingTrigger,\n};\n"]}
@@ -0,0 +1,72 @@
1
+ import type { ReactiveControllerHost } from 'lit';
2
+ import { type KeyBindingControllerOptions, type KeyBindingOptions } from './key-bindings.js';
3
+ type RovingFocusHost = ReactiveControllerHost & HTMLElement;
4
+ /**
5
+ * Whether an arrow axis navigates. A predicate keeps the keys bound - and
6
+ * their default behavior suppressed - while gating the navigation itself,
7
+ * for hosts that switch orientation at runtime.
8
+ */
9
+ type RovingFocusAxis = boolean | (() => boolean);
10
+ type RovingFocusControllerOptions<T extends Element> = {
11
+ /**
12
+ * The keyboard-navigable items, in order, with non-interactive ones
13
+ * already excluded.
14
+ */
15
+ items: () => T[];
16
+ /**
17
+ * The item navigation moves relative to - typically resolved from the
18
+ * focused element. Arrow navigation and activation are no-ops while nullish.
19
+ */
20
+ current: () => T | null | undefined;
21
+ /**
22
+ * Moves focus to `item`. The host owns the focus delegation and any side
23
+ * effects riding along with it (scrolling, selection-follows-focus).
24
+ */
25
+ focusItem: (item: T) => void;
26
+ /**
27
+ * Invoked with the current item on Enter/Space, when the item is part of
28
+ * {@link RovingFocusControllerOptions.items}. Omit to skip the activation
29
+ * binding altogether.
30
+ */
31
+ activateItem?: (item: T) => void;
32
+ /** Binding options for the activation keys. */
33
+ activateOptions?: KeyBindingOptions;
34
+ /**
35
+ * Whether ArrowLeft/ArrowRight navigate, following the writing direction.
36
+ * Defaults to `true`.
37
+ */
38
+ horizontal?: RovingFocusAxis;
39
+ /** Whether ArrowUp/ArrowDown navigate. Defaults to `false`. */
40
+ vertical?: RovingFocusAxis;
41
+ /** Whether Home/End jump to the first/last item. Defaults to `true`. */
42
+ homeEnd?: boolean;
43
+ /**
44
+ * How arrow navigation treats a nullish current item:
45
+ * - `skip` - do nothing (default)
46
+ * - `wrap` - navigate from just outside the list, so "next" lands on the
47
+ * first item and "previous" on the last one
48
+ */
49
+ missingCurrent?: 'skip' | 'wrap';
50
+ /** Options forwarded to the underlying key-bindings controller. */
51
+ keybindings?: KeyBindingControllerOptions;
52
+ };
53
+ /**
54
+ * Implements the roving keyboard navigation shared by the container
55
+ * components: Home/End jumps and wrapping ArrowKey moves over a flat list of
56
+ * enabled items, relative to the currently focused one, with the horizontal
57
+ * axis following the writing direction.
58
+ *
59
+ * The host supplies the item list, the current-item resolution and the focus
60
+ * delegation; hierarchical structures (the tree) need their own navigation.
61
+ */
62
+ declare class RovingFocusController<T extends Element> {
63
+ private readonly _host;
64
+ private readonly _options;
65
+ constructor(host: RovingFocusHost, options: RovingFocusControllerOptions<T>);
66
+ private _focus;
67
+ private _move;
68
+ private _activate;
69
+ }
70
+ /** Creates and adds a {@link RovingFocusController} to the given host. */
71
+ export declare function addRovingFocusController<T extends Element>(host: RovingFocusHost, options: RovingFocusControllerOptions<T>): RovingFocusController<T>;
72
+ export type { RovingFocusController, RovingFocusControllerOptions };
@@ -0,0 +1,61 @@
1
+ import { firstOf, isEmpty, lastOf } from '../utils/arrays.js';
2
+ import { isLTR } from '../utils/dom.js';
3
+ import { wrap } from '../utils/math.js';
4
+ import { isFunction } from '../utils/types.js';
5
+ import { addKeybindings, arrowDown, arrowLeft, arrowRight, arrowUp, endKey, homeKey, } from './key-bindings.js';
6
+ class RovingFocusController {
7
+ constructor(host, options) {
8
+ this._host = host;
9
+ this._options = options;
10
+ const { horizontal = true, vertical = false, homeEnd = true, activateItem, activateOptions, keybindings, } = options;
11
+ const bindings = addKeybindings(host, keybindings);
12
+ if (horizontal) {
13
+ bindings
14
+ .set(arrowLeft, () => this._move(-1, horizontal, true))
15
+ .set(arrowRight, () => this._move(1, horizontal, true));
16
+ }
17
+ if (vertical) {
18
+ bindings
19
+ .set(arrowUp, () => this._move(-1, vertical, false))
20
+ .set(arrowDown, () => this._move(1, vertical, false));
21
+ }
22
+ if (homeEnd) {
23
+ bindings
24
+ .set(homeKey, () => this._focus(firstOf(this._options.items())))
25
+ .set(endKey, () => this._focus(lastOf(this._options.items())));
26
+ }
27
+ if (activateItem) {
28
+ bindings.setActivateHandler(() => this._activate(), activateOptions);
29
+ }
30
+ }
31
+ _focus(item) {
32
+ if (item) {
33
+ this._options.focusItem.call(this._host, item);
34
+ }
35
+ }
36
+ _move(delta, axis, rtlAware) {
37
+ if (isFunction(axis) && !axis.call(this._host)) {
38
+ return;
39
+ }
40
+ const items = this._options.items();
41
+ const current = this._options.current();
42
+ if (isEmpty(items) ||
43
+ (!current && this._options.missingCurrent !== 'wrap')) {
44
+ return;
45
+ }
46
+ const index = current ? items.indexOf(current) : -1;
47
+ const direction = rtlAware && !isLTR(this._host) ? -delta : delta;
48
+ const next = wrap(0, items.length - 1, index + direction);
49
+ this._focus(items[next]);
50
+ }
51
+ _activate() {
52
+ const current = this._options.current();
53
+ if (current && this._options.items().includes(current)) {
54
+ this._options.activateItem.call(this._host, current);
55
+ }
56
+ }
57
+ }
58
+ export function addRovingFocusController(host, options) {
59
+ return new RovingFocusController(host, options);
60
+ }
61
+ //# sourceMappingURL=roving-focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roving-focus.js","sourceRoot":"","sources":["../../../src/internals/controllers/roving-focus.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveControllerHost } from 'lit';\nimport { firstOf, isEmpty, lastOf } from '../utils/arrays.js';\nimport { isLTR } from '../utils/dom.js';\nimport { wrap } from '../utils/math.js';\nimport { isFunction } from '../utils/types.js';\nimport {\n addKeybindings,\n arrowDown,\n arrowLeft,\n arrowRight,\n arrowUp,\n endKey,\n homeKey,\n type KeyBindingControllerOptions,\n type KeyBindingOptions,\n} from './key-bindings.js';\n\ntype RovingFocusHost = ReactiveControllerHost & HTMLElement;\n\n/**\n * Whether an arrow axis navigates. A predicate keeps the keys bound - and\n * their default behavior suppressed - while gating the navigation itself,\n * for hosts that switch orientation at runtime.\n */\ntype RovingFocusAxis = boolean | (() => boolean);\n\ntype RovingFocusControllerOptions<T extends Element> = {\n /**\n * The keyboard-navigable items, in order, with non-interactive ones\n * already excluded.\n */\n items: () => T[];\n /**\n * The item navigation moves relative to - typically resolved from the\n * focused element. Arrow navigation and activation are no-ops while nullish.\n */\n current: () => T | null | undefined;\n /**\n * Moves focus to `item`. The host owns the focus delegation and any side\n * effects riding along with it (scrolling, selection-follows-focus).\n */\n focusItem: (item: T) => void;\n /**\n * Invoked with the current item on Enter/Space, when the item is part of\n * {@link RovingFocusControllerOptions.items}. Omit to skip the activation\n * binding altogether.\n */\n activateItem?: (item: T) => void;\n /** Binding options for the activation keys. */\n activateOptions?: KeyBindingOptions;\n /**\n * Whether ArrowLeft/ArrowRight navigate, following the writing direction.\n * Defaults to `true`.\n */\n horizontal?: RovingFocusAxis;\n /** Whether ArrowUp/ArrowDown navigate. Defaults to `false`. */\n vertical?: RovingFocusAxis;\n /** Whether Home/End jump to the first/last item. Defaults to `true`. */\n homeEnd?: boolean;\n /**\n * How arrow navigation treats a nullish current item:\n * - `skip` - do nothing (default)\n * - `wrap` - navigate from just outside the list, so \"next\" lands on the\n * first item and \"previous\" on the last one\n */\n missingCurrent?: 'skip' | 'wrap';\n /** Options forwarded to the underlying key-bindings controller. */\n keybindings?: KeyBindingControllerOptions;\n};\n\n/**\n * Implements the roving keyboard navigation shared by the container\n * components: Home/End jumps and wrapping ArrowKey moves over a flat list of\n * enabled items, relative to the currently focused one, with the horizontal\n * axis following the writing direction.\n *\n * The host supplies the item list, the current-item resolution and the focus\n * delegation; hierarchical structures (the tree) need their own navigation.\n */\nclass RovingFocusController<T extends Element> {\n private readonly _host: RovingFocusHost;\n private readonly _options: RovingFocusControllerOptions<T>;\n\n constructor(host: RovingFocusHost, options: RovingFocusControllerOptions<T>) {\n this._host = host;\n this._options = options;\n\n const {\n horizontal = true,\n vertical = false,\n homeEnd = true,\n activateItem,\n activateOptions,\n keybindings,\n } = options;\n\n const bindings = addKeybindings(host, keybindings);\n\n if (horizontal) {\n bindings\n .set(arrowLeft, () => this._move(-1, horizontal, true))\n .set(arrowRight, () => this._move(1, horizontal, true));\n }\n\n if (vertical) {\n bindings\n .set(arrowUp, () => this._move(-1, vertical, false))\n .set(arrowDown, () => this._move(1, vertical, false));\n }\n\n if (homeEnd) {\n bindings\n .set(homeKey, () => this._focus(firstOf(this._options.items())))\n .set(endKey, () => this._focus(lastOf(this._options.items())));\n }\n\n if (activateItem) {\n bindings.setActivateHandler(() => this._activate(), activateOptions);\n }\n }\n\n private _focus(item?: T): void {\n if (item) {\n this._options.focusItem.call(this._host, item);\n }\n }\n\n private _move(delta: -1 | 1, axis: RovingFocusAxis, rtlAware: boolean): void {\n if (isFunction(axis) && !axis.call(this._host)) {\n return;\n }\n\n const items = this._options.items();\n const current = this._options.current();\n\n if (\n isEmpty(items) ||\n (!current && this._options.missingCurrent !== 'wrap')\n ) {\n return;\n }\n\n // A current item outside the set resolves to -1 as well, wrapping the\n // navigation in from the closest list edge.\n const index = current ? items.indexOf(current) : -1;\n const direction = rtlAware && !isLTR(this._host) ? -delta : delta;\n const next = wrap(0, items.length - 1, index + direction);\n\n this._focus(items[next]);\n }\n\n private _activate(): void {\n const current = this._options.current();\n\n if (current && this._options.items().includes(current)) {\n this._options.activateItem!.call(this._host, current);\n }\n }\n}\n\n/** Creates and adds a {@link RovingFocusController} to the given host. */\nexport function addRovingFocusController<T extends Element>(\n host: RovingFocusHost,\n options: RovingFocusControllerOptions<T>\n): RovingFocusController<T> {\n return new RovingFocusController(host, options);\n}\n\nexport type { RovingFocusController, RovingFocusControllerOptions };\n"]}
@@ -71,6 +71,14 @@ declare class SlotController<T> implements ReactiveController {
71
71
  * See {@link SlotQueryOptions.flatten} and {@link SlotQueryOptions.selector} for more information.
72
72
  */
73
73
  getAssignedElements<U extends Element>(slot: T, options?: SlotQueryOptions): U[];
74
+ /**
75
+ * Returns the combined text content of the nodes assigned to `slot`,
76
+ * trimmed and with consecutive whitespace collapsed.
77
+ *
78
+ * Useful for deriving an accessible label from projected content.
79
+ * See {@link SlotQueryOptions.flatten} for the `flatten` semantics.
80
+ */
81
+ getAssignedText(slot: T, flatten?: boolean): string;
74
82
  /**
75
83
  * Return whether `slot` has assigned nodes.
76
84
  *
@@ -1,5 +1,6 @@
1
1
  import { isServer, } from 'lit';
2
2
  import { isEmpty } from '../utils/arrays.js';
3
+ import { normalizedTextContent } from '../utils/dom.js';
3
4
  const DefaultSlot = '[default]';
4
5
  class SlotController {
5
6
  constructor(host, options) {
@@ -40,6 +41,9 @@ class SlotController {
40
41
  ? elements.filter((e) => e.matches(options.selector))
41
42
  : elements;
42
43
  }
44
+ getAssignedText(slot, flatten = false) {
45
+ return normalizedTextContent(this.getAssignedNodes(slot, flatten));
46
+ }
43
47
  hasAssignedNodes(slot, flatten = false) {
44
48
  return !isEmpty(this.getAssignedNodes(slot, flatten));
45
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"slot.js","sourceRoot":"","sources":["../../../src/internals/controllers/slot.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isServer,\n type LitElement,\n type ReactiveController,\n type ReactiveControllerHost,\n} from 'lit';\nimport { isEmpty } from '../utils/arrays.js';\n\ntype InferSlotNames<T> = T extends readonly (infer U)[] ? U : never;\n\n/**\n * Additional query options for the slot controller methods.\n */\ntype SlotQueryOptions = {\n /**\n * If set to `true`, it returns a sequence of both the elements assigned to the queried slot,\n * as well as elements assigned to any other slots that are descendants of this slot. If no\n * assigned elements are found, it returns the slot's fallback content.\n *\n * @remarks\n * Mind that fallback content when checking whether a consumer projected anything:\n * for a slot rendered as `<slot>${this.label}</slot>`, a flattened query reports\n * the rendered `label` as assigned content even though nothing was projected.\n *\n * Defaults to `false`.\n */\n flatten?: boolean;\n /**\n * CSS selector used to filter the elements returned.\n */\n selector?: string;\n};\n\ntype SlotChangeCallback<T> = (\n parameters: SlotChangeCallbackParameters<T>\n) => void;\n\ntype SlotChangeCallbackParameters<T> = {\n /** The slot name that has its assigned nodes changed. */\n slot: T;\n /** `true` if the slot is the default slot. */\n isDefault: boolean;\n /** `true` if the callback handler is called for the initial host update. */\n isInitial: boolean;\n};\n\ntype SlotControllerOptions<T> = {\n /** An iterable collection of slot names to observe. */\n slots?: Iterable<T>;\n /** Callback function which is invoked a slot's assigned nodes change. */\n onChange?: SlotChangeCallback<T>;\n /** If set to `true`, the `onChange` callback is invoked once after the host is updated for the first time. */\n initial?: boolean;\n};\n\nconst DefaultSlot = '[default]';\n\nclass SlotController<T> implements ReactiveController {\n private readonly _host: ReactiveControllerHost & LitElement;\n private readonly _options: SlotControllerOptions<T>;\n private readonly _slots?: Set<T>;\n private readonly _slotCache = new Map<T | undefined, HTMLSlotElement>();\n private _initialized = false;\n\n constructor(\n host: ReactiveControllerHost & LitElement,\n options?: SlotControllerOptions<T>\n ) {\n this._host = host;\n this._host.addController(this);\n\n this._options = { ...options };\n this._slots = options?.slots ? new Set(options.slots) : undefined;\n }\n\n /**\n * The query results are cached, since the accessors below are routinely called\n * from a host's `render`. Only a still connected slot is served from the cache -\n * one removed by a conditional template falls back to a fresh query.\n *\n * There is no slot to find before the host creates its render root, which happens\n * when it connects. A query that comes earlier, such as one from an attribute that\n * the parser applies on upgrade, reports no slot instead of an error.\n */\n private _getSlot(slotName?: T): HTMLSlotElement | null {\n if (isServer) return null;\n\n const cached = this._slotCache.get(slotName);\n\n if (cached?.isConnected) {\n return cached;\n }\n\n const selector =\n slotName === DefaultSlot\n ? 'slot:not([name])'\n : `slot[name=\"${slotName}\"]`;\n const slot =\n this._host.renderRoot?.querySelector<HTMLSlotElement>(selector) ?? null;\n\n if (slot) {\n this._slotCache.set(slotName, slot);\n } else {\n this._slotCache.delete(slotName);\n }\n\n return slot;\n }\n\n /**\n * Returns an array of the assigned nodes for `slot`.\n *\n * If `flatten` is set to `true`, it returns a sequence of both the nodes assigned to the queried slot,\n * as well as nodes assigned to any other slots that are descendants of this slot. If no\n * assigned nodes are found, it returns the slot's fallback content.\n */\n public getAssignedNodes(slot: T, flatten = false): Node[] {\n return this._getSlot(slot)?.assignedNodes({ flatten }) ?? [];\n }\n\n /**\n * Returns an array of the assigned elements for `slot` with additional `options`.\n *\n * See {@link SlotQueryOptions.flatten} and {@link SlotQueryOptions.selector} for more information.\n */\n public getAssignedElements<U extends Element>(\n slot: T,\n options?: SlotQueryOptions\n ): U[] {\n const elements =\n (this._getSlot(slot)?.assignedElements({\n flatten: options?.flatten,\n }) as U[]) ?? [];\n\n return options?.selector\n ? elements.filter((e) => e.matches(options.selector!))\n : elements;\n }\n\n /**\n * Return whether `slot` has assigned nodes.\n *\n * If `flatten` is set to `true`, it returns a sequence of both the nodes assigned to the queried slot,\n * as well as nodes assigned to any other slots that are descendants of this slot. If no\n * assigned nodes are found, it returns the slot's fallback content - so a slot with\n * fallback content always reports as having nodes. See {@link SlotQueryOptions.flatten}.\n */\n public hasAssignedNodes(slot: T, flatten = false): boolean {\n return !isEmpty(this.getAssignedNodes(slot, flatten));\n }\n\n /**\n * Return whether `slot` has assigned elements accepting additional `options`.\n *\n * See {@link SlotQueryOptions.flatten} and {@link SlotQueryOptions.selector} for more information.\n */\n public hasAssignedElements(slot: T, options?: SlotQueryOptions): boolean {\n return !isEmpty(this.getAssignedElements(slot, options));\n }\n\n /** @internal */\n public handleEvent(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n const name = slot.name as T;\n const isDefault = name === '';\n const observed = isDefault ? (DefaultSlot as T) : name;\n\n if (!this._slots || this._slots.has(observed)) {\n this._options.onChange?.call(this._host, {\n slot: name,\n isDefault,\n isInitial: false,\n });\n this._host.requestUpdate();\n }\n }\n\n /** @internal */\n public hostConnected(): void {\n this._host.renderRoot.addEventListener('slotchange', this);\n }\n\n /** @internal */\n public hostDisconnected(): void {\n this._host.renderRoot.removeEventListener('slotchange', this);\n }\n\n /** @internal */\n public hostUpdated(): void {\n if (!this._initialized && this._options.initial) {\n this._initialized = true;\n this._options.onChange?.call(this._host, {\n slot: '<initial>' as T,\n isDefault: false,\n isInitial: true,\n });\n }\n }\n}\n\nfunction addSlotController<K extends readonly string[]>(\n host: ReactiveControllerHost,\n options?: SlotControllerOptions<InferSlotNames<K>> & {\n slots?: K;\n }\n): SlotController<InferSlotNames<K>> {\n return new SlotController(host as ReactiveControllerHost & LitElement, {\n ...options,\n slots: options?.slots as Iterable<InferSlotNames<K>>,\n });\n}\n\nfunction setSlots<const T extends readonly string[]>(...slots: T) {\n return [DefaultSlot, ...slots] as const;\n}\n\nexport type {\n InferSlotNames,\n SlotChangeCallback,\n SlotChangeCallbackParameters,\n SlotController,\n SlotControllerOptions,\n SlotQueryOptions,\n};\nexport { addSlotController, DefaultSlot, setSlots };\n"]}
1
+ {"version":3,"file":"slot.js","sourceRoot":"","sources":["../../../src/internals/controllers/slot.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,GAIT,MAAM,KAAK,CAAC;AACb,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiDxD,MAAM,WAAW,GAAG,WAAW,CAAC;AAEhC,MAAM,cAAc;IAOlB,YACE,IAAyC,EACzC,OAAkC;QALnB,eAAU,GAAG,IAAI,GAAG,EAAkC,CAAC;QAChE,iBAAY,GAAG,KAAK,CAAC;QAM3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAE/B,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,CAAC;IAWO,QAAQ,CAAC,QAAY;QAC3B,IAAI,QAAQ;YAAE,OAAO,IAAI,CAAC;QAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE7C,IAAI,MAAM,EAAE,WAAW,EAAE,CAAC;YACxB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,QAAQ,GACZ,QAAQ,KAAK,WAAW;YACtB,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,cAAc,QAAQ,IAAI,CAAC;QACjC,MAAM,IAAI,GACR,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,aAAa,CAAkB,QAAQ,CAAC,IAAI,IAAI,CAAC;QAE1E,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IASM,gBAAgB,CAAC,IAAO,EAAE,OAAO,GAAG,KAAK;QAC9C,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC;IAC/D,CAAC;IAOM,mBAAmB,CACxB,IAAO,EACP,OAA0B;QAE1B,MAAM,QAAQ,GACX,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC;YACrC,OAAO,EAAE,OAAO,EAAE,OAAO;SAC1B,CAAS,IAAI,EAAE,CAAC;QAEnB,OAAO,OAAO,EAAE,QAAQ;YACtB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAS,CAAC,CAAC;YACtD,CAAC,CAAC,QAAQ,CAAC;IACf,CAAC;IASM,eAAe,CAAC,IAAO,EAAE,OAAO,GAAG,KAAK;QAC7C,OAAO,qBAAqB,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,CAAC;IAUM,gBAAgB,CAAC,IAAO,EAAE,OAAO,GAAG,KAAK;QAC9C,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,CAAC;IAOM,mBAAmB,CAAC,IAAO,EAAE,OAA0B;QAC5D,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,CAAC;IAGM,WAAW,CAAC,KAAY;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAyB,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAS,CAAC;QAC5B,MAAM,SAAS,GAAG,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAE,WAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;QAEvD,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;gBACvC,IAAI,EAAE,IAAI;gBACV,SAAS;gBACT,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;IAGM,aAAa;QAClB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC7D,CAAC;IAGM,gBAAgB;QACrB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,mBAAmB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IAGM,WAAW;QAChB,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAChD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;gBACvC,IAAI,EAAE,WAAgB;gBACtB,SAAS,EAAE,KAAK;gBAChB,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF;AAED,SAAS,iBAAiB,CACxB,IAA4B,EAC5B,OAEC;IAED,OAAO,IAAI,cAAc,CAAC,IAA2C,EAAE;QACrE,GAAG,OAAO;QACV,KAAK,EAAE,OAAO,EAAE,KAAoC;KACrD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAoC,GAAG,KAAQ;IAC9D,OAAO,CAAC,WAAW,EAAE,GAAG,KAAK,CAAU,CAAC;AAC1C,CAAC;AAUD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC","sourcesContent":["import {\n isServer,\n type LitElement,\n type ReactiveController,\n type ReactiveControllerHost,\n} from 'lit';\nimport { isEmpty } from '../utils/arrays.js';\nimport { normalizedTextContent } from '../utils/dom.js';\n\ntype InferSlotNames<T> = T extends readonly (infer U)[] ? U : never;\n\n/**\n * Additional query options for the slot controller methods.\n */\ntype SlotQueryOptions = {\n /**\n * If set to `true`, it returns a sequence of both the elements assigned to the queried slot,\n * as well as elements assigned to any other slots that are descendants of this slot. If no\n * assigned elements are found, it returns the slot's fallback content.\n *\n * @remarks\n * Mind that fallback content when checking whether a consumer projected anything:\n * for a slot rendered as `<slot>${this.label}</slot>`, a flattened query reports\n * the rendered `label` as assigned content even though nothing was projected.\n *\n * Defaults to `false`.\n */\n flatten?: boolean;\n /**\n * CSS selector used to filter the elements returned.\n */\n selector?: string;\n};\n\ntype SlotChangeCallback<T> = (\n parameters: SlotChangeCallbackParameters<T>\n) => void;\n\ntype SlotChangeCallbackParameters<T> = {\n /** The slot name that has its assigned nodes changed. */\n slot: T;\n /** `true` if the slot is the default slot. */\n isDefault: boolean;\n /** `true` if the callback handler is called for the initial host update. */\n isInitial: boolean;\n};\n\ntype SlotControllerOptions<T> = {\n /** An iterable collection of slot names to observe. */\n slots?: Iterable<T>;\n /** Callback function which is invoked a slot's assigned nodes change. */\n onChange?: SlotChangeCallback<T>;\n /** If set to `true`, the `onChange` callback is invoked once after the host is updated for the first time. */\n initial?: boolean;\n};\n\nconst DefaultSlot = '[default]';\n\nclass SlotController<T> implements ReactiveController {\n private readonly _host: ReactiveControllerHost & LitElement;\n private readonly _options: SlotControllerOptions<T>;\n private readonly _slots?: Set<T>;\n private readonly _slotCache = new Map<T | undefined, HTMLSlotElement>();\n private _initialized = false;\n\n constructor(\n host: ReactiveControllerHost & LitElement,\n options?: SlotControllerOptions<T>\n ) {\n this._host = host;\n this._host.addController(this);\n\n this._options = { ...options };\n this._slots = options?.slots ? new Set(options.slots) : undefined;\n }\n\n /**\n * The query results are cached, since the accessors below are routinely called\n * from a host's `render`. Only a still connected slot is served from the cache -\n * one removed by a conditional template falls back to a fresh query.\n *\n * There is no slot to find before the host creates its render root, which happens\n * when it connects. A query that comes earlier, such as one from an attribute that\n * the parser applies on upgrade, reports no slot instead of an error.\n */\n private _getSlot(slotName?: T): HTMLSlotElement | null {\n if (isServer) return null;\n\n const cached = this._slotCache.get(slotName);\n\n if (cached?.isConnected) {\n return cached;\n }\n\n const selector =\n slotName === DefaultSlot\n ? 'slot:not([name])'\n : `slot[name=\"${slotName}\"]`;\n const slot =\n this._host.renderRoot?.querySelector<HTMLSlotElement>(selector) ?? null;\n\n if (slot) {\n this._slotCache.set(slotName, slot);\n } else {\n this._slotCache.delete(slotName);\n }\n\n return slot;\n }\n\n /**\n * Returns an array of the assigned nodes for `slot`.\n *\n * If `flatten` is set to `true`, it returns a sequence of both the nodes assigned to the queried slot,\n * as well as nodes assigned to any other slots that are descendants of this slot. If no\n * assigned nodes are found, it returns the slot's fallback content.\n */\n public getAssignedNodes(slot: T, flatten = false): Node[] {\n return this._getSlot(slot)?.assignedNodes({ flatten }) ?? [];\n }\n\n /**\n * Returns an array of the assigned elements for `slot` with additional `options`.\n *\n * See {@link SlotQueryOptions.flatten} and {@link SlotQueryOptions.selector} for more information.\n */\n public getAssignedElements<U extends Element>(\n slot: T,\n options?: SlotQueryOptions\n ): U[] {\n const elements =\n (this._getSlot(slot)?.assignedElements({\n flatten: options?.flatten,\n }) as U[]) ?? [];\n\n return options?.selector\n ? elements.filter((e) => e.matches(options.selector!))\n : elements;\n }\n\n /**\n * Returns the combined text content of the nodes assigned to `slot`,\n * trimmed and with consecutive whitespace collapsed.\n *\n * Useful for deriving an accessible label from projected content.\n * See {@link SlotQueryOptions.flatten} for the `flatten` semantics.\n */\n public getAssignedText(slot: T, flatten = false): string {\n return normalizedTextContent(this.getAssignedNodes(slot, flatten));\n }\n\n /**\n * Return whether `slot` has assigned nodes.\n *\n * If `flatten` is set to `true`, it returns a sequence of both the nodes assigned to the queried slot,\n * as well as nodes assigned to any other slots that are descendants of this slot. If no\n * assigned nodes are found, it returns the slot's fallback content - so a slot with\n * fallback content always reports as having nodes. See {@link SlotQueryOptions.flatten}.\n */\n public hasAssignedNodes(slot: T, flatten = false): boolean {\n return !isEmpty(this.getAssignedNodes(slot, flatten));\n }\n\n /**\n * Return whether `slot` has assigned elements accepting additional `options`.\n *\n * See {@link SlotQueryOptions.flatten} and {@link SlotQueryOptions.selector} for more information.\n */\n public hasAssignedElements(slot: T, options?: SlotQueryOptions): boolean {\n return !isEmpty(this.getAssignedElements(slot, options));\n }\n\n /** @internal */\n public handleEvent(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n const name = slot.name as T;\n const isDefault = name === '';\n const observed = isDefault ? (DefaultSlot as T) : name;\n\n if (!this._slots || this._slots.has(observed)) {\n this._options.onChange?.call(this._host, {\n slot: name,\n isDefault,\n isInitial: false,\n });\n this._host.requestUpdate();\n }\n }\n\n /** @internal */\n public hostConnected(): void {\n this._host.renderRoot.addEventListener('slotchange', this);\n }\n\n /** @internal */\n public hostDisconnected(): void {\n this._host.renderRoot.removeEventListener('slotchange', this);\n }\n\n /** @internal */\n public hostUpdated(): void {\n if (!this._initialized && this._options.initial) {\n this._initialized = true;\n this._options.onChange?.call(this._host, {\n slot: '<initial>' as T,\n isDefault: false,\n isInitial: true,\n });\n }\n }\n}\n\nfunction addSlotController<K extends readonly string[]>(\n host: ReactiveControllerHost,\n options?: SlotControllerOptions<InferSlotNames<K>> & {\n slots?: K;\n }\n): SlotController<InferSlotNames<K>> {\n return new SlotController(host as ReactiveControllerHost & LitElement, {\n ...options,\n slots: options?.slots as Iterable<InferSlotNames<K>>,\n });\n}\n\nfunction setSlots<const T extends readonly string[]>(...slots: T) {\n return [DefaultSlot, ...slots] as const;\n}\n\nexport type {\n InferSlotNames,\n SlotChangeCallback,\n SlotChangeCallbackParameters,\n SlotController,\n SlotControllerOptions,\n SlotQueryOptions,\n};\nexport { addSlotController, DefaultSlot, setSlots };\n"]}