@lion/ui 0.0.3 → 0.0.4

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 (105) hide show
  1. package/components/core/src/SlotMixin.js +180 -42
  2. package/components/core/test/SlotMixin.test.js +341 -138
  3. package/components/core/types/SlotMixinTypes.ts +12 -1
  4. package/components/input-tel/src/LionInputTel.js +0 -9
  5. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +32 -29
  6. package/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js +5 -6
  7. package/components/localize/types/LocalizeMixinTypes.ts +1 -1
  8. package/custom-elements.json +1435 -133
  9. package/dist-types/components/calendar/src/LionCalendar.d.ts +1 -37
  10. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  11. package/dist-types/components/core/src/SlotMixin.d.ts +2 -0
  12. package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
  13. package/dist-types/components/core/test/SlotMixin.test.d.ts +1 -1
  14. package/dist-types/components/core/test/SlotMixin.test.d.ts.map +1 -1
  15. package/dist-types/components/core/types/SlotMixinTypes.d.ts +9 -1
  16. package/dist-types/components/core/types/SlotMixinTypes.d.ts.map +1 -1
  17. package/dist-types/components/input-amount/src/LionInputAmount.d.ts +1 -37
  18. package/dist-types/components/input-amount/src/LionInputAmount.d.ts.map +1 -1
  19. package/dist-types/components/input-date/src/LionInputDate.d.ts +1 -37
  20. package/dist-types/components/input-date/src/LionInputDate.d.ts.map +1 -1
  21. package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts +1 -37
  22. package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts.map +1 -1
  23. package/dist-types/components/input-email/src/LionInputEmail.d.ts +1 -37
  24. package/dist-types/components/input-email/src/LionInputEmail.d.ts.map +1 -1
  25. package/dist-types/components/input-iban/src/LionInputIban.d.ts +1 -37
  26. package/dist-types/components/input-iban/src/LionInputIban.d.ts.map +1 -1
  27. package/dist-types/components/input-tel/src/LionInputTel.d.ts +1 -42
  28. package/dist-types/components/input-tel/src/LionInputTel.d.ts.map +1 -1
  29. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts +14 -8
  30. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
  31. package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.d.ts.map +1 -1
  32. package/dist-types/components/localize/src/LocalizeMixin.d.ts +1 -37
  33. package/dist-types/components/localize/src/LocalizeMixin.d.ts.map +1 -1
  34. package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts +1 -1
  35. package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts.map +1 -1
  36. package/dist-types/components/pagination/src/LionPagination.d.ts +1 -37
  37. package/dist-types/components/pagination/src/LionPagination.d.ts.map +1 -1
  38. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts +1 -37
  39. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts.map +1 -1
  40. package/dist-types/exports/core.d.ts +1 -0
  41. package/dist-types/exports/core.d.ts.map +1 -1
  42. package/dist-types/exports/form-core.d.ts +2 -0
  43. package/dist-types/exports/form-core.d.ts.map +1 -1
  44. package/dist-types/exports/input-tel-dropdown.d.ts +2 -0
  45. package/dist-types/exports/input-tel-dropdown.d.ts.map +1 -1
  46. package/dist-types/exports/input-tel.d.ts +1 -0
  47. package/dist-types/exports/input-tel.d.ts.map +1 -1
  48. package/dist-types/exports/localize.d.ts +3 -0
  49. package/dist-types/exports/localize.d.ts.map +1 -1
  50. package/dist-types/exports/overlays.d.ts +4 -0
  51. package/dist-types/exports/overlays.d.ts.map +1 -1
  52. package/dist-types/tsconfig.tsbuildinfo +1 -1
  53. package/docs/components/checkbox-group/overview.md +2 -2
  54. package/docs/components/collapsible/overview.md +1 -1
  55. package/docs/components/combobox/overview.md +1 -1
  56. package/docs/components/combobox/use-cases.md +1 -1
  57. package/docs/components/dialog/overview.md +1 -1
  58. package/docs/components/fieldset/overview.md +5 -5
  59. package/docs/components/form/overview.md +3 -3
  60. package/docs/components/icon/use-cases.md +1 -1
  61. package/docs/components/input/overview.md +1 -1
  62. package/docs/components/input-amount/overview.md +3 -3
  63. package/docs/components/input-date/overview.md +3 -3
  64. package/docs/components/input-datepicker/overview.md +5 -5
  65. package/docs/components/input-email/overview.md +2 -2
  66. package/docs/components/input-iban/overview.md +2 -2
  67. package/docs/components/input-range/overview.md +2 -2
  68. package/docs/components/input-stepper/overview.md +1 -1
  69. package/docs/components/input-tel/overview.md +1 -1
  70. package/docs/components/input-tel/use-cases.md +6 -6
  71. package/docs/components/input-tel-dropdown/overview.md +1 -1
  72. package/docs/components/radio-group/overview.md +3 -3
  73. package/docs/components/select-rich/use-cases.md +1 -1
  74. package/docs/components/tabs/examples.md +1 -1
  75. package/docs/fundamentals/index.md +4 -4
  76. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  77. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  78. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  79. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  80. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  81. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  82. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  83. package/docs/fundamentals/systems/core/overview.md +1 -1
  84. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  85. package/docs/fundamentals/systems/form/model-value.md +1 -1
  86. package/docs/fundamentals/systems/form/overview.md +28 -28
  87. package/docs/fundamentals/systems/form/validate.md +1 -1
  88. package/docs/fundamentals/systems/localize/overview.md +4 -4
  89. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  90. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  91. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  92. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  93. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  94. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  95. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  96. package/docs/guides/index.md +8 -8
  97. package/docs/guides/principles/index.md +5 -5
  98. package/docs/guides/principles/styling.md +1 -1
  99. package/exports/core.js +2 -0
  100. package/exports/form-core.js +3 -0
  101. package/exports/input-tel-dropdown.js +3 -0
  102. package/exports/input-tel.js +2 -0
  103. package/exports/localize.js +4 -0
  104. package/exports/overlays.js +5 -0
  105. package/package.json +3 -2
