@everymatrix/general-input 1.22.0 → 1.22.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.
@@ -1,6 +1,299 @@
1
- import { i, r as registerStyles, N as TabindexMixin, l as FocusMixin, E as ElementMixin, T as ThemableMixin, C as ControllerMixin, P as PolymerElement, h as html, g as TooltipController } from './field-mixin.js';
1
+ import { o, i, d as dedupingMixin, u as usageStatistics, T as TabindexMixin, j as FocusMixin, P as PolymerElement, h as html } from './field-mixin.js';
2
2
  import { A as ActiveMixin } from './active-mixin.js';
3
3
 
4
+ /**
5
+ * @license
6
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
7
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8
+ */
9
+ /**
10
+ * @polymerMixin
11
+ */
12
+ const ThemePropertyMixin = (superClass) =>
13
+ class VaadinThemePropertyMixin extends superClass {
14
+ static get properties() {
15
+ return {
16
+ /**
17
+ * Helper property with theme attribute value facilitating propagation
18
+ * in shadow DOM.
19
+ *
20
+ * Enables the component implementation to propagate the `theme`
21
+ * attribute value to the sub-components in Shadow DOM by binding
22
+ * the sub-component's "theme" attribute to the `theme` property of
23
+ * the host.
24
+ *
25
+ * **NOTE:** Extending the mixin only provides the property for binding,
26
+ * and does not make the propagation alone.
27
+ *
28
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/styling-components/#sub-components).
29
+ * page for more information.
30
+ *
31
+ * @protected
32
+ */
33
+ _theme: {
34
+ type: String,
35
+ readOnly: true,
36
+ },
37
+ };
38
+ }
39
+
40
+ static get observedAttributes() {
41
+ return [...super.observedAttributes, 'theme'];
42
+ }
43
+
44
+ /** @protected */
45
+ attributeChangedCallback(name, oldValue, newValue) {
46
+ super.attributeChangedCallback(name, oldValue, newValue);
47
+
48
+ if (name === 'theme') {
49
+ this._set_theme(newValue);
50
+ }
51
+ }
52
+ };
53
+
54
+ /**
55
+ * @license
56
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
57
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
58
+ */
59
+
60
+ /**
61
+ * @typedef {Object} Theme
62
+ * @property {string} themeFor
63
+ * @property {CSSResult[]} styles
64
+ * @property {string | string[]} [include]
65
+ * @property {string} [moduleId]
66
+ *
67
+ * @typedef {CSSResult[] | CSSResult} CSSResultGroup
68
+ */
69
+
70
+ /**
71
+ * @type {Theme[]}
72
+ */
73
+ const themeRegistry = [];
74
+
75
+ /**
76
+ * Check if the custom element type has themes applied.
77
+ * @param {Function} elementClass
78
+ * @returns {boolean}
79
+ */
80
+ function classHasThemes(elementClass) {
81
+ return elementClass && Object.prototype.hasOwnProperty.call(elementClass, '__themes');
82
+ }
83
+
84
+ /**
85
+ * Check if the custom element type has themes applied.
86
+ * @param {string} tagName
87
+ * @returns {boolean}
88
+ */
89
+ function hasThemes(tagName) {
90
+ return classHasThemes(customElements.get(tagName));
91
+ }
92
+
93
+ /**
94
+ * Flattens the styles into a single array of styles.
95
+ * @param {CSSResultGroup} styles
96
+ * @param {CSSResult[]} result
97
+ * @returns {CSSResult[]}
98
+ */
99
+ function flattenStyles(styles = []) {
100
+ return [styles].flat(Infinity).filter((style) => {
101
+ if (style instanceof o) {
102
+ return true;
103
+ }
104
+ console.warn('An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`.');
105
+ return false;
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Registers CSS styles for a component type. Make sure to register the styles before
111
+ * the first instance of a component of the type is attached to DOM.
112
+ *
113
+ * @param {string} themeFor The local/tag name of the component type to register the styles for
114
+ * @param {CSSResultGroup} styles The CSS style rules to be registered for the component type
115
+ * matching themeFor and included in the local scope of each component instance
116
+ * @param {{moduleId?: string, include?: string | string[]}} options Additional options
117
+ * @return {void}
118
+ */
119
+ function registerStyles(themeFor, styles, options = {}) {
120
+ if (themeFor) {
121
+ if (hasThemes(themeFor)) {
122
+ console.warn(`The custom element definition for "${themeFor}"
123
+ was finalized before a style module was registered.
124
+ Make sure to add component specific style modules before
125
+ importing the corresponding custom element.`);
126
+ }
127
+ }
128
+
129
+ styles = flattenStyles(styles);
130
+
131
+ if (window.Vaadin && window.Vaadin.styleModules) {
132
+ window.Vaadin.styleModules.registerStyles(themeFor, styles, options);
133
+ } else {
134
+ themeRegistry.push({
135
+ themeFor,
136
+ styles,
137
+ include: options.include,
138
+ moduleId: options.moduleId,
139
+ });
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Returns all registered themes. By default the themeRegistry is returned as is.
145
+ * In case the style-modules adapter is imported, the themes are obtained from there instead
146
+ * @returns {Theme[]}
147
+ */
148
+ function getAllThemes() {
149
+ if (window.Vaadin && window.Vaadin.styleModules) {
150
+ return window.Vaadin.styleModules.getAllThemes();
151
+ }
152
+ return themeRegistry;
153
+ }
154
+
155
+ /**
156
+ * Returns true if the themeFor string matches the tag name
157
+ * @param {string} themeFor
158
+ * @param {string} tagName
159
+ * @returns {boolean}
160
+ */
161
+ function matchesThemeFor(themeFor, tagName) {
162
+ return (themeFor || '').split(' ').some((themeForToken) => {
163
+ return new RegExp(`^${themeForToken.split('*').join('.*')}$`, 'u').test(tagName);
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Maps the moduleName to an include priority number which is used for
169
+ * determining the order in which styles are applied.
170
+ * @param {string} moduleName
171
+ * @returns {number}
172
+ */
173
+ function getIncludePriority(moduleName = '') {
174
+ let includePriority = 0;
175
+ if (moduleName.startsWith('lumo-') || moduleName.startsWith('material-')) {
176
+ includePriority = 1;
177
+ } else if (moduleName.startsWith('vaadin-')) {
178
+ includePriority = 2;
179
+ }
180
+ return includePriority;
181
+ }
182
+
183
+ /**
184
+ * Gets an array of CSSResults matching the include property of the theme.
185
+ * @param {Theme} theme
186
+ * @returns {CSSResult[]}
187
+ */
188
+ function getIncludedStyles(theme) {
189
+ const includedStyles = [];
190
+ if (theme.include) {
191
+ [].concat(theme.include).forEach((includeModuleId) => {
192
+ const includedTheme = getAllThemes().find((s) => s.moduleId === includeModuleId);
193
+ if (includedTheme) {
194
+ includedStyles.push(...getIncludedStyles(includedTheme), ...includedTheme.styles);
195
+ } else {
196
+ console.warn(`Included moduleId ${includeModuleId} not found in style registry`);
197
+ }
198
+ }, theme.styles);
199
+ }
200
+ return includedStyles;
201
+ }
202
+
203
+ /**
204
+ * Includes the styles to the template.
205
+ * @param {CSSResult[]} styles
206
+ * @param {HTMLTemplateElement} template
207
+ */
208
+ function addStylesToTemplate(styles, template) {
209
+ const styleEl = document.createElement('style');
210
+ styleEl.innerHTML = styles.map((style) => style.cssText).join('\n');
211
+ template.content.appendChild(styleEl);
212
+ }
213
+
214
+ /**
215
+ * Returns an array of themes that should be used for styling a component matching
216
+ * the tag name. The array is sorted by the include order.
217
+ * @param {string} tagName
218
+ * @returns {Theme[]}
219
+ */
220
+ function getThemes(tagName) {
221
+ const defaultModuleName = `${tagName}-default-theme`;
222
+
223
+ const themes = getAllThemes()
224
+ // Filter by matching themeFor properties
225
+ .filter((theme) => theme.moduleId !== defaultModuleName && matchesThemeFor(theme.themeFor, tagName))
226
+ .map((theme) => ({
227
+ ...theme,
228
+ // Prepend styles from included themes
229
+ styles: [...getIncludedStyles(theme), ...theme.styles],
230
+ // Map moduleId to includePriority
231
+ includePriority: getIncludePriority(theme.moduleId),
232
+ }))
233
+ // Sort by includePriority
234
+ .sort((themeA, themeB) => themeB.includePriority - themeA.includePriority);
235
+
236
+ if (themes.length > 0) {
237
+ return themes;
238
+ }
239
+ // No theme modules found, return the default module if it exists
240
+ return getAllThemes().filter((theme) => theme.moduleId === defaultModuleName);
241
+ }
242
+
243
+ /**
244
+ * @polymerMixin
245
+ * @mixes ThemePropertyMixin
246
+ */
247
+ const ThemableMixin = (superClass) =>
248
+ class VaadinThemableMixin extends ThemePropertyMixin(superClass) {
249
+ /**
250
+ * Covers PolymerElement based component styling
251
+ * @protected
252
+ */
253
+ static finalize() {
254
+ super.finalize();
255
+
256
+ // Make sure not to run the logic intended for PolymerElement when LitElement is used.
257
+ if (this.elementStyles) {
258
+ return;
259
+ }
260
+
261
+ const template = this.prototype._template;
262
+ if (!template || classHasThemes(this)) {
263
+ return;
264
+ }
265
+
266
+ addStylesToTemplate(this.getStylesForThis(), template);
267
+ }
268
+
269
+ /**
270
+ * Covers LitElement based component styling
271
+ *
272
+ * @protected
273
+ */
274
+ static finalizeStyles(styles) {
275
+ // The "styles" object originates from the "static get styles()" function of
276
+ // a LitElement based component. The theme styles are added after it
277
+ // so that they can override the component styles.
278
+ const themeStyles = this.getStylesForThis();
279
+ return styles ? [...super.finalizeStyles(styles), ...themeStyles] : themeStyles;
280
+ }
281
+
282
+ /**
283
+ * Get styles for the component type
284
+ *
285
+ * @private
286
+ */
287
+ static getStylesForThis() {
288
+ const parent = Object.getPrototypeOf(this.prototype);
289
+ const inheritedThemes = (parent ? parent.constructor.__themes : []) || [];
290
+ this.__themes = [...inheritedThemes, ...getThemes(this.is)];
291
+ const themeStyles = this.__themes.flatMap((theme) => theme.styles);
292
+ // Remove duplicates
293
+ return themeStyles.filter((style, index) => index === themeStyles.lastIndexOf(style));
294
+ }
295
+ };
296
+
4
297
  const button = i`
5
298
  :host {
6
299
  /* Sizing */
@@ -21,6 +314,7 @@ const button = i`
21
314
  -webkit-tap-highlight-color: transparent;
22
315
  -webkit-font-smoothing: antialiased;
23
316
  -moz-osx-font-smoothing: grayscale;
317
+ flex-shrink: 0;
24
318
  }
25
319
 
26
320
  /* Set only for the internal parts so we don't affect the host vertical alignment */
@@ -51,10 +345,7 @@ const button = i`
51
345
  /* We rely on the host always being relative */
52
346
  position: absolute;
53
347
  z-index: 1;
54
- top: 0;
55
- right: 0;
56
- bottom: 0;
57
- left: 0;
348
+ inset: 0;
58
349
  background-color: currentColor;
59
350
  border-radius: inherit;
60
351
  opacity: 0;
@@ -199,16 +490,14 @@ const button = i`
199
490
 
200
491
  /* Icons */
201
492
 
202
- [part] ::slotted(vaadin-icon),
203
- [part] ::slotted(iron-icon) {
493
+ [part] ::slotted(vaadin-icon) {
204
494
  display: inline-block;
205
495
  width: var(--lumo-icon-size-m);
206
496
  height: var(--lumo-icon-size-m);
207
497
  }
208
498
 
209
499
  /* Vaadin icons are based on a 16x16 grid (unlike Lumo and Material icons with 24x24), so they look too big by default */
210
- [part] ::slotted(vaadin-icon[icon^='vaadin:']),
211
- [part] ::slotted(iron-icon[icon^='vaadin:']) {
500
+ [part] ::slotted(vaadin-icon[icon^='vaadin:']) {
212
501
  padding: 0.25em;
213
502
  box-sizing: border-box !important;
214
503
  }
@@ -260,191 +549,1276 @@ registerStyles('vaadin-button', button, { moduleId: 'lumo-button' });
260
549
 
261
550
  /**
262
551
  * @license
263
- * Copyright (c) 2017 - 2022 Vaadin Ltd.
552
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
264
553
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
265
554
  */
266
555
 
267
556
  /**
268
- * A mixin providing common button functionality.
557
+ * @typedef ReactiveController
558
+ * @type {import('lit').ReactiveController}
559
+ */
560
+
561
+ /**
562
+ * A mixin for connecting controllers to the element.
269
563
  *
270
564
  * @polymerMixin
271
- * @mixes ActiveMixin
272
- * @mixes FocusMixin
273
- * @mixes TabindexMixin
274
565
  */
275
- const ButtonMixin = (superClass) =>
276
- class ButtonMixinClass extends ActiveMixin(TabindexMixin(FocusMixin(superClass))) {
277
- static get properties() {
278
- return {
279
- /**
280
- * Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
281
- *
282
- * @override
283
- * @protected
284
- */
285
- tabindex: {
286
- value: 0,
287
- },
288
- };
566
+ const ControllerMixin = dedupingMixin((superClass) => {
567
+ // If the superclass extends from LitElement,
568
+ // use its own controllers implementation.
569
+ if (typeof superClass.prototype.addController === 'function') {
570
+ return superClass;
571
+ }
572
+
573
+ return class ControllerMixinClass extends superClass {
574
+ constructor() {
575
+ super();
576
+
577
+ /**
578
+ * @type {Set<ReactiveController>}
579
+ */
580
+ this.__controllers = new Set();
289
581
  }
290
582
 
291
- /**
292
- * By default, `Space` is the only possible activation key for a focusable HTML element.
293
- * Nonetheless, the button is an exception as it can be also activated by pressing `Enter`.
294
- * See the "Keyboard Support" section in https://www.w3.org/TR/wai-aria-practices/examples/button/button.html.
295
- *
296
- * @protected
297
- * @override
298
- */
299
- get _activeKeys() {
300
- return ['Enter', ' '];
583
+ /** @protected */
584
+ connectedCallback() {
585
+ super.connectedCallback();
586
+
587
+ this.__controllers.forEach((c) => {
588
+ if (c.hostConnected) {
589
+ c.hostConnected();
590
+ }
591
+ });
301
592
  }
302
593
 
303
594
  /** @protected */
304
- ready() {
305
- super.ready();
595
+ disconnectedCallback() {
596
+ super.disconnectedCallback();
306
597
 
307
- // By default, if the user hasn't provided a custom role,
308
- // the role attribute is set to "button".
309
- if (!this.hasAttribute('role')) {
310
- this.setAttribute('role', 'button');
311
- }
598
+ this.__controllers.forEach((c) => {
599
+ if (c.hostDisconnected) {
600
+ c.hostDisconnected();
601
+ }
602
+ });
312
603
  }
313
604
 
314
605
  /**
315
- * Since the button component is designed on the base of the `[role=button]` attribute,
316
- * and doesn't have a native <button> inside, in order to be fully accessible from the keyboard,
317
- * it should manually fire the `click` event once an activation key is pressed,
318
- * as it follows from the WAI-ARIA specifications:
319
- * https://www.w3.org/TR/wai-aria-practices-1.1/#button
320
- *
321
- * According to the UI Events specifications,
322
- * the `click` event should be fired exactly on `keydown`:
323
- * https://www.w3.org/TR/uievents/#event-type-keydown
606
+ * Registers a controller to participate in the element update cycle.
324
607
  *
325
- * @param {KeyboardEvent} event
608
+ * @param {ReactiveController} controller
326
609
  * @protected
327
- * @override
328
610
  */
329
- _onKeyDown(event) {
330
- super._onKeyDown(event);
331
-
332
- if (this._activeKeys.includes(event.key)) {
333
- event.preventDefault();
334
-
335
- // `DisabledMixin` overrides the standard `click()` method
336
- // so that it doesn't fire the `click` event when the element is disabled.
337
- this.click();
611
+ addController(controller) {
612
+ this.__controllers.add(controller);
613
+ // Call hostConnected if a controller is added after the element is attached.
614
+ if (this.$ !== undefined && this.isConnected && controller.hostConnected) {
615
+ controller.hostConnected();
338
616
  }
339
617
  }
618
+
619
+ /**
620
+ * Removes a controller from the element.
621
+ *
622
+ * @param {ReactiveController} controller
623
+ * @protected
624
+ */
625
+ removeController(controller) {
626
+ this.__controllers.delete(controller);
627
+ }
340
628
  };
629
+ });
341
630
 
342
631
  /**
343
632
  * @license
344
- * Copyright (c) 2017 - 2022 Vaadin Ltd.
633
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
345
634
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
346
635
  */
636
+ function defineCustomElement(CustomElement) {
637
+ const defined = customElements.get(CustomElement.is);
638
+ if (!defined) {
639
+ customElements.define(CustomElement.is, CustomElement);
640
+ } else {
641
+ const definedVersion = defined.version;
642
+ if (definedVersion && CustomElement.version && definedVersion === CustomElement.version) {
643
+ // Just loading the same thing again
644
+ console.warn(`The component ${CustomElement.is} has been loaded twice`);
645
+ } else {
646
+ console.error(
647
+ `Tried to define ${CustomElement.is} version ${CustomElement.version} when version ${defined.version} is already in use. Something will probably break.`,
648
+ );
649
+ }
650
+ }
651
+ }
347
652
 
348
653
  /**
349
- * `<vaadin-button>` is an accessible and customizable button that allows users to perform actions.
350
- *
351
- * ```html
352
- * <vaadin-button>Press me</vaadin-button>
353
- * ```
354
- *
355
- * ### Styling
356
- *
357
- * The following shadow DOM parts are available for styling:
358
- *
359
- * Part name | Description
360
- * ----------|-------------
361
- * `label` | The label (text) inside the button.
362
- * `prefix` | A slot for content before the label (e.g. an icon).
363
- * `suffix` | A slot for content after the label (e.g. an icon).
364
- *
365
- * The following attributes are available for styling:
366
- *
367
- * Attribute | Description
368
- * -------------|-------------
369
- * `active` | Set when the button is pressed down, either with mouse, touch or the keyboard.
370
- * `disabled` | Set when the button is disabled.
371
- * `focus-ring` | Set when the button is focused using the keyboard.
372
- * `focused` | Set when the button is focused.
373
- *
374
- * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
654
+ * @license
655
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
656
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
657
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
658
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
659
+ * Code distributed by Google as part of the polymer project is also
660
+ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
661
+ */
662
+
663
+ /**
664
+ * Async interface wrapper around `requestIdleCallback`. Falls back to
665
+ * `setTimeout` on browsers that do not support `requestIdleCallback`.
375
666
  *
376
- * @extends HTMLElement
377
- * @mixes ButtonMixin
378
- * @mixes ControllerMixin
379
- * @mixes ElementMixin
380
- * @mixes ThemableMixin
667
+ * @namespace
668
+ * @summary Async interface wrapper around `requestIdleCallback`.
381
669
  */
382
- class Button extends ButtonMixin(ElementMixin(ThemableMixin(ControllerMixin(PolymerElement)))) {
383
- static get is() {
384
- return 'vaadin-button';
670
+ const idlePeriod = {
671
+ /**
672
+ * Enqueues a function called at `requestIdleCallback` timing.
673
+ *
674
+ * @memberof idlePeriod
675
+ * @param {function(!IdleDeadline):void} fn Callback to run
676
+ * @return {number} Handle used for canceling task
677
+ */
678
+ run(fn) {
679
+ return window.requestIdleCallback ? window.requestIdleCallback(fn) : window.setTimeout(fn, 16);
680
+ },
681
+ /**
682
+ * Cancels a previously enqueued `idlePeriod` callback.
683
+ *
684
+ * @memberof idlePeriod
685
+ * @param {number} handle Handle returned from `run` of callback to cancel
686
+ * @return {void}
687
+ */
688
+ cancel(handle) {
689
+ if (window.cancelIdleCallback) {
690
+ window.cancelIdleCallback(handle);
691
+ } else {
692
+ window.clearTimeout(handle);
693
+ }
694
+ },
695
+ };
696
+
697
+ /**
698
+ @license
699
+ Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
700
+ This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
701
+ The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
702
+ The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
703
+ Code distributed by Google as part of the polymer project is also
704
+ subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
705
+ */
706
+
707
+ const debouncerQueue = new Set();
708
+
709
+ /**
710
+ * @summary Collapse multiple callbacks into one invocation after a timer.
711
+ */
712
+ class Debouncer {
713
+ /**
714
+ * Creates a debouncer if no debouncer is passed as a parameter
715
+ * or it cancels an active debouncer otherwise. The following
716
+ * example shows how a debouncer can be called multiple times within a
717
+ * microtask and "debounced" such that the provided callback function is
718
+ * called once. Add this method to a custom element:
719
+ *
720
+ * ```js
721
+ * import {microTask} from '@vaadin/component-base/src/async.js';
722
+ * import {Debouncer} from '@vaadin/component-base/src/debounce.js';
723
+ * // ...
724
+ *
725
+ * _debounceWork() {
726
+ * this._debounceJob = Debouncer.debounce(this._debounceJob,
727
+ * microTask, () => this._doWork());
728
+ * }
729
+ * ```
730
+ *
731
+ * If the `_debounceWork` method is called multiple times within the same
732
+ * microtask, the `_doWork` function will be called only once at the next
733
+ * microtask checkpoint.
734
+ *
735
+ * Note: In testing it is often convenient to avoid asynchrony. To accomplish
736
+ * this with a debouncer, you can use `enqueueDebouncer` and
737
+ * `flush`. For example, extend the above example by adding
738
+ * `enqueueDebouncer(this._debounceJob)` at the end of the
739
+ * `_debounceWork` method. Then in a test, call `flush` to ensure
740
+ * the debouncer has completed.
741
+ *
742
+ * @param {Debouncer?} debouncer Debouncer object.
743
+ * @param {!AsyncInterface} asyncModule Object with Async interface
744
+ * @param {function()} callback Callback to run.
745
+ * @return {!Debouncer} Returns a debouncer object.
746
+ */
747
+ static debounce(debouncer, asyncModule, callback) {
748
+ if (debouncer instanceof Debouncer) {
749
+ // Cancel the async callback, but leave in debouncerQueue if it was
750
+ // enqueued, to maintain 1.x flush order
751
+ debouncer._cancelAsync();
752
+ } else {
753
+ debouncer = new Debouncer();
754
+ }
755
+ debouncer.setConfig(asyncModule, callback);
756
+ return debouncer;
385
757
  }
386
758
 
387
- static get template() {
388
- return html`
389
- <style>
390
- :host {
391
- display: inline-block;
392
- position: relative;
393
- outline: none;
394
- white-space: nowrap;
395
- -webkit-user-select: none;
396
- -moz-user-select: none;
397
- user-select: none;
398
- }
759
+ constructor() {
760
+ this._asyncModule = null;
761
+ this._callback = null;
762
+ this._timer = null;
763
+ }
399
764
 
400
- :host([hidden]) {
401
- display: none !important;
402
- }
765
+ /**
766
+ * Sets the scheduler; that is, a module with the Async interface,
767
+ * a callback and optional arguments to be passed to the run function
768
+ * from the async module.
769
+ *
770
+ * @param {!AsyncInterface} asyncModule Object with Async interface.
771
+ * @param {function()} callback Callback to run.
772
+ * @return {void}
773
+ */
774
+ setConfig(asyncModule, callback) {
775
+ this._asyncModule = asyncModule;
776
+ this._callback = callback;
777
+ this._timer = this._asyncModule.run(() => {
778
+ this._timer = null;
779
+ debouncerQueue.delete(this);
780
+ this._callback();
781
+ });
782
+ }
403
783
 
404
- /* Aligns the button with form fields when placed on the same line.
405
- Note, to make it work, the form fields should have the same "::before" pseudo-element. */
406
- .vaadin-button-container::before {
407
- content: '\\2003';
408
- display: inline-block;
409
- width: 0;
410
- max-height: 100%;
411
- }
784
+ /**
785
+ * Cancels an active debouncer and returns a reference to itself.
786
+ *
787
+ * @return {void}
788
+ */
789
+ cancel() {
790
+ if (this.isActive()) {
791
+ this._cancelAsync();
792
+ // Canceling a debouncer removes its spot from the flush queue,
793
+ // so if a debouncer is manually canceled and re-debounced, it
794
+ // will reset its flush order (this is a very minor difference from 1.x)
795
+ // Re-debouncing via the `debounce` API retains the 1.x FIFO flush order
796
+ debouncerQueue.delete(this);
797
+ }
798
+ }
412
799
 
413
- .vaadin-button-container {
414
- display: inline-flex;
415
- align-items: center;
416
- justify-content: center;
417
- text-align: center;
418
- width: 100%;
419
- height: 100%;
420
- min-height: inherit;
421
- text-shadow: inherit;
422
- }
800
+ /**
801
+ * Cancels a debouncer's async callback.
802
+ *
803
+ * @return {void}
804
+ */
805
+ _cancelAsync() {
806
+ if (this.isActive()) {
807
+ this._asyncModule.cancel(/** @type {number} */ (this._timer));
808
+ this._timer = null;
809
+ }
810
+ }
423
811
 
424
- [part='prefix'],
425
- [part='suffix'] {
426
- flex: none;
427
- }
812
+ /**
813
+ * Flushes an active debouncer and returns a reference to itself.
814
+ *
815
+ * @return {void}
816
+ */
817
+ flush() {
818
+ if (this.isActive()) {
819
+ this.cancel();
820
+ this._callback();
821
+ }
822
+ }
428
823
 
429
- [part='label'] {
430
- white-space: nowrap;
431
- overflow: hidden;
432
- text-overflow: ellipsis;
433
- }
434
- </style>
435
- <div class="vaadin-button-container">
436
- <span part="prefix" aria-hidden="true">
437
- <slot name="prefix"></slot>
438
- </span>
439
- <span part="label">
440
- <slot></slot>
441
- </span>
442
- <span part="suffix" aria-hidden="true">
443
- <slot name="suffix"></slot>
444
- </span>
445
- </div>
446
- <slot name="tooltip"></slot>
447
- `;
824
+ /**
825
+ * Returns true if the debouncer is active.
826
+ *
827
+ * @return {boolean} True if active.
828
+ */
829
+ isActive() {
830
+ return this._timer != null;
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Adds a `Debouncer` to a list of globally flushable tasks.
836
+ *
837
+ * @param {!Debouncer} debouncer Debouncer to enqueue
838
+ * @return {void}
839
+ */
840
+ function enqueueDebouncer(debouncer) {
841
+ debouncerQueue.add(debouncer);
842
+ }
843
+
844
+ /**
845
+ * @license
846
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
847
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
848
+ */
849
+
850
+ /**
851
+ * Array of Vaadin custom element classes that have been subscribed to the dir changes.
852
+ */
853
+ const directionSubscribers = [];
854
+
855
+ function alignDirs(element, documentDir, elementDir = element.getAttribute('dir')) {
856
+ if (documentDir) {
857
+ element.setAttribute('dir', documentDir);
858
+ } else if (elementDir != null) {
859
+ element.removeAttribute('dir');
860
+ }
861
+ }
862
+
863
+ function getDocumentDir() {
864
+ return document.documentElement.getAttribute('dir');
865
+ }
866
+
867
+ function directionUpdater() {
868
+ const documentDir = getDocumentDir();
869
+ directionSubscribers.forEach((element) => {
870
+ alignDirs(element, documentDir);
871
+ });
872
+ }
873
+
874
+ const directionObserver = new MutationObserver(directionUpdater);
875
+ directionObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] });
876
+
877
+ /**
878
+ * A mixin to handle `dir` attribute based on the one set on the `<html>` element.
879
+ *
880
+ * @polymerMixin
881
+ */
882
+ const DirMixin = (superClass) =>
883
+ class VaadinDirMixin extends superClass {
884
+ static get properties() {
885
+ return {
886
+ /**
887
+ * @protected
888
+ */
889
+ dir: {
890
+ type: String,
891
+ value: '',
892
+ reflectToAttribute: true,
893
+ converter: {
894
+ fromAttribute: (attr) => {
895
+ return !attr ? '' : attr;
896
+ },
897
+ toAttribute: (prop) => {
898
+ return prop === '' ? null : prop;
899
+ },
900
+ },
901
+ },
902
+ };
903
+ }
904
+
905
+ /**
906
+ * @return {boolean}
907
+ * @protected
908
+ */
909
+ get __isRTL() {
910
+ return this.getAttribute('dir') === 'rtl';
911
+ }
912
+
913
+ /** @protected */
914
+ connectedCallback() {
915
+ super.connectedCallback();
916
+
917
+ if (!this.hasAttribute('dir') || this.__restoreSubscription) {
918
+ this.__subscribe();
919
+ alignDirs(this, getDocumentDir(), null);
920
+ }
921
+ }
922
+
923
+ /** @protected */
924
+ attributeChangedCallback(name, oldValue, newValue) {
925
+ super.attributeChangedCallback(name, oldValue, newValue);
926
+ if (name !== 'dir') {
927
+ return;
928
+ }
929
+
930
+ const documentDir = getDocumentDir();
931
+
932
+ // New value equals to the document direction and the element is not subscribed to the changes
933
+ const newValueEqlDocDir = newValue === documentDir && directionSubscribers.indexOf(this) === -1;
934
+ // Value was emptied and the element is not subscribed to the changes
935
+ const newValueEmptied = !newValue && oldValue && directionSubscribers.indexOf(this) === -1;
936
+ // New value is different and the old equals to document direction and the element is not subscribed to the changes
937
+ const newDiffValue = newValue !== documentDir && oldValue === documentDir;
938
+
939
+ if (newValueEqlDocDir || newValueEmptied) {
940
+ this.__subscribe();
941
+ alignDirs(this, documentDir, newValue);
942
+ } else if (newDiffValue) {
943
+ this.__unsubscribe();
944
+ }
945
+ }
946
+
947
+ /** @protected */
948
+ disconnectedCallback() {
949
+ super.disconnectedCallback();
950
+ this.__restoreSubscription = directionSubscribers.includes(this);
951
+ this.__unsubscribe();
952
+ }
953
+
954
+ /** @protected */
955
+ _valueToNodeAttribute(node, value, attribute) {
956
+ // Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
957
+ // If the property contains an empty string then it should not create an empty attribute
958
+ if (attribute === 'dir' && value === '' && !node.hasAttribute('dir')) {
959
+ return;
960
+ }
961
+ super._valueToNodeAttribute(node, value, attribute);
962
+ }
963
+
964
+ /** @protected */
965
+ _attributeToProperty(attribute, value, type) {
966
+ // Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
967
+ // If the attribute is removed, then the dir property should contain an empty string instead of null
968
+ if (attribute === 'dir' && !value) {
969
+ this.dir = '';
970
+ } else {
971
+ super._attributeToProperty(attribute, value, type);
972
+ }
973
+ }
974
+
975
+ /** @private */
976
+ __subscribe() {
977
+ if (!directionSubscribers.includes(this)) {
978
+ directionSubscribers.push(this);
979
+ }
980
+ }
981
+
982
+ /** @private */
983
+ __unsubscribe() {
984
+ if (directionSubscribers.includes(this)) {
985
+ directionSubscribers.splice(directionSubscribers.indexOf(this), 1);
986
+ }
987
+ }
988
+ };
989
+
990
+ /**
991
+ * @license
992
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
993
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
994
+ */
995
+
996
+ if (!window.Vaadin) {
997
+ window.Vaadin = {};
998
+ }
999
+
1000
+ /**
1001
+ * Array of Vaadin custom element classes that have been finalized.
1002
+ */
1003
+ if (!window.Vaadin.registrations) {
1004
+ window.Vaadin.registrations = [];
1005
+ }
1006
+
1007
+ if (!window.Vaadin.developmentModeCallback) {
1008
+ window.Vaadin.developmentModeCallback = {};
1009
+ }
1010
+
1011
+ window.Vaadin.developmentModeCallback['vaadin-usage-statistics'] = function () {
1012
+ usageStatistics();
1013
+ };
1014
+
1015
+ let statsJob;
1016
+
1017
+ const registered = new Set();
1018
+
1019
+ /**
1020
+ * @polymerMixin
1021
+ * @mixes DirMixin
1022
+ */
1023
+ const ElementMixin = (superClass) =>
1024
+ class VaadinElementMixin extends DirMixin(superClass) {
1025
+ static get version() {
1026
+ return '24.2.3';
1027
+ }
1028
+
1029
+ /** @protected */
1030
+ static finalize() {
1031
+ super.finalize();
1032
+
1033
+ const { is } = this;
1034
+
1035
+ // Registers a class prototype for telemetry purposes.
1036
+ if (is && !registered.has(is)) {
1037
+ window.Vaadin.registrations.push(this);
1038
+ registered.add(is);
1039
+
1040
+ if (window.Vaadin.developmentModeCallback) {
1041
+ statsJob = Debouncer.debounce(statsJob, idlePeriod, () => {
1042
+ window.Vaadin.developmentModeCallback['vaadin-usage-statistics']();
1043
+ });
1044
+ enqueueDebouncer(statsJob);
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ constructor() {
1050
+ super();
1051
+
1052
+ if (document.doctype === null) {
1053
+ console.warn(
1054
+ 'Vaadin components require the "standards mode" declaration. Please add <!DOCTYPE html> to the HTML document.',
1055
+ );
1056
+ }
1057
+ }
1058
+ };
1059
+
1060
+ /**
1061
+ * @license
1062
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
1063
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1064
+ */
1065
+
1066
+ /**
1067
+ * Returns true if the given node is an empty text node, false otherwise.
1068
+ *
1069
+ * @param {Node} node
1070
+ * @return {boolean}
1071
+ */
1072
+ function isEmptyTextNode(node) {
1073
+ return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '';
1074
+ }
1075
+
1076
+ /**
1077
+ * @license
1078
+ * Copyright (c) 2023 Vaadin Ltd.
1079
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1080
+ */
1081
+
1082
+ /**
1083
+ * A helper for observing slot changes.
1084
+ */
1085
+ class SlotObserver {
1086
+ constructor(slot, callback) {
1087
+ /** @type HTMLSlotElement */
1088
+ this.slot = slot;
1089
+
1090
+ /** @type Function */
1091
+ this.callback = callback;
1092
+
1093
+ /** @type {Node[]} */
1094
+ this._storedNodes = [];
1095
+
1096
+ this._connected = false;
1097
+ this._scheduled = false;
1098
+
1099
+ this._boundSchedule = () => {
1100
+ this._schedule();
1101
+ };
1102
+
1103
+ this.connect();
1104
+ this._schedule();
1105
+ }
1106
+
1107
+ /**
1108
+ * Activates an observer. This method is automatically called when
1109
+ * a `SlotObserver` is created. It should only be called to re-activate
1110
+ * an observer that has been deactivated via the `disconnect` method.
1111
+ */
1112
+ connect() {
1113
+ this.slot.addEventListener('slotchange', this._boundSchedule);
1114
+ this._connected = true;
1115
+ }
1116
+
1117
+ /**
1118
+ * Deactivates the observer. After calling this method the observer callback
1119
+ * will not be called when changes to slotted nodes occur. The `connect` method
1120
+ * may be subsequently called to reactivate the observer.
1121
+ */
1122
+ disconnect() {
1123
+ this.slot.removeEventListener('slotchange', this._boundSchedule);
1124
+ this._connected = false;
1125
+ }
1126
+
1127
+ /** @private */
1128
+ _schedule() {
1129
+ if (!this._scheduled) {
1130
+ this._scheduled = true;
1131
+
1132
+ queueMicrotask(() => {
1133
+ this.flush();
1134
+ });
1135
+ }
1136
+ }
1137
+
1138
+ /**
1139
+ * Run the observer callback synchronously.
1140
+ */
1141
+ flush() {
1142
+ if (!this._connected) {
1143
+ return;
1144
+ }
1145
+
1146
+ this._scheduled = false;
1147
+
1148
+ this._processNodes();
1149
+ }
1150
+
1151
+ /** @private */
1152
+ _processNodes() {
1153
+ const currentNodes = this.slot.assignedNodes({ flatten: true });
1154
+
1155
+ let addedNodes = [];
1156
+ const removedNodes = [];
1157
+ const movedNodes = [];
1158
+
1159
+ if (currentNodes.length) {
1160
+ addedNodes = currentNodes.filter((node) => !this._storedNodes.includes(node));
1161
+ }
1162
+
1163
+ if (this._storedNodes.length) {
1164
+ this._storedNodes.forEach((node, index) => {
1165
+ const idx = currentNodes.indexOf(node);
1166
+ if (idx === -1) {
1167
+ removedNodes.push(node);
1168
+ } else if (idx !== index) {
1169
+ movedNodes.push(node);
1170
+ }
1171
+ });
1172
+ }
1173
+
1174
+ if (addedNodes.length || removedNodes.length || movedNodes.length) {
1175
+ this.callback({ addedNodes, movedNodes, removedNodes });
1176
+ }
1177
+
1178
+ this._storedNodes = currentNodes;
1179
+ }
1180
+ }
1181
+
1182
+ /**
1183
+ * @license
1184
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
1185
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1186
+ */
1187
+
1188
+ let uniqueId = 0;
1189
+
1190
+ /**
1191
+ * Returns a unique integer id.
1192
+ *
1193
+ * @return {number}
1194
+ */
1195
+ function generateUniqueId() {
1196
+ // eslint-disable-next-line no-plusplus
1197
+ return uniqueId++;
1198
+ }
1199
+
1200
+ /**
1201
+ * @license
1202
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
1203
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1204
+ */
1205
+
1206
+ /**
1207
+ * A controller for providing content to slot element and observing changes.
1208
+ */
1209
+ class SlotController extends EventTarget {
1210
+ /**
1211
+ * Ensure that every instance has unique ID.
1212
+ *
1213
+ * @param {HTMLElement} host
1214
+ * @param {string} slotName
1215
+ * @return {string}
1216
+ * @protected
1217
+ */
1218
+ static generateId(host, slotName) {
1219
+ const prefix = slotName || 'default';
1220
+ return `${prefix}-${host.localName}-${generateUniqueId()}`;
1221
+ }
1222
+
1223
+ constructor(host, slotName, tagName, config = {}) {
1224
+ super();
1225
+
1226
+ const { initializer, multiple, observe, useUniqueId } = config;
1227
+
1228
+ this.host = host;
1229
+ this.slotName = slotName;
1230
+ this.tagName = tagName;
1231
+ this.observe = typeof observe === 'boolean' ? observe : true;
1232
+ this.multiple = typeof multiple === 'boolean' ? multiple : false;
1233
+ this.slotInitializer = initializer;
1234
+
1235
+ if (multiple) {
1236
+ this.nodes = [];
1237
+ }
1238
+
1239
+ // Only generate the default ID if requested by the controller.
1240
+ if (useUniqueId) {
1241
+ this.defaultId = this.constructor.generateId(host, slotName);
1242
+ }
1243
+ }
1244
+
1245
+ hostConnected() {
1246
+ if (!this.initialized) {
1247
+ if (this.multiple) {
1248
+ this.initMultiple();
1249
+ } else {
1250
+ this.initSingle();
1251
+ }
1252
+
1253
+ if (this.observe) {
1254
+ this.observeSlot();
1255
+ }
1256
+
1257
+ this.initialized = true;
1258
+ }
1259
+ }
1260
+
1261
+ /** @protected */
1262
+ initSingle() {
1263
+ let node = this.getSlotChild();
1264
+
1265
+ if (!node) {
1266
+ node = this.attachDefaultNode();
1267
+ this.initNode(node);
1268
+ } else {
1269
+ this.node = node;
1270
+ this.initAddedNode(node);
1271
+ }
1272
+ }
1273
+
1274
+ /** @protected */
1275
+ initMultiple() {
1276
+ const children = this.getSlotChildren();
1277
+
1278
+ if (children.length === 0) {
1279
+ const defaultNode = this.attachDefaultNode();
1280
+ if (defaultNode) {
1281
+ this.nodes = [defaultNode];
1282
+ this.initNode(defaultNode);
1283
+ }
1284
+ } else {
1285
+ this.nodes = children;
1286
+ children.forEach((node) => {
1287
+ this.initAddedNode(node);
1288
+ });
1289
+ }
1290
+ }
1291
+
1292
+ /**
1293
+ * Create and attach default node using the provided tag name, if any.
1294
+ * @return {Node | undefined}
1295
+ * @protected
1296
+ */
1297
+ attachDefaultNode() {
1298
+ const { host, slotName, tagName } = this;
1299
+
1300
+ // Check if the node was created previously and if so, reuse it.
1301
+ let node = this.defaultNode;
1302
+
1303
+ // Tag name is optional, sometimes we don't init default content.
1304
+ if (!node && tagName) {
1305
+ node = document.createElement(tagName);
1306
+ if (node instanceof Element) {
1307
+ if (slotName !== '') {
1308
+ node.setAttribute('slot', slotName);
1309
+ }
1310
+ this.node = node;
1311
+ this.defaultNode = node;
1312
+ }
1313
+ }
1314
+
1315
+ if (node) {
1316
+ host.appendChild(node);
1317
+ }
1318
+
1319
+ return node;
1320
+ }
1321
+
1322
+ /**
1323
+ * Return the list of nodes matching the slot managed by the controller.
1324
+ * @return {Node}
1325
+ */
1326
+ getSlotChildren() {
1327
+ const { slotName } = this;
1328
+ return Array.from(this.host.childNodes).filter((node) => {
1329
+ // Either an element (any slot) or a text node (only un-named slot).
1330
+ return (
1331
+ (node.nodeType === Node.ELEMENT_NODE && node.slot === slotName) ||
1332
+ (node.nodeType === Node.TEXT_NODE && node.textContent.trim() && slotName === '')
1333
+ );
1334
+ });
1335
+ }
1336
+
1337
+ /**
1338
+ * Return a reference to the node managed by the controller.
1339
+ * @return {Node}
1340
+ */
1341
+ getSlotChild() {
1342
+ return this.getSlotChildren()[0];
1343
+ }
1344
+
1345
+ /**
1346
+ * Run `slotInitializer` for the node managed by the controller.
1347
+ *
1348
+ * @param {Node} node
1349
+ * @protected
1350
+ */
1351
+ initNode(node) {
1352
+ const { slotInitializer } = this;
1353
+ // Don't try to bind `this` to initializer (normally it's arrow function).
1354
+ // Instead, pass the host as a first argument to access component's state.
1355
+ if (slotInitializer) {
1356
+ slotInitializer(node, this.host);
1357
+ }
1358
+ }
1359
+
1360
+ /**
1361
+ * Override to initialize the newly added custom node.
1362
+ *
1363
+ * @param {Node} _node
1364
+ * @protected
1365
+ */
1366
+ initCustomNode(_node) {}
1367
+
1368
+ /**
1369
+ * Override to teardown slotted node when it's removed.
1370
+ *
1371
+ * @param {Node} _node
1372
+ * @protected
1373
+ */
1374
+ teardownNode(_node) {}
1375
+
1376
+ /**
1377
+ * Run both `initCustomNode` and `initNode` for a custom slotted node.
1378
+ *
1379
+ * @param {Node} node
1380
+ * @protected
1381
+ */
1382
+ initAddedNode(node) {
1383
+ if (node !== this.defaultNode) {
1384
+ this.initCustomNode(node);
1385
+ this.initNode(node);
1386
+ }
1387
+ }
1388
+
1389
+ /**
1390
+ * Setup the observer to manage slot content changes.
1391
+ * @protected
1392
+ */
1393
+ observeSlot() {
1394
+ const { slotName } = this;
1395
+ const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
1396
+ const slot = this.host.shadowRoot.querySelector(selector);
1397
+
1398
+ this.__slotObserver = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
1399
+ const current = this.multiple ? this.nodes : [this.node];
1400
+
1401
+ // Calling `slot.assignedNodes()` includes whitespace text nodes in case of default slot:
1402
+ // unlike comment nodes, they are not filtered out. So we need to manually ignore them.
1403
+ const newNodes = addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
1404
+
1405
+ if (removedNodes.length) {
1406
+ this.nodes = current.filter((node) => !removedNodes.includes(node));
1407
+
1408
+ removedNodes.forEach((node) => {
1409
+ this.teardownNode(node);
1410
+ });
1411
+ }
1412
+
1413
+ if (newNodes && newNodes.length > 0) {
1414
+ if (this.multiple) {
1415
+ // Remove default node if exists
1416
+ if (this.defaultNode) {
1417
+ this.defaultNode.remove();
1418
+ }
1419
+ this.nodes = [...current, ...newNodes].filter((node) => node !== this.defaultNode);
1420
+ newNodes.forEach((node) => {
1421
+ this.initAddedNode(node);
1422
+ });
1423
+ } else {
1424
+ // Remove previous node if exists
1425
+ if (this.node) {
1426
+ this.node.remove();
1427
+ }
1428
+ this.node = newNodes[0];
1429
+ this.initAddedNode(this.node);
1430
+ }
1431
+ }
1432
+ });
1433
+ }
1434
+ }
1435
+
1436
+ /**
1437
+ * @license
1438
+ * Copyright (c) 2022 - 2023 Vaadin Ltd.
1439
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1440
+ */
1441
+
1442
+ /**
1443
+ * A controller that manages the slotted tooltip element.
1444
+ */
1445
+ class TooltipController extends SlotController {
1446
+ constructor(host) {
1447
+ // Do not provide slot factory to create tooltip lazily.
1448
+ super(host, 'tooltip');
1449
+
1450
+ this.setTarget(host);
1451
+ }
1452
+
1453
+ /**
1454
+ * Override to initialize the newly added custom tooltip.
1455
+ *
1456
+ * @param {Node} tooltipNode
1457
+ * @protected
1458
+ * @override
1459
+ */
1460
+ initCustomNode(tooltipNode) {
1461
+ tooltipNode.target = this.target;
1462
+
1463
+ if (this.ariaTarget !== undefined) {
1464
+ tooltipNode.ariaTarget = this.ariaTarget;
1465
+ }
1466
+
1467
+ if (this.context !== undefined) {
1468
+ tooltipNode.context = this.context;
1469
+ }
1470
+
1471
+ if (this.manual !== undefined) {
1472
+ tooltipNode.manual = this.manual;
1473
+ }
1474
+
1475
+ if (this.opened !== undefined) {
1476
+ tooltipNode.opened = this.opened;
1477
+ }
1478
+
1479
+ if (this.position !== undefined) {
1480
+ tooltipNode._position = this.position;
1481
+ }
1482
+
1483
+ if (this.shouldShow !== undefined) {
1484
+ tooltipNode.shouldShow = this.shouldShow;
1485
+ }
1486
+
1487
+ this.__notifyChange();
1488
+ }
1489
+
1490
+ /**
1491
+ * Override to notify the host when the tooltip is removed.
1492
+ *
1493
+ * @param {Node} tooltipNode
1494
+ * @protected
1495
+ * @override
1496
+ */
1497
+ teardownNode() {
1498
+ this.__notifyChange();
1499
+ }
1500
+
1501
+ /**
1502
+ * Set an HTML element for linking with the tooltip overlay
1503
+ * via `aria-describedby` attribute used by screen readers.
1504
+ * @param {HTMLElement} ariaTarget
1505
+ */
1506
+ setAriaTarget(ariaTarget) {
1507
+ this.ariaTarget = ariaTarget;
1508
+
1509
+ const tooltipNode = this.node;
1510
+ if (tooltipNode) {
1511
+ tooltipNode.ariaTarget = ariaTarget;
1512
+ }
1513
+ }
1514
+
1515
+ /**
1516
+ * Set a context object to be used by generator.
1517
+ * @param {object} context
1518
+ */
1519
+ setContext(context) {
1520
+ this.context = context;
1521
+
1522
+ const tooltipNode = this.node;
1523
+ if (tooltipNode) {
1524
+ tooltipNode.context = context;
1525
+ }
1526
+ }
1527
+
1528
+ /**
1529
+ * Toggle manual state on the slotted tooltip.
1530
+ * @param {boolean} manual
1531
+ */
1532
+ setManual(manual) {
1533
+ this.manual = manual;
1534
+
1535
+ const tooltipNode = this.node;
1536
+ if (tooltipNode) {
1537
+ tooltipNode.manual = manual;
1538
+ }
1539
+ }
1540
+
1541
+ /**
1542
+ * Toggle opened state on the slotted tooltip.
1543
+ * @param {boolean} opened
1544
+ */
1545
+ setOpened(opened) {
1546
+ this.opened = opened;
1547
+
1548
+ const tooltipNode = this.node;
1549
+ if (tooltipNode) {
1550
+ tooltipNode.opened = opened;
1551
+ }
1552
+ }
1553
+
1554
+ /**
1555
+ * Set default position for the slotted tooltip.
1556
+ * This can be overridden by setting the position
1557
+ * using corresponding property or attribute.
1558
+ * @param {string} position
1559
+ */
1560
+ setPosition(position) {
1561
+ this.position = position;
1562
+
1563
+ const tooltipNode = this.node;
1564
+ if (tooltipNode) {
1565
+ tooltipNode._position = position;
1566
+ }
1567
+ }
1568
+
1569
+ /**
1570
+ * Set function used to detect whether to show
1571
+ * the tooltip based on a condition.
1572
+ * @param {Function} shouldShow
1573
+ */
1574
+ setShouldShow(shouldShow) {
1575
+ this.shouldShow = shouldShow;
1576
+
1577
+ const tooltipNode = this.node;
1578
+ if (tooltipNode) {
1579
+ tooltipNode.shouldShow = shouldShow;
1580
+ }
1581
+ }
1582
+
1583
+ /**
1584
+ * Set an HTML element to attach the tooltip to.
1585
+ * @param {HTMLElement} target
1586
+ */
1587
+ setTarget(target) {
1588
+ this.target = target;
1589
+
1590
+ const tooltipNode = this.node;
1591
+ if (tooltipNode) {
1592
+ tooltipNode.target = target;
1593
+ }
1594
+ }
1595
+
1596
+ /** @private */
1597
+ __notifyChange() {
1598
+ this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node: this.node } }));
1599
+ }
1600
+ }
1601
+
1602
+ /**
1603
+ * @license
1604
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
1605
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1606
+ */
1607
+
1608
+ const buttonStyles = i`
1609
+ :host {
1610
+ display: inline-block;
1611
+ position: relative;
1612
+ outline: none;
1613
+ white-space: nowrap;
1614
+ -webkit-user-select: none;
1615
+ -moz-user-select: none;
1616
+ user-select: none;
1617
+ }
1618
+
1619
+ :host([hidden]) {
1620
+ display: none !important;
1621
+ }
1622
+
1623
+ /* Aligns the button with form fields when placed on the same line.
1624
+ Note, to make it work, the form fields should have the same "::before" pseudo-element. */
1625
+ .vaadin-button-container::before {
1626
+ content: '\\2003';
1627
+ display: inline-block;
1628
+ width: 0;
1629
+ max-height: 100%;
1630
+ }
1631
+
1632
+ .vaadin-button-container {
1633
+ display: inline-flex;
1634
+ align-items: center;
1635
+ justify-content: center;
1636
+ text-align: center;
1637
+ width: 100%;
1638
+ height: 100%;
1639
+ min-height: inherit;
1640
+ text-shadow: inherit;
1641
+ }
1642
+
1643
+ [part='prefix'],
1644
+ [part='suffix'] {
1645
+ flex: none;
1646
+ }
1647
+
1648
+ [part='label'] {
1649
+ white-space: nowrap;
1650
+ overflow: hidden;
1651
+ text-overflow: ellipsis;
1652
+ }
1653
+
1654
+ @media (forced-colors: active) {
1655
+ :host {
1656
+ outline: 1px solid;
1657
+ outline-offset: -1px;
1658
+ }
1659
+
1660
+ :host([focused]) {
1661
+ outline-width: 2px;
1662
+ }
1663
+
1664
+ :host([disabled]) {
1665
+ outline-color: GrayText;
1666
+ }
1667
+ }
1668
+ `;
1669
+
1670
+ const buttonTemplate = (html) => html`
1671
+ <div class="vaadin-button-container">
1672
+ <span part="prefix" aria-hidden="true">
1673
+ <slot name="prefix"></slot>
1674
+ </span>
1675
+ <span part="label">
1676
+ <slot></slot>
1677
+ </span>
1678
+ <span part="suffix" aria-hidden="true">
1679
+ <slot name="suffix"></slot>
1680
+ </span>
1681
+ </div>
1682
+ <slot name="tooltip"></slot>
1683
+ `;
1684
+
1685
+ /**
1686
+ * @license
1687
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
1688
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1689
+ */
1690
+
1691
+ /**
1692
+ * A mixin providing common button functionality.
1693
+ *
1694
+ * @polymerMixin
1695
+ * @mixes ActiveMixin
1696
+ * @mixes FocusMixin
1697
+ * @mixes TabindexMixin
1698
+ */
1699
+ const ButtonMixin = (superClass) =>
1700
+ class ButtonMixinClass extends ActiveMixin(TabindexMixin(FocusMixin(superClass))) {
1701
+ static get properties() {
1702
+ return {
1703
+ /**
1704
+ * Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
1705
+ *
1706
+ * @override
1707
+ * @protected
1708
+ */
1709
+ tabindex: {
1710
+ type: Number,
1711
+ value: 0,
1712
+ reflectToAttribute: true,
1713
+ },
1714
+ };
1715
+ }
1716
+
1717
+ /**
1718
+ * By default, `Space` is the only possible activation key for a focusable HTML element.
1719
+ * Nonetheless, the button is an exception as it can be also activated by pressing `Enter`.
1720
+ * See the "Keyboard Support" section in https://www.w3.org/TR/wai-aria-practices/examples/button/button.html.
1721
+ *
1722
+ * @protected
1723
+ * @override
1724
+ */
1725
+ get _activeKeys() {
1726
+ return ['Enter', ' '];
1727
+ }
1728
+
1729
+ /** @protected */
1730
+ ready() {
1731
+ super.ready();
1732
+
1733
+ // By default, if the user hasn't provided a custom role,
1734
+ // the role attribute is set to "button".
1735
+ if (!this.hasAttribute('role')) {
1736
+ this.setAttribute('role', 'button');
1737
+ }
1738
+ }
1739
+
1740
+ /**
1741
+ * Since the button component is designed on the base of the `[role=button]` attribute,
1742
+ * and doesn't have a native <button> inside, in order to be fully accessible from the keyboard,
1743
+ * it should manually fire the `click` event once an activation key is pressed,
1744
+ * as it follows from the WAI-ARIA specifications:
1745
+ * https://www.w3.org/TR/wai-aria-practices-1.1/#button
1746
+ *
1747
+ * According to the UI Events specifications,
1748
+ * the `click` event should be fired exactly on `keydown`:
1749
+ * https://www.w3.org/TR/uievents/#event-type-keydown
1750
+ *
1751
+ * @param {KeyboardEvent} event
1752
+ * @protected
1753
+ * @override
1754
+ */
1755
+ _onKeyDown(event) {
1756
+ super._onKeyDown(event);
1757
+
1758
+ if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) {
1759
+ return;
1760
+ }
1761
+
1762
+ if (this._activeKeys.includes(event.key)) {
1763
+ event.preventDefault();
1764
+
1765
+ // `DisabledMixin` overrides the standard `click()` method
1766
+ // so that it doesn't fire the `click` event when the element is disabled.
1767
+ this.click();
1768
+ }
1769
+ }
1770
+ };
1771
+
1772
+ /**
1773
+ * @license
1774
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
1775
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1776
+ */
1777
+
1778
+ registerStyles('vaadin-button', buttonStyles, { moduleId: 'vaadin-button-styles' });
1779
+
1780
+ /**
1781
+ * `<vaadin-button>` is an accessible and customizable button that allows users to perform actions.
1782
+ *
1783
+ * ```html
1784
+ * <vaadin-button>Press me</vaadin-button>
1785
+ * ```
1786
+ *
1787
+ * ### Styling
1788
+ *
1789
+ * The following shadow DOM parts are available for styling:
1790
+ *
1791
+ * Part name | Description
1792
+ * ----------|-------------
1793
+ * `label` | The label (text) inside the button.
1794
+ * `prefix` | A slot for content before the label (e.g. an icon).
1795
+ * `suffix` | A slot for content after the label (e.g. an icon).
1796
+ *
1797
+ * The following attributes are available for styling:
1798
+ *
1799
+ * Attribute | Description
1800
+ * -------------|-------------
1801
+ * `active` | Set when the button is pressed down, either with mouse, touch or the keyboard.
1802
+ * `disabled` | Set when the button is disabled.
1803
+ * `focus-ring` | Set when the button is focused using the keyboard.
1804
+ * `focused` | Set when the button is focused.
1805
+ *
1806
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
1807
+ *
1808
+ * @customElement
1809
+ * @extends HTMLElement
1810
+ * @mixes ButtonMixin
1811
+ * @mixes ControllerMixin
1812
+ * @mixes ElementMixin
1813
+ * @mixes ThemableMixin
1814
+ */
1815
+ class Button extends ButtonMixin(ElementMixin(ThemableMixin(ControllerMixin(PolymerElement)))) {
1816
+ static get is() {
1817
+ return 'vaadin-button';
1818
+ }
1819
+
1820
+ static get template() {
1821
+ return buttonTemplate(html);
448
1822
  }
449
1823
 
450
1824
  /** @protected */
@@ -456,6 +1830,6 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(ControllerMixin(Poly
456
1830
  }
457
1831
  }
458
1832
 
459
- customElements.define(Button.is, Button);
1833
+ defineCustomElement(Button);
460
1834
 
461
1835
  export { Button as B, button as b };