@@ -6,10 +6,22 @@ import { isTemplateResult } from 'lit/directive-helpers.js';
6
6
  /**
7
7
  * @typedef {import('../types/SlotMixinTypes.js').SlotMixin} SlotMixin
8
8
  * @typedef {import('../types/SlotMixinTypes.js').SlotsMap} SlotsMap
9
+ * @typedef {import('../types/SlotMixinTypes.js').SlotFunctionResult} SlotFunctionResult
10
+ * @typedef {import('../types/SlotMixinTypes.js').SlotRerenderObject} SlotRerenderObject
9
11
  * @typedef {import('lit').LitElement} LitElement
10
12
  */
11
13
 
14
+ const isRerenderConfig = (/** @type {SlotFunctionResult} */ o) =>
15
+ !Array.isArray(o) && typeof o === 'object' && 'template' in o;
16
+
12
17
  /**
18
+ * All intricacies involved in managing light dom can be delegated to SlotMixin. Amongst others, it automatically:
19
+ * - mediates between light dom provided by the user ('public slots') and light dom provided by the component author ('private slots').
20
+ * - allows to hook into the reactive update loop of LitElement (rerendering on property changes)
21
+ * - respects the scoped registry belonging to the shadow root.
22
+ *
23
+ * Be sure to read all details about SlotMixin in the [SlotMixin documentation](docs/fundamentals/systems/core/SlotMixin.md)
24
+ *
13
25
  * @type {SlotMixin}
14
26
  * @param {import('@open-wc/dedupe-mixin').Constructor<LitElement>} superclass
15
27
  */
@@ -25,8 +37,35 @@ const SlotMixinImplementation = superclass =>
25
37
 
26
38
  constructor() {
27
39
  super();
28
- /** @private */
29
- this.__privateSlots = new Set(null);
40
+
41
+ /**
42
+ * The roots that are used to do a rerender in.
43
+ * In case of a SlotRerenderObject, this will create a render wrapper in the scoped context (shadow root)
44
+ * and connect this under the slot name to the light dom. All (re)renders will happen from here,
45
+ * so that all interactions work as intended and no focus issues can arise (which would be the case
46
+ * when (cloned) nodes of a render outcome would be moved around)
47
+ * @private
48
+ * @type { Map<string, HTMLDivElement> } */
49
+ this.__scopedRenderRoots = new Map();
50
+ /**
51
+ * @private
52
+ * @type {Set<string>}
53
+ */
54
+ this.__slotsThatNeedRerender = new Set();
55
+ /**
56
+ * Those are slots that should not be touched by SlotMixin
57
+ * @private
58
+ * @type {Set<string>}
59
+ */
60
+ this.__slotsProvidedByUserOnFirstConnected = new Set();
61
+ /**
62
+ * Those are slots that should be touched by SlotMixin
63
+ * The opposite of __slotsProvidedByUserOnFirstConnected,
64
+ * also taking into account undefined (a.k.a. conditional) slots
65
+ * @private
66
+ * @type {Set<string>}
67
+ */
68
+ this.__privateSlots = new Set();
30
69
  }
31
70
 
32
71
  connectedCallback() {
@@ -34,61 +73,160 @@ const SlotMixinImplementation = superclass =>
34
73
  this._connectSlotMixin();
35
74
  }
36
75
 
76
+ /**
77
+ * @param {string} slotName
78
+ */
79
+ __rerenderSlot(slotName) {
80
+ const slotFunctionResult = /** @type {SlotRerenderObject} */ (this.slots[slotName]());
81
+ this.__renderTemplateInScopedContext({
82
+ template: slotFunctionResult.template,
83
+ slotName,
84
+ shouldRerender: true,
85
+ });
86
+ slotFunctionResult.afterRender?.();
87
+ }
88
+
89
+ /**
90
+ * Here we rerender slots defined with a `SlotRerenderObject`
91
+ * @param {import('lit-element').PropertyValues } changedProperties
92
+ */
93
+ updated(changedProperties) {
94
+ super.updated(changedProperties);
95
+
96
+ if (this.__slotsThatNeedRerender.size) {
97
+ for (const slotName of Array.from(this.__slotsThatNeedRerender)) {
98
+ this.__rerenderSlot(slotName);
99
+ }
100
+ }
101
+
102
+ if (this.__isFirstSlotUpdate) {
103
+ this.__isFirstSlotUpdate = false;
104
+ }
105
+ }
106
+
37
107
  /**
38
108
  * @private
39
- * @param {import('lit').TemplateResult} template
109
+ * @param {object} opts
110
+ * @param {import('lit').TemplateResult} opts.template
111
+ * @param {string} opts.slotName
112
+ * @param {boolean} [opts.shouldRerender] false when TemplateResult, true when SlotRerenderObject
40
113
  */
41
- __renderAsNodes(template) {
114
+ __renderTemplateInScopedContext({ template, slotName, shouldRerender }) {
42
115
  // @ts-expect-error wait for browser support
43
116
  const supportsScopedRegistry = !!ShadowRoot.prototype.createElement;
44
117
  const registryRoot = supportsScopedRegistry ? this.shadowRoot : document;
45
- // @ts-expect-error wait for browser support
46
- const tempRenderTarget = registryRoot.createElement('div');
118
+
119
+ let renderTarget;
120
+ // Reuse the existing offline renderTargets for results consistent with that of rendering to one target (shadow dom)
121
+ if (this.__scopedRenderRoots.has(slotName)) {
122
+ renderTarget = this.__scopedRenderRoots.get(slotName);
123
+ } else {
124
+ // @ts-expect-error wait for browser support
125
+ renderTarget = registryRoot.createElement('div');
126
+ if (shouldRerender) {
127
+ renderTarget.slot = slotName;
128
+ this.appendChild(renderTarget);
129
+ }
130
+ this.__scopedRenderRoots.set(slotName, renderTarget);
131
+ }
132
+
47
133
  // Providing all options breaks Safari; keep host and creationScope
48
134
  const { creationScope, host } = this.renderOptions;
49
- render(template, tempRenderTarget, { creationScope, host });
50
- return Array.from(tempRenderTarget.childNodes);
135
+ render(template, renderTarget, { creationScope, host });
136
+
137
+ return renderTarget;
138
+ }
139
+
140
+ /**
141
+ * @param {object} options
142
+ * @param {Node[]} options.nodes
143
+ * @param {Element} [options.renderParent] It's recommended to create a render target in light dom (like <div slot=myslot>),
144
+ * which can be used as a render target for most
145
+ * @param {string} options.slotName For the first render, it's best to use slotName
146
+ */
147
+ __appendNodesForOneTimeRender({ nodes, renderParent = this, slotName }) {
148
+ for (const node of nodes) {
149
+ if (!(node instanceof Node)) {
150
+ return;
151
+ }
152
+ // Here, we add the slot name to the node that is an element
153
+ // (ignoring helper comment nodes that might be in our nodes array)
154
+ if (node instanceof Element && slotName && slotName !== '') {
155
+ node.setAttribute('slot', slotName);
156
+ }
157
+ renderParent.appendChild(node);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Here we look what's inside our `get slots`.
163
+ * Rerenderable slots get scheduled and "one time slots" get rendered once on connected
164
+ * @param {string[]} slotNames
165
+ */
166
+ __initSlots(slotNames) {
167
+ for (const slotName of slotNames) {
168
+ if (this.__slotsProvidedByUserOnFirstConnected.has(slotName)) {
169
+ // eslint-disable-next-line no-continue
170
+ continue;
171
+ }
172
+
173
+ const slotFunctionResult = this.slots[slotName]();
174
+ // Allow to conditionally return a slot
175
+ if (slotFunctionResult === undefined) {
176
+ return;
177
+ }
178
+
179
+ if (!this.__isConnectedSlotMixin) {
180
+ this.__privateSlots.add(slotName);
181
+ }
182
+
183
+ if (isTemplateResult(slotFunctionResult)) {
184
+ const renderTarget = this.__renderTemplateInScopedContext({
185
+ template: slotFunctionResult,
186
+ slotName,
187
+ });
188
+ const nodes = Array.from(renderTarget.childNodes);
189
+ this.__appendNodesForOneTimeRender({ nodes, renderParent: this, slotName });
190
+ } else if (slotFunctionResult instanceof Node) {
191
+ const nodes = [/** @type {Node} */ (slotFunctionResult)];
192
+ this.__appendNodesForOneTimeRender({ nodes, renderParent: this, slotName });
193
+ } else if (isRerenderConfig(slotFunctionResult)) {
194
+ // Rerenderable slots are scheduled in the "updated loop"
195
+ this.__slotsThatNeedRerender.add(slotName);
196
+ } else {
197
+ throw new Error(
198
+ 'Please provide a function inside "get slots()" returning TemplateResult | Node | {template:TemplateResult, afterRender?:function} | undefined',
199
+ );
200
+ }
201
+ }
51
202
  }
52
203
 
53
204
  /**
54
205
  * @protected
55
206
  */
56
207
  _connectSlotMixin() {
57
- if (!this.__isConnectedSlotMixin) {
58
- Object.keys(this.slots).forEach(slotName => {
59
- const hasSlottableFromUser =
60
- slotName === ''
61
- ? // for default slot (''), we can't use el.slot because polyfill for IE11
62
- // will do .querySelector('[slot=]') which produces a fatal error
63
- // therefore we check if there's children that do not have a slot attr
64
- Array.from(this.children).find(el => !el.hasAttribute('slot'))
65
- : Array.from(this.children).find(el => el.slot === slotName);
66
-
67
- if (!hasSlottableFromUser) {
68
- const slotContent = this.slots[slotName]();
69
- /** @type {Node[]} */
70
- let nodes = [];
71
-
72
- if (isTemplateResult(slotContent)) {
73
- nodes = this.__renderAsNodes(slotContent);
74
- } else if (!Array.isArray(slotContent)) {
75
- nodes = [/** @type {Node} */ (slotContent)];
76
- }
77
-
78
- nodes.forEach(node => {
79
- if (!(node instanceof Node)) {
80
- return;
81
- }
82
- if (node instanceof Element && slotName !== '') {
83
- node.setAttribute('slot', slotName);
84
- }
85
- this.appendChild(node);
86
- this.__privateSlots.add(slotName);
87
- });
88
- }
89
- });
90
- this.__isConnectedSlotMixin = true;
208
+ if (this.__isConnectedSlotMixin) {
209
+ return;
91
210
  }
211
+
212
+ const allSlots = Object.keys(this.slots);
213
+
214
+ for (const slotName of allSlots) {
215
+ const hasSlottableFromUser =
216
+ slotName === ''
217
+ ? // for default slot (''), we can't use el.slot because polyfill for IE11
218
+ // will do .querySelector('[slot=]') which produces a fatal error
219
+ // therefore we check if there's children that do not have a slot attr
220
+ Array.from(this.children).find(el => !el.hasAttribute('slot'))
221
+ : Array.from(this.children).find(el => el.slot === slotName);
222
+
223
+ if (hasSlottableFromUser) {
224
+ this.__slotsProvidedByUserOnFirstConnected.add(slotName);
225
+ }
226
+ }
227
+
228
+ this.__initSlots(allSlots);
229
+ this.__isConnectedSlotMixin = true;
92
230
  }
93
231
 
94
232
  /**