@everymatrix/general-input 1.22.1 → 1.22.2

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.
@@ -55,26 +55,91 @@ const t$1=window,e$2=t$1.ShadowRoot&&(void 0===t$1.ShadyCSS||t$1.ShadyCSS.native
55
55
  * @license
56
56
  * Copyright 2017 Google LLC
57
57
  * SPDX-License-Identifier: BSD-3-Clause
58
- */var s$2;const e$1=window,r$1=e$1.trustedTypes,h$1=r$1?r$1.emptyScript:"",o$2=e$1.reactiveElementPolyfillSupport,n$2={toAttribute(t,i){switch(i){case Boolean:t=t?h$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,i){let s=t;switch(i){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t);}catch(t){s=null;}}return s}},a$1=(t,i)=>i!==t&&(i==i||t==t),l$2={attribute:!0,type:String,converter:n$2,reflect:!1,hasChanged:a$1},d$1="finalized";class u$1 extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u();}static addInitializer(t){var i;this.finalize(),(null!==(i=this.h)&&void 0!==i?i:this.h=[]).push(t);}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((i,s)=>{const e=this._$Ep(s,i);void 0!==e&&(this._$Ev.set(e,s),t.push(e));})),t}static createProperty(t,i=l$2){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(t,i),!i.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,e=this.getPropertyDescriptor(t,s,i);void 0!==e&&Object.defineProperty(this.prototype,t,e);}}static getPropertyDescriptor(t,i,s){return {get(){return this[i]},set(e){const r=this[t];this[i]=e,this.requestUpdate(t,r,s);},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||l$2}static finalize(){if(this.hasOwnProperty(d$1))return !1;this[d$1]=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,i=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of i)this.createProperty(s,t[s]);}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(i){const s=[];if(Array.isArray(i)){const e=new Set(i.flat(1/0).reverse());for(const i of e)s.unshift(c$1(i));}else void 0!==i&&s.push(c$1(i));return s}static _$Ep(t,i){const s=i.attribute;return !1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)));}addController(t){var i,s;(null!==(i=this._$ES)&&void 0!==i?i:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t));}removeController(t){var i;null===(i=this._$ES)||void 0===i||i.splice(this._$ES.indexOf(t)>>>0,1);}_$Eg(){this.constructor.elementProperties.forEach(((t,i)=>{this.hasOwnProperty(i)&&(this._$Ei.set(i,this[i]),delete this[i]);}));}createRenderRoot(){var t;const s=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return S$1(s,this.constructor.elementStyles),s}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostConnected)||void 0===i?void 0:i.call(t)}));}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostDisconnected)||void 0===i?void 0:i.call(t)}));}attributeChangedCallback(t,i,s){this._$AK(t,s);}_$EO(t,i,s=l$2){var e;const r=this.constructor._$Ep(t,s);if(void 0!==r&&!0===s.reflect){const h=(void 0!==(null===(e=s.converter)||void 0===e?void 0:e.toAttribute)?s.converter:n$2).toAttribute(i,s.type);this._$El=t,null==h?this.removeAttribute(r):this.setAttribute(r,h),this._$El=null;}}_$AK(t,i){var s;const e=this.constructor,r=e._$Ev.get(t);if(void 0!==r&&this._$El!==r){const t=e.getPropertyOptions(r),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:n$2;this._$El=r,this[r]=h.fromAttribute(i,t.type),this._$El=null;}}requestUpdate(t,i,s){let e=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||a$1)(this[t],i)?(this._$AL.has(t)||this._$AL.set(t,i),!0===s.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,s))):e=!1),!this.isUpdatePending&&e&&(this._$E_=this._$Ej());}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this._$Ei&&(this._$Ei.forEach(((t,i)=>this[i]=t)),this._$Ei=void 0);let i=!1;const s=this._$AL;try{i=this.shouldUpdate(s),i?(this.willUpdate(s),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostUpdate)||void 0===i?void 0:i.call(t)})),this.update(s)):this._$Ek();}catch(t){throw i=!1,this._$Ek(),t}i&&this._$AE(s);}willUpdate(t){}_$AE(t){var i;null===(i=this._$ES)||void 0===i||i.forEach((t=>{var i;return null===(i=t.hostUpdated)||void 0===i?void 0:i.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t);}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return !0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,i)=>this._$EO(i,this[i],t))),this._$EC=void 0),this._$Ek();}updated(t){}firstUpdated(t){}}u$1[d$1]=!0,u$1.elementProperties=new Map,u$1.elementStyles=[],u$1.shadowRootOptions={mode:"open"},null==o$2||o$2({ReactiveElement:u$1}),(null!==(s$2=e$1.reactiveElementVersions)&&void 0!==s$2?s$2:e$1.reactiveElementVersions=[]).push("1.6.2");
58
+ */var s$2;const e$1=window,r$1=e$1.trustedTypes,h$1=r$1?r$1.emptyScript:"",o$2=e$1.reactiveElementPolyfillSupport,n$2={toAttribute(t,i){switch(i){case Boolean:t=t?h$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,i){let s=t;switch(i){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t);}catch(t){s=null;}}return s}},a$1=(t,i)=>i!==t&&(i==i||t==t),l$2={attribute:!0,type:String,converter:n$2,reflect:!1,hasChanged:a$1},d$1="finalized";class u$1 extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this._$Eu();}static addInitializer(t){var i;this.finalize(),(null!==(i=this.h)&&void 0!==i?i:this.h=[]).push(t);}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((i,s)=>{const e=this._$Ep(s,i);void 0!==e&&(this._$Ev.set(e,s),t.push(e));})),t}static createProperty(t,i=l$2){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(t,i),!i.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,e=this.getPropertyDescriptor(t,s,i);void 0!==e&&Object.defineProperty(this.prototype,t,e);}}static getPropertyDescriptor(t,i,s){return {get(){return this[i]},set(e){const r=this[t];this[i]=e,this.requestUpdate(t,r,s);},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||l$2}static finalize(){if(this.hasOwnProperty(d$1))return !1;this[d$1]=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,i=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of i)this.createProperty(s,t[s]);}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(i){const s=[];if(Array.isArray(i)){const e=new Set(i.flat(1/0).reverse());for(const i of e)s.unshift(c$1(i));}else void 0!==i&&s.push(c$1(i));return s}static _$Ep(t,i){const s=i.attribute;return !1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}_$Eu(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)));}addController(t){var i,s;(null!==(i=this._$ES)&&void 0!==i?i:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t));}removeController(t){var i;null===(i=this._$ES)||void 0===i||i.splice(this._$ES.indexOf(t)>>>0,1);}_$Eg(){this.constructor.elementProperties.forEach(((t,i)=>{this.hasOwnProperty(i)&&(this._$Ei.set(i,this[i]),delete this[i]);}));}createRenderRoot(){var t;const s=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return S$1(s,this.constructor.elementStyles),s}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostConnected)||void 0===i?void 0:i.call(t)}));}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostDisconnected)||void 0===i?void 0:i.call(t)}));}attributeChangedCallback(t,i,s){this._$AK(t,s);}_$EO(t,i,s=l$2){var e;const r=this.constructor._$Ep(t,s);if(void 0!==r&&!0===s.reflect){const h=(void 0!==(null===(e=s.converter)||void 0===e?void 0:e.toAttribute)?s.converter:n$2).toAttribute(i,s.type);this._$El=t,null==h?this.removeAttribute(r):this.setAttribute(r,h),this._$El=null;}}_$AK(t,i){var s;const e=this.constructor,r=e._$Ev.get(t);if(void 0!==r&&this._$El!==r){const t=e.getPropertyOptions(r),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:n$2;this._$El=r,this[r]=h.fromAttribute(i,t.type),this._$El=null;}}requestUpdate(t,i,s){let e=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||a$1)(this[t],i)?(this._$AL.has(t)||this._$AL.set(t,i),!0===s.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,s))):e=!1),!this.isUpdatePending&&e&&(this._$E_=this._$Ej());}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this._$Ei&&(this._$Ei.forEach(((t,i)=>this[i]=t)),this._$Ei=void 0);let i=!1;const s=this._$AL;try{i=this.shouldUpdate(s),i?(this.willUpdate(s),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostUpdate)||void 0===i?void 0:i.call(t)})),this.update(s)):this._$Ek();}catch(t){throw i=!1,this._$Ek(),t}i&&this._$AE(s);}willUpdate(t){}_$AE(t){var i;null===(i=this._$ES)||void 0===i||i.forEach((t=>{var i;return null===(i=t.hostUpdated)||void 0===i?void 0:i.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t);}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return !0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,i)=>this._$EO(i,this[i],t))),this._$EC=void 0),this._$Ek();}updated(t){}firstUpdated(t){}}u$1[d$1]=!0,u$1.elementProperties=new Map,u$1.elementStyles=[],u$1.shadowRootOptions={mode:"open"},null==o$2||o$2({ReactiveElement:u$1}),(null!==(s$2=e$1.reactiveElementVersions)&&void 0!==s$2?s$2:e$1.reactiveElementVersions=[]).push("1.6.3");
59
59
 
60
60
  /**
61
61
  * @license
62
62
  * Copyright 2017 Google LLC
63
63
  * SPDX-License-Identifier: BSD-3-Clause
64
64
  */
65
- var t;const i=window,s$1=i.trustedTypes,e=s$1?s$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,o$1="$lit$",n$1=`lit$${(Math.random()+"").slice(9)}$`,l$1="?"+n$1,h=`<${l$1}>`,r=document,d=()=>r.createComment(""),u=t=>null===t||"object"!=typeof t&&"function"!=typeof t,c=Array.isArray,v=t=>c(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]),a="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${a}(?:([^\\s"'>=/]+)(${a}*=${a}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,T=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),E=new WeakMap,C=r.createTreeWalker(r,129,null,!1),P=(t,i)=>{const s=t.length-1,l=[];let r,d=2===i?"<svg>":"",u=f;for(let i=0;i<s;i++){const s=t[i];let e,c,v=-1,a=0;for(;a<s.length&&(u.lastIndex=a,c=u.exec(s),null!==c);)a=u.lastIndex,u===f?"!--"===c[1]?u=_:void 0!==c[1]?u=m:void 0!==c[2]?(y.test(c[2])&&(r=RegExp("</"+c[2],"g")),u=p):void 0!==c[3]&&(u=p):u===p?">"===c[0]?(u=null!=r?r:f,v=-1):void 0===c[1]?v=-2:(v=u.lastIndex-c[2].length,e=c[1],u=void 0===c[3]?p:'"'===c[3]?$:g):u===$||u===g?u=p:u===_||u===m?u=f:(u=p,r=void 0);const w=u===p&&t[i+1].startsWith("/>")?" ":"";d+=u===f?s+h:v>=0?(l.push(e),s.slice(0,v)+o$1+s.slice(v)+n$1+w):s+n$1+(-2===v?(l.push(void 0),i):w);}const c=d+(t[s]||"<?>")+(2===i?"</svg>":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return [void 0!==e?e.createHTML(c):c,l]};class V{constructor({strings:t,_$litType$:i},e){let h;this.parts=[];let r=0,u=0;const c=t.length-1,v=this.parts,[a,f]=P(t,i);if(this.el=V.createElement(a,e),C.currentNode=this.el.content,2===i){const t=this.el.content,i=t.firstChild;i.remove(),t.append(...i.childNodes);}for(;null!==(h=C.nextNode())&&v.length<c;){if(1===h.nodeType){if(h.hasAttributes()){const t=[];for(const i of h.getAttributeNames())if(i.endsWith(o$1)||i.startsWith(n$1)){const s=f[u++];if(t.push(i),void 0!==s){const t=h.getAttribute(s.toLowerCase()+o$1).split(n$1),i=/([.?@])?(.*)/.exec(s);v.push({type:1,index:r,name:i[2],strings:t,ctor:"."===i[1]?k:"?"===i[1]?I:"@"===i[1]?L:R});}else v.push({type:6,index:r});}for(const i of t)h.removeAttribute(i);}if(y.test(h.tagName)){const t=h.textContent.split(n$1),i=t.length-1;if(i>0){h.textContent=s$1?s$1.emptyScript:"";for(let s=0;s<i;s++)h.append(t[s],d()),C.nextNode(),v.push({type:2,index:++r});h.append(t[i],d());}}}else if(8===h.nodeType)if(h.data===l$1)v.push({type:2,index:r});else {let t=-1;for(;-1!==(t=h.data.indexOf(n$1,t+1));)v.push({type:7,index:r}),t+=n$1.length-1;}r++;}}static createElement(t,i){const s=r.createElement("template");return s.innerHTML=t,s}}function N(t,i,s=t,e){var o,n,l,h;if(i===T)return i;let r=void 0!==e?null===(o=s._$Co)||void 0===o?void 0:o[e]:s._$Cl;const d=u(i)?void 0:i._$litDirective$;return (null==r?void 0:r.constructor)!==d&&(null===(n=null==r?void 0:r._$AO)||void 0===n||n.call(r,!1),void 0===d?r=void 0:(r=new d(t),r._$AT(t,s,e)),void 0!==e?(null!==(l=(h=s)._$Co)&&void 0!==l?l:h._$Co=[])[e]=r:s._$Cl=r),void 0!==r&&(i=N(t,r._$AS(t,i.values),r,e)),i}class S{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){var i;const{el:{content:s},parts:e}=this._$AD,o=(null!==(i=null==t?void 0:t.creationScope)&&void 0!==i?i:r).importNode(s,!0);C.currentNode=o;let n=C.nextNode(),l=0,h=0,d=e[0];for(;void 0!==d;){if(l===d.index){let i;2===d.type?i=new M(n,n.nextSibling,this,t):1===d.type?i=new d.ctor(n,d.name,d.strings,this,t):6===d.type&&(i=new z(n,this,t)),this._$AV.push(i),d=e[++h];}l!==(null==d?void 0:d.index)&&(n=C.nextNode(),l++);}return C.currentNode=r,o}v(t){let i=0;for(const s of this._$AV)void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class M{constructor(t,i,s,e){var o;this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cp=null===(o=null==e?void 0:e.isConnected)||void 0===o||o;}get _$AU(){var t,i;return null!==(i=null===(t=this._$AM)||void 0===t?void 0:t._$AU)&&void 0!==i?i:this._$Cp}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===(null==t?void 0:t.nodeType)&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=N(this,t,i),u(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==T&&this._(t):void 0!==t._$litType$?this.g(t):void 0!==t.nodeType?this.$(t):v(t)?this.T(t):this._(t);}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t));}_(t){this._$AH!==A&&u(this._$AH)?this._$AA.nextSibling.data=t:this.$(r.createTextNode(t)),this._$AH=t;}g(t){var i;const{values:s,_$litType$:e}=t,o="number"==typeof e?this._$AC(t):(void 0===e.el&&(e.el=V.createElement(e.h,this.options)),e);if((null===(i=this._$AH)||void 0===i?void 0:i._$AD)===o)this._$AH.v(s);else {const t=new S(o,this),i=t.u(this.options);t.v(s),this.$(i),this._$AH=t;}}_$AC(t){let i=E.get(t.strings);return void 0===i&&E.set(t.strings,i=new V(t)),i}T(t){c(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const o of t)e===i.length?i.push(s=new M(this.k(d()),this.k(d()),this,this.options)):s=i[e],s._$AI(o),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,i){var s;for(null===(s=this._$AP)||void 0===s||s.call(this,!1,!0,i);t&&t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i;}}setConnected(t){var i;void 0===this._$AM&&(this._$Cp=t,null===(i=this._$AP)||void 0===i||i.call(this,t));}}class R{constructor(t,i,s,e,o){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=o,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,i=this,s,e){const o=this.strings;let n=!1;if(void 0===o)t=N(this,t,i,0),n=!u(t)||t!==this._$AH&&t!==T,n&&(this._$AH=t);else {const e=t;let l,h;for(t=o[0],l=0;l<o.length-1;l++)h=N(this,e[s+l],i,l),h===T&&(h=this._$AH[l]),n||(n=!u(h)||h!==this._$AH[l]),h===A?t=A:t!==A&&(t+=(null!=h?h:"")+o[l+1]),this._$AH[l]=h;}n&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,null!=t?t:"");}}class k extends R{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}const H=s$1?s$1.emptyScript:"";class I extends R{constructor(){super(...arguments),this.type=4;}j(t){t&&t!==A?this.element.setAttribute(this.name,H):this.element.removeAttribute(this.name);}}class L extends R{constructor(t,i,s,e,o){super(t,i,s,e,o),this.type=5;}_$AI(t,i=this){var s;if((t=null!==(s=N(this,t,i,0))&&void 0!==s?s:A)===T)return;const e=this._$AH,o=t===A&&e!==A||t.capture!==e.capture||t.once!==e.once||t.passive!==e.passive,n=t!==A&&(e===A||o);o&&this.element.removeEventListener(this.name,this,e),n&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){var i,s;"function"==typeof this._$AH?this._$AH.call(null!==(s=null===(i=this.options)||void 0===i?void 0:i.host)&&void 0!==s?s:this.element,t):this._$AH.handleEvent(t);}}class z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){N(this,t);}}const j=i.litHtmlPolyfillSupport;null==j||j(V,M),(null!==(t=i.litHtmlVersions)&&void 0!==t?t:i.litHtmlVersions=[]).push("2.7.4");const B=(t,i,s)=>{var e,o;const n=null!==(e=null==s?void 0:s.renderBefore)&&void 0!==e?e:i;let l=n._$litPart$;if(void 0===l){const t=null!==(o=null==s?void 0:s.renderBefore)&&void 0!==o?o:null;n._$litPart$=l=new M(i.insertBefore(d(),t),t,void 0,null!=s?s:{});}return l._$AI(t),l};
65
+ var t;const i=window,s$1=i.trustedTypes,e=s$1?s$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,o$1="$lit$",n$1=`lit$${(Math.random()+"").slice(9)}$`,l$1="?"+n$1,h=`<${l$1}>`,r=document,u=()=>r.createComment(""),d=t=>null===t||"object"!=typeof t&&"function"!=typeof t,c=Array.isArray,v=t=>c(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]),a="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${a}(?:([^\\s"'>=/]+)(${a}*=${a}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,T=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),E=new WeakMap,C=r.createTreeWalker(r,129,null,!1);function P(t,i){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e?e.createHTML(i):i}const V=(t,i)=>{const s=t.length-1,e=[];let l,r=2===i?"<svg>":"",u=f;for(let i=0;i<s;i++){const s=t[i];let d,c,v=-1,a=0;for(;a<s.length&&(u.lastIndex=a,c=u.exec(s),null!==c);)a=u.lastIndex,u===f?"!--"===c[1]?u=_:void 0!==c[1]?u=m:void 0!==c[2]?(y.test(c[2])&&(l=RegExp("</"+c[2],"g")),u=p):void 0!==c[3]&&(u=p):u===p?">"===c[0]?(u=null!=l?l:f,v=-1):void 0===c[1]?v=-2:(v=u.lastIndex-c[2].length,d=c[1],u=void 0===c[3]?p:'"'===c[3]?$:g):u===$||u===g?u=p:u===_||u===m?u=f:(u=p,l=void 0);const w=u===p&&t[i+1].startsWith("/>")?" ":"";r+=u===f?s+h:v>=0?(e.push(d),s.slice(0,v)+o$1+s.slice(v)+n$1+w):s+n$1+(-2===v?(e.push(void 0),i):w);}return [P(t,r+(t[s]||"<?>")+(2===i?"</svg>":"")),e]};class N{constructor({strings:t,_$litType$:i},e){let h;this.parts=[];let r=0,d=0;const c=t.length-1,v=this.parts,[a,f]=V(t,i);if(this.el=N.createElement(a,e),C.currentNode=this.el.content,2===i){const t=this.el.content,i=t.firstChild;i.remove(),t.append(...i.childNodes);}for(;null!==(h=C.nextNode())&&v.length<c;){if(1===h.nodeType){if(h.hasAttributes()){const t=[];for(const i of h.getAttributeNames())if(i.endsWith(o$1)||i.startsWith(n$1)){const s=f[d++];if(t.push(i),void 0!==s){const t=h.getAttribute(s.toLowerCase()+o$1).split(n$1),i=/([.?@])?(.*)/.exec(s);v.push({type:1,index:r,name:i[2],strings:t,ctor:"."===i[1]?H:"?"===i[1]?L:"@"===i[1]?z:k});}else v.push({type:6,index:r});}for(const i of t)h.removeAttribute(i);}if(y.test(h.tagName)){const t=h.textContent.split(n$1),i=t.length-1;if(i>0){h.textContent=s$1?s$1.emptyScript:"";for(let s=0;s<i;s++)h.append(t[s],u()),C.nextNode(),v.push({type:2,index:++r});h.append(t[i],u());}}}else if(8===h.nodeType)if(h.data===l$1)v.push({type:2,index:r});else {let t=-1;for(;-1!==(t=h.data.indexOf(n$1,t+1));)v.push({type:7,index:r}),t+=n$1.length-1;}r++;}}static createElement(t,i){const s=r.createElement("template");return s.innerHTML=t,s}}function S(t,i,s=t,e){var o,n,l,h;if(i===T)return i;let r=void 0!==e?null===(o=s._$Co)||void 0===o?void 0:o[e]:s._$Cl;const u=d(i)?void 0:i._$litDirective$;return (null==r?void 0:r.constructor)!==u&&(null===(n=null==r?void 0:r._$AO)||void 0===n||n.call(r,!1),void 0===u?r=void 0:(r=new u(t),r._$AT(t,s,e)),void 0!==e?(null!==(l=(h=s)._$Co)&&void 0!==l?l:h._$Co=[])[e]=r:s._$Cl=r),void 0!==r&&(i=S(t,r._$AS(t,i.values),r,e)),i}class M{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){var i;const{el:{content:s},parts:e}=this._$AD,o=(null!==(i=null==t?void 0:t.creationScope)&&void 0!==i?i:r).importNode(s,!0);C.currentNode=o;let n=C.nextNode(),l=0,h=0,u=e[0];for(;void 0!==u;){if(l===u.index){let i;2===u.type?i=new R(n,n.nextSibling,this,t):1===u.type?i=new u.ctor(n,u.name,u.strings,this,t):6===u.type&&(i=new Z(n,this,t)),this._$AV.push(i),u=e[++h];}l!==(null==u?void 0:u.index)&&(n=C.nextNode(),l++);}return C.currentNode=r,o}v(t){let i=0;for(const s of this._$AV)void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class R{constructor(t,i,s,e){var o;this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cp=null===(o=null==e?void 0:e.isConnected)||void 0===o||o;}get _$AU(){var t,i;return null!==(i=null===(t=this._$AM)||void 0===t?void 0:t._$AU)&&void 0!==i?i:this._$Cp}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===(null==t?void 0:t.nodeType)&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=S(this,t,i),d(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==T&&this._(t):void 0!==t._$litType$?this.g(t):void 0!==t.nodeType?this.$(t):v(t)?this.T(t):this._(t);}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t));}_(t){this._$AH!==A&&d(this._$AH)?this._$AA.nextSibling.data=t:this.$(r.createTextNode(t)),this._$AH=t;}g(t){var i;const{values:s,_$litType$:e}=t,o="number"==typeof e?this._$AC(t):(void 0===e.el&&(e.el=N.createElement(P(e.h,e.h[0]),this.options)),e);if((null===(i=this._$AH)||void 0===i?void 0:i._$AD)===o)this._$AH.v(s);else {const t=new M(o,this),i=t.u(this.options);t.v(s),this.$(i),this._$AH=t;}}_$AC(t){let i=E.get(t.strings);return void 0===i&&E.set(t.strings,i=new N(t)),i}T(t){c(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const o of t)e===i.length?i.push(s=new R(this.k(u()),this.k(u()),this,this.options)):s=i[e],s._$AI(o),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,i){var s;for(null===(s=this._$AP)||void 0===s||s.call(this,!1,!0,i);t&&t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i;}}setConnected(t){var i;void 0===this._$AM&&(this._$Cp=t,null===(i=this._$AP)||void 0===i||i.call(this,t));}}class k{constructor(t,i,s,e,o){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=o,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,i=this,s,e){const o=this.strings;let n=!1;if(void 0===o)t=S(this,t,i,0),n=!d(t)||t!==this._$AH&&t!==T,n&&(this._$AH=t);else {const e=t;let l,h;for(t=o[0],l=0;l<o.length-1;l++)h=S(this,e[s+l],i,l),h===T&&(h=this._$AH[l]),n||(n=!d(h)||h!==this._$AH[l]),h===A?t=A:t!==A&&(t+=(null!=h?h:"")+o[l+1]),this._$AH[l]=h;}n&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,null!=t?t:"");}}class H extends k{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}const I=s$1?s$1.emptyScript:"";class L extends k{constructor(){super(...arguments),this.type=4;}j(t){t&&t!==A?this.element.setAttribute(this.name,I):this.element.removeAttribute(this.name);}}class z extends k{constructor(t,i,s,e,o){super(t,i,s,e,o),this.type=5;}_$AI(t,i=this){var s;if((t=null!==(s=S(this,t,i,0))&&void 0!==s?s:A)===T)return;const e=this._$AH,o=t===A&&e!==A||t.capture!==e.capture||t.once!==e.once||t.passive!==e.passive,n=t!==A&&(e===A||o);o&&this.element.removeEventListener(this.name,this,e),n&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){var i,s;"function"==typeof this._$AH?this._$AH.call(null!==(s=null===(i=this.options)||void 0===i?void 0:i.host)&&void 0!==s?s:this.element,t):this._$AH.handleEvent(t);}}class Z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){S(this,t);}}const B=i.litHtmlPolyfillSupport;null==B||B(N,R),(null!==(t=i.litHtmlVersions)&&void 0!==t?t:i.litHtmlVersions=[]).push("2.8.0");const D=(t,i,s)=>{var e,o;const n=null!==(e=null==s?void 0:s.renderBefore)&&void 0!==e?e:i;let l=n._$litPart$;if(void 0===l){const t=null!==(o=null==s?void 0:s.renderBefore)&&void 0!==o?o:null;n._$litPart$=l=new R(i.insertBefore(u(),t),t,void 0,null!=s?s:{});}return l._$AI(t),l};
66
66
 
67
67
  /**
68
68
  * @license
69
69
  * Copyright 2017 Google LLC
70
70
  * SPDX-License-Identifier: BSD-3-Clause
71
- */var l,o;class s extends u$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){var t,e;const i=super.createRenderRoot();return null!==(t=(e=this.renderOptions).renderBefore)&&void 0!==t||(e.renderBefore=i.firstChild),i}update(t){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=B(i,this.renderRoot,this.renderOptions);}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0);}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1);}render(){return T}}s.finalized=!0,s._$litElement$=!0,null===(l=globalThis.litElementHydrateSupport)||void 0===l||l.call(globalThis,{LitElement:s});const n=globalThis.litElementPolyfillSupport;null==n||n({LitElement:s});(null!==(o=globalThis.litElementVersions)&&void 0!==o?o:globalThis.litElementVersions=[]).push("3.3.2");
71
+ */var l,o;class s extends u$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){var t,e;const i=super.createRenderRoot();return null!==(t=(e=this.renderOptions).renderBefore)&&void 0!==t||(e.renderBefore=i.firstChild),i}update(t){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=D(i,this.renderRoot,this.renderOptions);}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0);}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1);}render(){return T}}s.finalized=!0,s._$litElement$=!0,null===(l=globalThis.litElementHydrateSupport)||void 0===l||l.call(globalThis,{LitElement:s});const n=globalThis.litElementPolyfillSupport;null==n||n({LitElement:s});(null!==(o=globalThis.litElementVersions)&&void 0!==o?o:globalThis.litElementVersions=[]).push("3.3.3");
72
72
 
73
73
  /**
74
74
  * @license
75
75
  * Copyright (c) 2017 - 2023 Vaadin Ltd.
76
76
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
77
77
  */
78
+ /**
79
+ * @polymerMixin
80
+ */
81
+ const ThemePropertyMixin = (superClass) =>
82
+ class VaadinThemePropertyMixin extends superClass {
83
+ static get properties() {
84
+ return {
85
+ /**
86
+ * Helper property with theme attribute value facilitating propagation
87
+ * in shadow DOM.
88
+ *
89
+ * Enables the component implementation to propagate the `theme`
90
+ * attribute value to the sub-components in Shadow DOM by binding
91
+ * the sub-component's "theme" attribute to the `theme` property of
92
+ * the host.
93
+ *
94
+ * **NOTE:** Extending the mixin only provides the property for binding,
95
+ * and does not make the propagation alone.
96
+ *
97
+ * See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/styling-components/#sub-components).
98
+ * page for more information.
99
+ *
100
+ * @protected
101
+ */
102
+ _theme: {
103
+ type: String,
104
+ readOnly: true,
105
+ },
106
+ };
107
+ }
108
+
109
+ static get observedAttributes() {
110
+ return [...super.observedAttributes, 'theme'];
111
+ }
112
+
113
+ /** @protected */
114
+ attributeChangedCallback(name, oldValue, newValue) {
115
+ super.attributeChangedCallback(name, oldValue, newValue);
116
+
117
+ if (name === 'theme') {
118
+ this._set_theme(newValue);
119
+ }
120
+ }
121
+ };
122
+
123
+ /**
124
+ * @license
125
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
126
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
127
+ */
128
+
129
+ /**
130
+ * @typedef {Object} Theme
131
+ * @property {string} themeFor
132
+ * @property {CSSResult[]} styles
133
+ * @property {string | string[]} [include]
134
+ * @property {string} [moduleId]
135
+ *
136
+ * @typedef {CSSResult[] | CSSResult} CSSResultGroup
137
+ */
138
+
139
+ /**
140
+ * @type {Theme[]}
141
+ */
142
+ const themeRegistry = [];
78
143
 
79
144
  /**
80
145
  * Check if the custom element type has themes applied.
@@ -134,9 +199,170 @@ function registerStyles(themeFor, styles, options = {}) {
134
199
 
135
200
  if (window.Vaadin && window.Vaadin.styleModules) {
136
201
  window.Vaadin.styleModules.registerStyles(themeFor, styles, options);
202
+ } else {
203
+ themeRegistry.push({
204
+ themeFor,
205
+ styles,
206
+ include: options.include,
207
+ moduleId: options.moduleId,
208
+ });
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Returns all registered themes. By default the themeRegistry is returned as is.
214
+ * In case the style-modules adapter is imported, the themes are obtained from there instead
215
+ * @returns {Theme[]}
216
+ */
217
+ function getAllThemes() {
218
+ if (window.Vaadin && window.Vaadin.styleModules) {
219
+ return window.Vaadin.styleModules.getAllThemes();
220
+ }
221
+ return themeRegistry;
222
+ }
223
+
224
+ /**
225
+ * Returns true if the themeFor string matches the tag name
226
+ * @param {string} themeFor
227
+ * @param {string} tagName
228
+ * @returns {boolean}
229
+ */
230
+ function matchesThemeFor(themeFor, tagName) {
231
+ return (themeFor || '').split(' ').some((themeForToken) => {
232
+ return new RegExp(`^${themeForToken.split('*').join('.*')}$`, 'u').test(tagName);
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Maps the moduleName to an include priority number which is used for
238
+ * determining the order in which styles are applied.
239
+ * @param {string} moduleName
240
+ * @returns {number}
241
+ */
242
+ function getIncludePriority(moduleName = '') {
243
+ let includePriority = 0;
244
+ if (moduleName.startsWith('lumo-') || moduleName.startsWith('material-')) {
245
+ includePriority = 1;
246
+ } else if (moduleName.startsWith('vaadin-')) {
247
+ includePriority = 2;
248
+ }
249
+ return includePriority;
250
+ }
251
+
252
+ /**
253
+ * Gets an array of CSSResults matching the include property of the theme.
254
+ * @param {Theme} theme
255
+ * @returns {CSSResult[]}
256
+ */
257
+ function getIncludedStyles(theme) {
258
+ const includedStyles = [];
259
+ if (theme.include) {
260
+ [].concat(theme.include).forEach((includeModuleId) => {
261
+ const includedTheme = getAllThemes().find((s) => s.moduleId === includeModuleId);
262
+ if (includedTheme) {
263
+ includedStyles.push(...getIncludedStyles(includedTheme), ...includedTheme.styles);
264
+ } else {
265
+ console.warn(`Included moduleId ${includeModuleId} not found in style registry`);
266
+ }
267
+ }, theme.styles);
137
268
  }
269
+ return includedStyles;
270
+ }
271
+
272
+ /**
273
+ * Includes the styles to the template.
274
+ * @param {CSSResult[]} styles
275
+ * @param {HTMLTemplateElement} template
276
+ */
277
+ function addStylesToTemplate(styles, template) {
278
+ const styleEl = document.createElement('style');
279
+ styleEl.innerHTML = styles.map((style) => style.cssText).join('\n');
280
+ template.content.appendChild(styleEl);
281
+ }
282
+
283
+ /**
284
+ * Returns an array of themes that should be used for styling a component matching
285
+ * the tag name. The array is sorted by the include order.
286
+ * @param {string} tagName
287
+ * @returns {Theme[]}
288
+ */
289
+ function getThemes(tagName) {
290
+ const defaultModuleName = `${tagName}-default-theme`;
291
+
292
+ const themes = getAllThemes()
293
+ // Filter by matching themeFor properties
294
+ .filter((theme) => theme.moduleId !== defaultModuleName && matchesThemeFor(theme.themeFor, tagName))
295
+ .map((theme) => ({
296
+ ...theme,
297
+ // Prepend styles from included themes
298
+ styles: [...getIncludedStyles(theme), ...theme.styles],
299
+ // Map moduleId to includePriority
300
+ includePriority: getIncludePriority(theme.moduleId),
301
+ }))
302
+ // Sort by includePriority
303
+ .sort((themeA, themeB) => themeB.includePriority - themeA.includePriority);
304
+
305
+ if (themes.length > 0) {
306
+ return themes;
307
+ }
308
+ // No theme modules found, return the default module if it exists
309
+ return getAllThemes().filter((theme) => theme.moduleId === defaultModuleName);
138
310
  }
139
311
 
312
+ /**
313
+ * @polymerMixin
314
+ * @mixes ThemePropertyMixin
315
+ */
316
+ const ThemableMixin = (superClass) =>
317
+ class VaadinThemableMixin extends ThemePropertyMixin(superClass) {
318
+ /**
319
+ * Covers PolymerElement based component styling
320
+ * @protected
321
+ */
322
+ static finalize() {
323
+ super.finalize();
324
+
325
+ // Make sure not to run the logic intended for PolymerElement when LitElement is used.
326
+ if (this.elementStyles) {
327
+ return;
328
+ }
329
+
330
+ const template = this.prototype._template;
331
+ if (!template || classHasThemes(this)) {
332
+ return;
333
+ }
334
+
335
+ addStylesToTemplate(this.getStylesForThis(), template);
336
+ }
337
+
338
+ /**
339
+ * Covers LitElement based component styling
340
+ *
341
+ * @protected
342
+ */
343
+ static finalizeStyles(styles) {
344
+ // The "styles" object originates from the "static get styles()" function of
345
+ // a LitElement based component. The theme styles are added after it
346
+ // so that they can override the component styles.
347
+ const themeStyles = this.getStylesForThis();
348
+ return styles ? [...super.finalizeStyles(styles), ...themeStyles] : themeStyles;
349
+ }
350
+
351
+ /**
352
+ * Get styles for the component type
353
+ *
354
+ * @private
355
+ */
356
+ static getStylesForThis() {
357
+ const parent = Object.getPrototypeOf(this.prototype);
358
+ const inheritedThemes = (parent ? parent.constructor.__themes : []) || [];
359
+ this.__themes = [...inheritedThemes, ...getThemes(this.is)];
360
+ const themeStyles = this.__themes.flatMap((theme) => theme.styles);
361
+ // Remove duplicates
362
+ return themeStyles.filter((style, index) => index === themeStyles.lastIndexOf(style));
363
+ }
364
+ };
365
+
140
366
  /**
141
367
  * @license
142
368
  * Copyright (c) 2017 - 2023 Vaadin Ltd.
@@ -1620,19 +1846,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
1620
1846
  */
1621
1847
 
1622
1848
  // Microtask implemented using Mutation Observer
1623
- let microtaskCurrHandle = 0;
1624
- let microtaskLastHandle = 0;
1625
- let microtaskCallbacks = [];
1849
+ let microtaskCurrHandle$1 = 0;
1850
+ let microtaskLastHandle$1 = 0;
1851
+ let microtaskCallbacks$1 = [];
1626
1852
  let microtaskNodeContent = 0;
1627
- let microtaskScheduled = false;
1853
+ let microtaskScheduled$1 = false;
1628
1854
  let microtaskNode = document.createTextNode('');
1629
- new window.MutationObserver(microtaskFlush).observe(microtaskNode, {characterData: true});
1855
+ new window.MutationObserver(microtaskFlush$1).observe(microtaskNode, {characterData: true});
1630
1856
 
1631
- function microtaskFlush() {
1632
- microtaskScheduled = false;
1633
- const len = microtaskCallbacks.length;
1857
+ function microtaskFlush$1() {
1858
+ microtaskScheduled$1 = false;
1859
+ const len = microtaskCallbacks$1.length;
1634
1860
  for (let i = 0; i < len; i++) {
1635
- let cb = microtaskCallbacks[i];
1861
+ let cb = microtaskCallbacks$1[i];
1636
1862
  if (cb) {
1637
1863
  try {
1638
1864
  cb();
@@ -1641,8 +1867,8 @@ function microtaskFlush() {
1641
1867
  }
1642
1868
  }
1643
1869
  }
1644
- microtaskCallbacks.splice(0, len);
1645
- microtaskLastHandle += len;
1870
+ microtaskCallbacks$1.splice(0, len);
1871
+ microtaskLastHandle$1 += len;
1646
1872
  }
1647
1873
 
1648
1874
  /**
@@ -1651,7 +1877,7 @@ function microtaskFlush() {
1651
1877
  * @namespace
1652
1878
  * @summary Async interface wrapper around `setTimeout`.
1653
1879
  */
1654
- const timeOut = {
1880
+ const timeOut$1 = {
1655
1881
  /**
1656
1882
  * Returns a sub-module with the async interface providing the provided
1657
1883
  * delay.
@@ -1704,7 +1930,7 @@ const timeOut = {
1704
1930
  * @summary Async interface for enqueuing callbacks that run at microtask
1705
1931
  * timing.
1706
1932
  */
1707
- const microTask = {
1933
+ const microTask$1 = {
1708
1934
 
1709
1935
  /**
1710
1936
  * Enqueues a function called at microtask timing.
@@ -1714,12 +1940,12 @@ const microTask = {
1714
1940
  * @return {number} Handle used for canceling task
1715
1941
  */
1716
1942
  run(callback) {
1717
- if (!microtaskScheduled) {
1718
- microtaskScheduled = true;
1943
+ if (!microtaskScheduled$1) {
1944
+ microtaskScheduled$1 = true;
1719
1945
  microtaskNode.textContent = microtaskNodeContent++;
1720
1946
  }
1721
- microtaskCallbacks.push(callback);
1722
- return microtaskCurrHandle++;
1947
+ microtaskCallbacks$1.push(callback);
1948
+ return microtaskCurrHandle$1++;
1723
1949
  },
1724
1950
 
1725
1951
  /**
@@ -1730,12 +1956,12 @@ const microTask = {
1730
1956
  * @return {void}
1731
1957
  */
1732
1958
  cancel(handle) {
1733
- const idx = handle - microtaskLastHandle;
1959
+ const idx = handle - microtaskLastHandle$1;
1734
1960
  if (idx >= 0) {
1735
- if (!microtaskCallbacks[idx]) {
1961
+ if (!microtaskCallbacks$1[idx]) {
1736
1962
  throw new Error('invalid async handle: ' + handle);
1737
1963
  }
1738
- microtaskCallbacks[idx] = null;
1964
+ microtaskCallbacks$1[idx] = null;
1739
1965
  }
1740
1966
  }
1741
1967
 
@@ -1752,7 +1978,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
1752
1978
  */
1753
1979
 
1754
1980
  /** @const {!AsyncInterface} */
1755
- const microtask = microTask;
1981
+ const microtask = microTask$1;
1756
1982
 
1757
1983
  /**
1758
1984
  * Element class mixin that provides basic meta-programming for creating one
@@ -6750,7 +6976,7 @@ const builtCSS = window.ShadyCSS && window.ShadyCSS['cssBuild'];
6750
6976
  * @param {function(new:T)} superClass Class to apply mixin to.
6751
6977
  * @return {function(new:T)} superClass with mixin applied.
6752
6978
  */
6753
- const ElementMixin = dedupingMixin(base => {
6979
+ const ElementMixin$1 = dedupingMixin(base => {
6754
6980
  /**
6755
6981
  * @constructor
6756
6982
  * @implements {Polymer_PropertyEffects}
@@ -7688,7 +7914,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
7688
7914
  * key meta-programming features including template stamping, data-binding,
7689
7915
  * attribute deserialization, and property change observation
7690
7916
  */
7691
- const PolymerElement = ElementMixin(HTMLElement);
7917
+ const PolymerElement = ElementMixin$1(HTMLElement);
7692
7918
 
7693
7919
  const DEV_MODE_CODE_REGEXP =
7694
7920
  /\/\*[\*!]\s+vaadin-dev-mode:start([\s\S]*)vaadin-dev-mode:end\s+\*\*\//i;
@@ -8283,572 +8509,808 @@ const usageStatistics = function() {
8283
8509
 
8284
8510
  /**
8285
8511
  * @license
8286
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
8287
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8512
+ * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
8513
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
8514
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
8515
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
8516
+ * Code distributed by Google as part of the polymer project is also
8517
+ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
8288
8518
  */
8289
8519
 
8290
8520
  /**
8291
- * A mixin to provide disabled property for field components.
8521
+ * @fileoverview
8292
8522
  *
8293
- * @polymerMixin
8523
+ * This module provides a number of strategies for enqueuing asynchronous
8524
+ * tasks. Each sub-module provides a standard `run(fn)` interface that returns a
8525
+ * handle, and a `cancel(handle)` interface for canceling async tasks before
8526
+ * they run.
8527
+ *
8528
+ * @summary Module that provides a number of strategies for enqueuing
8529
+ * asynchronous tasks.
8294
8530
  */
8295
- const DisabledMixin = dedupingMixin(
8296
- (superclass) =>
8297
- class DisabledMixinClass extends superclass {
8298
- static get properties() {
8299
- return {
8300
- /**
8301
- * If true, the user cannot interact with this element.
8302
- */
8303
- disabled: {
8304
- type: Boolean,
8305
- value: false,
8306
- observer: '_disabledChanged',
8307
- reflectToAttribute: true,
8308
- },
8309
- };
8310
- }
8311
8531
 
8312
- /**
8313
- * @param {boolean} disabled
8314
- * @protected
8315
- */
8316
- _disabledChanged(disabled) {
8317
- this._setAriaDisabled(disabled);
8318
- }
8532
+ let microtaskCurrHandle = 0;
8533
+ let microtaskLastHandle = 0;
8534
+ const microtaskCallbacks = [];
8535
+ let microtaskScheduled = false;
8319
8536
 
8320
- /**
8321
- * @param {boolean} disabled
8322
- * @protected
8323
- */
8324
- _setAriaDisabled(disabled) {
8325
- if (disabled) {
8326
- this.setAttribute('aria-disabled', 'true');
8327
- } else {
8328
- this.removeAttribute('aria-disabled');
8329
- }
8537
+ function microtaskFlush() {
8538
+ microtaskScheduled = false;
8539
+ const len = microtaskCallbacks.length;
8540
+ for (let i = 0; i < len; i++) {
8541
+ const cb = microtaskCallbacks[i];
8542
+ if (cb) {
8543
+ try {
8544
+ cb();
8545
+ } catch (e) {
8546
+ setTimeout(() => {
8547
+ throw e;
8548
+ });
8330
8549
  }
8550
+ }
8551
+ }
8552
+ microtaskCallbacks.splice(0, len);
8553
+ microtaskLastHandle += len;
8554
+ }
8331
8555
 
8332
- /**
8333
- * Overrides the default element `click` method in order to prevent
8334
- * firing the `click` event when the element is disabled.
8335
- * @protected
8336
- * @override
8337
- */
8338
- click() {
8339
- if (!this.disabled) {
8340
- super.click();
8341
- }
8342
- }
8343
- },
8344
- );
8556
+ /**
8557
+ * Async interface wrapper around `setTimeout`.
8558
+ *
8559
+ * @namespace
8560
+ * @summary Async interface wrapper around `setTimeout`.
8561
+ */
8562
+ const timeOut = {
8563
+ /**
8564
+ * Returns a sub-module with the async interface providing the provided
8565
+ * delay.
8566
+ *
8567
+ * @memberof timeOut
8568
+ * @param {number=} delay Time to wait before calling callbacks in ms
8569
+ * @return {!AsyncInterface} An async timeout interface
8570
+ */
8571
+ after(delay) {
8572
+ return {
8573
+ run(fn) {
8574
+ return window.setTimeout(fn, delay);
8575
+ },
8576
+ cancel(handle) {
8577
+ window.clearTimeout(handle);
8578
+ },
8579
+ };
8580
+ },
8581
+ /**
8582
+ * Enqueues a function called in the next task.
8583
+ *
8584
+ * @memberof timeOut
8585
+ * @param {!Function} fn Callback to run
8586
+ * @param {number=} delay Delay in milliseconds
8587
+ * @return {number} Handle used for canceling task
8588
+ */
8589
+ run(fn, delay) {
8590
+ return window.setTimeout(fn, delay);
8591
+ },
8592
+ /**
8593
+ * Cancels a previously enqueued `timeOut` callback.
8594
+ *
8595
+ * @memberof timeOut
8596
+ * @param {number} handle Handle returned from `run` of callback to cancel
8597
+ * @return {void}
8598
+ */
8599
+ cancel(handle) {
8600
+ window.clearTimeout(handle);
8601
+ },
8602
+ };
8345
8603
 
8346
8604
  /**
8347
- * @license
8348
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
8349
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8605
+ * Async interface wrapper around `requestAnimationFrame`.
8606
+ *
8607
+ * @namespace
8608
+ * @summary Async interface wrapper around `requestAnimationFrame`.
8350
8609
  */
8610
+ const animationFrame = {
8611
+ /**
8612
+ * Enqueues a function called at `requestAnimationFrame` timing.
8613
+ *
8614
+ * @memberof animationFrame
8615
+ * @param {function(number):void} fn Callback to run
8616
+ * @return {number} Handle used for canceling task
8617
+ */
8618
+ run(fn) {
8619
+ return window.requestAnimationFrame(fn);
8620
+ },
8621
+ /**
8622
+ * Cancels a previously enqueued `animationFrame` callback.
8623
+ *
8624
+ * @memberof animationFrame
8625
+ * @param {number} handle Handle returned from `run` of callback to cancel
8626
+ * @return {void}
8627
+ */
8628
+ cancel(handle) {
8629
+ window.cancelAnimationFrame(handle);
8630
+ },
8631
+ };
8351
8632
 
8352
8633
  /**
8353
- * A mixin that manages keyboard handling.
8354
- * The mixin subscribes to the keyboard events while an actual implementation
8355
- * for the event handlers is left to the client (a component or another mixin).
8634
+ * Async interface wrapper around `requestIdleCallback`. Falls back to
8635
+ * `setTimeout` on browsers that do not support `requestIdleCallback`.
8356
8636
  *
8357
- * @polymerMixin
8637
+ * @namespace
8638
+ * @summary Async interface wrapper around `requestIdleCallback`.
8358
8639
  */
8359
- const KeyboardMixin = dedupingMixin(
8360
- (superclass) =>
8361
- class KeyboardMixinClass extends superclass {
8362
- /** @protected */
8363
- ready() {
8364
- super.ready();
8365
-
8366
- this.addEventListener('keydown', (event) => {
8367
- this._onKeyDown(event);
8368
- });
8369
-
8370
- this.addEventListener('keyup', (event) => {
8371
- this._onKeyUp(event);
8372
- });
8373
- }
8640
+ const idlePeriod = {
8641
+ /**
8642
+ * Enqueues a function called at `requestIdleCallback` timing.
8643
+ *
8644
+ * @memberof idlePeriod
8645
+ * @param {function(!IdleDeadline):void} fn Callback to run
8646
+ * @return {number} Handle used for canceling task
8647
+ */
8648
+ run(fn) {
8649
+ return window.requestIdleCallback ? window.requestIdleCallback(fn) : window.setTimeout(fn, 16);
8650
+ },
8651
+ /**
8652
+ * Cancels a previously enqueued `idlePeriod` callback.
8653
+ *
8654
+ * @memberof idlePeriod
8655
+ * @param {number} handle Handle returned from `run` of callback to cancel
8656
+ * @return {void}
8657
+ */
8658
+ cancel(handle) {
8659
+ if (window.cancelIdleCallback) {
8660
+ window.cancelIdleCallback(handle);
8661
+ } else {
8662
+ window.clearTimeout(handle);
8663
+ }
8664
+ },
8665
+ };
8374
8666
 
8375
- /**
8376
- * A handler for the `keydown` event. By default, it calls
8377
- * separate methods for handling "Enter" and "Escape" keys.
8378
- * Override the method to implement your own behavior.
8379
- *
8380
- * @param {KeyboardEvent} event
8381
- * @protected
8382
- */
8383
- _onKeyDown(event) {
8384
- switch (event.key) {
8385
- case 'Enter':
8386
- this._onEnter(event);
8387
- break;
8388
- case 'Escape':
8389
- this._onEscape(event);
8390
- break;
8391
- }
8392
- }
8667
+ /**
8668
+ * Async interface for enqueuing callbacks that run at microtask timing.
8669
+ *
8670
+ * @namespace
8671
+ * @summary Async interface for enqueuing callbacks that run at microtask
8672
+ * timing.
8673
+ */
8674
+ const microTask = {
8675
+ /**
8676
+ * Enqueues a function called at microtask timing.
8677
+ *
8678
+ * @memberof microTask
8679
+ * @param {!Function=} callback Callback to run
8680
+ * @return {number} Handle used for canceling task
8681
+ */
8682
+ run(callback) {
8683
+ if (!microtaskScheduled) {
8684
+ microtaskScheduled = true;
8685
+ queueMicrotask(() => microtaskFlush());
8686
+ }
8687
+ microtaskCallbacks.push(callback);
8688
+ const result = microtaskCurrHandle;
8689
+ microtaskCurrHandle += 1;
8690
+ return result;
8691
+ },
8393
8692
 
8394
- /**
8395
- * A handler for the `keyup` event. By default, it does nothing.
8396
- * Override the method to implement your own behavior.
8397
- *
8398
- * @param {KeyboardEvent} _event
8399
- * @protected
8400
- */
8401
- _onKeyUp(_event) {
8402
- // To be implemented.
8693
+ /**
8694
+ * Cancels a previously enqueued `microTask` callback.
8695
+ *
8696
+ * @memberof microTask
8697
+ * @param {number} handle Handle returned from `run` of callback to cancel
8698
+ * @return {void}
8699
+ */
8700
+ cancel(handle) {
8701
+ const idx = handle - microtaskLastHandle;
8702
+ if (idx >= 0) {
8703
+ if (!microtaskCallbacks[idx]) {
8704
+ throw new Error(`invalid async handle: ${handle}`);
8403
8705
  }
8706
+ microtaskCallbacks[idx] = null;
8707
+ }
8708
+ },
8709
+ };
8404
8710
 
8405
- /**
8406
- * A handler for the "Enter" key. By default, it does nothing.
8407
- * Override the method to implement your own behavior.
8408
- *
8409
- * @param {KeyboardEvent} _event
8410
- * @protected
8411
- */
8412
- _onEnter(_event) {
8413
- // To be implemented.
8414
- }
8711
+ /**
8712
+ @license
8713
+ Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
8714
+ This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
8715
+ The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
8716
+ The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
8717
+ Code distributed by Google as part of the polymer project is also
8718
+ subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
8719
+ */
8415
8720
 
8416
- /**
8417
- * A handler for the "Escape" key. By default, it does nothing.
8418
- * Override the method to implement your own behavior.
8419
- *
8420
- * @param {KeyboardEvent} _event
8421
- * @protected
8422
- */
8423
- _onEscape(_event) {
8424
- // To be implemented.
8425
- }
8426
- },
8427
- );
8721
+ const debouncerQueue = new Set();
8428
8722
 
8429
8723
  /**
8430
- * @license
8431
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
8432
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8724
+ * @summary Collapse multiple callbacks into one invocation after a timer.
8433
8725
  */
8726
+ class Debouncer {
8727
+ /**
8728
+ * Creates a debouncer if no debouncer is passed as a parameter
8729
+ * or it cancels an active debouncer otherwise. The following
8730
+ * example shows how a debouncer can be called multiple times within a
8731
+ * microtask and "debounced" such that the provided callback function is
8732
+ * called once. Add this method to a custom element:
8733
+ *
8734
+ * ```js
8735
+ * import {microTask} from '@vaadin/component-base/src/async.js';
8736
+ * import {Debouncer} from '@vaadin/component-base/src/debounce.js';
8737
+ * // ...
8738
+ *
8739
+ * _debounceWork() {
8740
+ * this._debounceJob = Debouncer.debounce(this._debounceJob,
8741
+ * microTask, () => this._doWork());
8742
+ * }
8743
+ * ```
8744
+ *
8745
+ * If the `_debounceWork` method is called multiple times within the same
8746
+ * microtask, the `_doWork` function will be called only once at the next
8747
+ * microtask checkpoint.
8748
+ *
8749
+ * Note: In testing it is often convenient to avoid asynchrony. To accomplish
8750
+ * this with a debouncer, you can use `enqueueDebouncer` and
8751
+ * `flush`. For example, extend the above example by adding
8752
+ * `enqueueDebouncer(this._debounceJob)` at the end of the
8753
+ * `_debounceWork` method. Then in a test, call `flush` to ensure
8754
+ * the debouncer has completed.
8755
+ *
8756
+ * @param {Debouncer?} debouncer Debouncer object.
8757
+ * @param {!AsyncInterface} asyncModule Object with Async interface
8758
+ * @param {function()} callback Callback to run.
8759
+ * @return {!Debouncer} Returns a debouncer object.
8760
+ */
8761
+ static debounce(debouncer, asyncModule, callback) {
8762
+ if (debouncer instanceof Debouncer) {
8763
+ // Cancel the async callback, but leave in debouncerQueue if it was
8764
+ // enqueued, to maintain 1.x flush order
8765
+ debouncer._cancelAsync();
8766
+ } else {
8767
+ debouncer = new Debouncer();
8768
+ }
8769
+ debouncer.setConfig(asyncModule, callback);
8770
+ return debouncer;
8771
+ }
8434
8772
 
8435
- // We consider the keyboard to be active if the window has received a keydown
8436
- // event since the last mousedown event.
8437
- let keyboardActive = false;
8773
+ constructor() {
8774
+ this._asyncModule = null;
8775
+ this._callback = null;
8776
+ this._timer = null;
8777
+ }
8438
8778
 
8439
- // Listen for top-level keydown and mousedown events.
8440
- // Use capture phase so we detect events even if they're handled.
8441
- window.addEventListener(
8442
- 'keydown',
8443
- () => {
8444
- keyboardActive = true;
8445
- },
8446
- { capture: true },
8447
- );
8779
+ /**
8780
+ * Sets the scheduler; that is, a module with the Async interface,
8781
+ * a callback and optional arguments to be passed to the run function
8782
+ * from the async module.
8783
+ *
8784
+ * @param {!AsyncInterface} asyncModule Object with Async interface.
8785
+ * @param {function()} callback Callback to run.
8786
+ * @return {void}
8787
+ */
8788
+ setConfig(asyncModule, callback) {
8789
+ this._asyncModule = asyncModule;
8790
+ this._callback = callback;
8791
+ this._timer = this._asyncModule.run(() => {
8792
+ this._timer = null;
8793
+ debouncerQueue.delete(this);
8794
+ this._callback();
8795
+ });
8796
+ }
8448
8797
 
8449
- window.addEventListener(
8450
- 'mousedown',
8451
- () => {
8452
- keyboardActive = false;
8453
- },
8454
- { capture: true },
8455
- );
8798
+ /**
8799
+ * Cancels an active debouncer and returns a reference to itself.
8800
+ *
8801
+ * @return {void}
8802
+ */
8803
+ cancel() {
8804
+ if (this.isActive()) {
8805
+ this._cancelAsync();
8806
+ // Canceling a debouncer removes its spot from the flush queue,
8807
+ // so if a debouncer is manually canceled and re-debounced, it
8808
+ // will reset its flush order (this is a very minor difference from 1.x)
8809
+ // Re-debouncing via the `debounce` API retains the 1.x FIFO flush order
8810
+ debouncerQueue.delete(this);
8811
+ }
8812
+ }
8813
+
8814
+ /**
8815
+ * Cancels a debouncer's async callback.
8816
+ *
8817
+ * @return {void}
8818
+ */
8819
+ _cancelAsync() {
8820
+ if (this.isActive()) {
8821
+ this._asyncModule.cancel(/** @type {number} */ (this._timer));
8822
+ this._timer = null;
8823
+ }
8824
+ }
8825
+
8826
+ /**
8827
+ * Flushes an active debouncer and returns a reference to itself.
8828
+ *
8829
+ * @return {void}
8830
+ */
8831
+ flush() {
8832
+ if (this.isActive()) {
8833
+ this.cancel();
8834
+ this._callback();
8835
+ }
8836
+ }
8837
+
8838
+ /**
8839
+ * Returns true if the debouncer is active.
8840
+ *
8841
+ * @return {boolean} True if active.
8842
+ */
8843
+ isActive() {
8844
+ return this._timer != null;
8845
+ }
8846
+ }
8456
8847
 
8457
8848
  /**
8458
- * Returns the actually focused element by traversing shadow
8459
- * trees recursively to ensure it's the leaf element.
8849
+ * Adds a `Debouncer` to a list of globally flushable tasks.
8460
8850
  *
8461
- * @return {Element}
8851
+ * @param {!Debouncer} debouncer Debouncer to enqueue
8852
+ * @return {void}
8462
8853
  */
8463
- function getDeepActiveElement() {
8464
- let host = document.activeElement || document.body;
8465
- while (host.shadowRoot && host.shadowRoot.activeElement) {
8466
- host = host.shadowRoot.activeElement;
8467
- }
8468
- return host;
8854
+ function enqueueDebouncer(debouncer) {
8855
+ debouncerQueue.add(debouncer);
8469
8856
  }
8470
8857
 
8471
8858
  /**
8472
- * Returns true if the window has received a keydown
8473
- * event since the last mousedown event.
8859
+ * Flushes any enqueued debouncers
8474
8860
  *
8475
- * @return {boolean}
8861
+ * @return {boolean} Returns whether any debouncers were flushed
8476
8862
  */
8477
- function isKeyboardActive() {
8478
- return keyboardActive;
8863
+ function flushDebouncers() {
8864
+ const didFlush = Boolean(debouncerQueue.size);
8865
+ // If new debouncers are added while flushing, Set.forEach will ensure
8866
+ // newly added ones are also flushed
8867
+ debouncerQueue.forEach((debouncer) => {
8868
+ try {
8869
+ debouncer.flush();
8870
+ } catch (e) {
8871
+ setTimeout(() => {
8872
+ throw e;
8873
+ });
8874
+ }
8875
+ });
8876
+ return didFlush;
8479
8877
  }
8480
8878
 
8879
+ const flush = () => {
8880
+ let debouncers;
8881
+ do {
8882
+ debouncers = flushDebouncers();
8883
+ } while (debouncers);
8884
+ };
8885
+
8481
8886
  /**
8482
- * Returns true if the element is hidden directly with `display: none` or `visibility: hidden`,
8483
- * false otherwise.
8484
- *
8485
- * The method doesn't traverse the element's ancestors, it only checks for the CSS properties
8486
- * set directly to or inherited by the element.
8487
- *
8488
- * @param {HTMLElement} element
8489
- * @return {boolean}
8887
+ * @license
8888
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
8889
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8490
8890
  */
8491
- function isElementHiddenDirectly(element) {
8492
- // Check inline style first to save a re-flow.
8493
- const style = element.style;
8494
- if (style.visibility === 'hidden' || style.display === 'none') {
8495
- return true;
8496
- }
8497
8891
 
8498
- const computedStyle = window.getComputedStyle(element);
8499
- if (computedStyle.visibility === 'hidden' || computedStyle.display === 'none') {
8500
- return true;
8892
+ /**
8893
+ * Array of Vaadin custom element classes that have been subscribed to the dir changes.
8894
+ */
8895
+ const directionSubscribers = [];
8896
+
8897
+ function alignDirs(element, documentDir, elementDir = element.getAttribute('dir')) {
8898
+ if (documentDir) {
8899
+ element.setAttribute('dir', documentDir);
8900
+ } else if (elementDir != null) {
8901
+ element.removeAttribute('dir');
8501
8902
  }
8903
+ }
8502
8904
 
8503
- return false;
8905
+ function getDocumentDir() {
8906
+ return document.documentElement.getAttribute('dir');
8504
8907
  }
8505
8908
 
8506
- /**
8507
- * Returns if element `a` has lower tab order compared to element `b`
8508
- * (both elements are assumed to be focusable and tabbable).
8509
- * Elements with tabindex = 0 have lower tab order compared to elements
8510
- * with tabindex > 0.
8511
- * If both have same tabindex, it returns false.
8512
- *
8513
- * @param {HTMLElement} a
8514
- * @param {HTMLElement} b
8515
- * @return {boolean}
8516
- */
8517
- function hasLowerTabOrder(a, b) {
8518
- // Normalize tabIndexes
8519
- // e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
8520
- const ati = Math.max(a.tabIndex, 0);
8521
- const bti = Math.max(b.tabIndex, 0);
8522
- return ati === 0 || bti === 0 ? bti > ati : ati > bti;
8909
+ function directionUpdater() {
8910
+ const documentDir = getDocumentDir();
8911
+ directionSubscribers.forEach((element) => {
8912
+ alignDirs(element, documentDir);
8913
+ });
8523
8914
  }
8524
8915
 
8916
+ const directionObserver = new MutationObserver(directionUpdater);
8917
+ directionObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] });
8918
+
8525
8919
  /**
8526
- * Merge sort iterator, merges the two arrays into one, sorted by tabindex.
8920
+ * A mixin to handle `dir` attribute based on the one set on the `<html>` element.
8527
8921
  *
8528
- * @param {HTMLElement[]} left
8529
- * @param {HTMLElement[]} right
8530
- * @return {HTMLElement[]}
8922
+ * @polymerMixin
8531
8923
  */
8532
- function mergeSortByTabIndex(left, right) {
8533
- const result = [];
8534
- while (left.length > 0 && right.length > 0) {
8535
- if (hasLowerTabOrder(left[0], right[0])) {
8536
- result.push(right.shift());
8537
- } else {
8538
- result.push(left.shift());
8924
+ const DirMixin = (superClass) =>
8925
+ class VaadinDirMixin extends superClass {
8926
+ static get properties() {
8927
+ return {
8928
+ /**
8929
+ * @protected
8930
+ */
8931
+ dir: {
8932
+ type: String,
8933
+ value: '',
8934
+ reflectToAttribute: true,
8935
+ converter: {
8936
+ fromAttribute: (attr) => {
8937
+ return !attr ? '' : attr;
8938
+ },
8939
+ toAttribute: (prop) => {
8940
+ return prop === '' ? null : prop;
8941
+ },
8942
+ },
8943
+ },
8944
+ };
8539
8945
  }
8540
- }
8541
8946
 
8542
- return result.concat(left, right);
8947
+ /**
8948
+ * @return {boolean}
8949
+ * @protected
8950
+ */
8951
+ get __isRTL() {
8952
+ return this.getAttribute('dir') === 'rtl';
8953
+ }
8954
+
8955
+ /** @protected */
8956
+ connectedCallback() {
8957
+ super.connectedCallback();
8958
+
8959
+ if (!this.hasAttribute('dir') || this.__restoreSubscription) {
8960
+ this.__subscribe();
8961
+ alignDirs(this, getDocumentDir(), null);
8962
+ }
8963
+ }
8964
+
8965
+ /** @protected */
8966
+ attributeChangedCallback(name, oldValue, newValue) {
8967
+ super.attributeChangedCallback(name, oldValue, newValue);
8968
+ if (name !== 'dir') {
8969
+ return;
8970
+ }
8971
+
8972
+ const documentDir = getDocumentDir();
8973
+
8974
+ // New value equals to the document direction and the element is not subscribed to the changes
8975
+ const newValueEqlDocDir = newValue === documentDir && directionSubscribers.indexOf(this) === -1;
8976
+ // Value was emptied and the element is not subscribed to the changes
8977
+ const newValueEmptied = !newValue && oldValue && directionSubscribers.indexOf(this) === -1;
8978
+ // New value is different and the old equals to document direction and the element is not subscribed to the changes
8979
+ const newDiffValue = newValue !== documentDir && oldValue === documentDir;
8980
+
8981
+ if (newValueEqlDocDir || newValueEmptied) {
8982
+ this.__subscribe();
8983
+ alignDirs(this, documentDir, newValue);
8984
+ } else if (newDiffValue) {
8985
+ this.__unsubscribe();
8986
+ }
8987
+ }
8988
+
8989
+ /** @protected */
8990
+ disconnectedCallback() {
8991
+ super.disconnectedCallback();
8992
+ this.__restoreSubscription = directionSubscribers.includes(this);
8993
+ this.__unsubscribe();
8994
+ }
8995
+
8996
+ /** @protected */
8997
+ _valueToNodeAttribute(node, value, attribute) {
8998
+ // Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
8999
+ // If the property contains an empty string then it should not create an empty attribute
9000
+ if (attribute === 'dir' && value === '' && !node.hasAttribute('dir')) {
9001
+ return;
9002
+ }
9003
+ super._valueToNodeAttribute(node, value, attribute);
9004
+ }
9005
+
9006
+ /** @protected */
9007
+ _attributeToProperty(attribute, value, type) {
9008
+ // Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
9009
+ // If the attribute is removed, then the dir property should contain an empty string instead of null
9010
+ if (attribute === 'dir' && !value) {
9011
+ this.dir = '';
9012
+ } else {
9013
+ super._attributeToProperty(attribute, value, type);
9014
+ }
9015
+ }
9016
+
9017
+ /** @private */
9018
+ __subscribe() {
9019
+ if (!directionSubscribers.includes(this)) {
9020
+ directionSubscribers.push(this);
9021
+ }
9022
+ }
9023
+
9024
+ /** @private */
9025
+ __unsubscribe() {
9026
+ if (directionSubscribers.includes(this)) {
9027
+ directionSubscribers.splice(directionSubscribers.indexOf(this), 1);
9028
+ }
9029
+ }
9030
+ };
9031
+
9032
+ /**
9033
+ * @license
9034
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
9035
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9036
+ */
9037
+
9038
+ if (!window.Vaadin) {
9039
+ window.Vaadin = {};
8543
9040
  }
8544
9041
 
8545
9042
  /**
8546
- * Sorts an array of elements by tabindex. Returns a new array.
8547
- *
8548
- * @param {HTMLElement[]} elements
8549
- * @return {HTMLElement[]}
9043
+ * Array of Vaadin custom element classes that have been finalized.
8550
9044
  */
8551
- function sortElementsByTabIndex(elements) {
8552
- // Implement a merge sort as Array.prototype.sort does a non-stable sort
8553
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
8554
- const len = elements.length;
8555
- if (len < 2) {
8556
- return elements;
8557
- }
8558
- const pivot = Math.ceil(len / 2);
8559
- const left = sortElementsByTabIndex(elements.slice(0, pivot));
8560
- const right = sortElementsByTabIndex(elements.slice(pivot));
9045
+ if (!window.Vaadin.registrations) {
9046
+ window.Vaadin.registrations = [];
9047
+ }
8561
9048
 
8562
- return mergeSortByTabIndex(left, right);
9049
+ if (!window.Vaadin.developmentModeCallback) {
9050
+ window.Vaadin.developmentModeCallback = {};
8563
9051
  }
8564
9052
 
9053
+ window.Vaadin.developmentModeCallback['vaadin-usage-statistics'] = function () {
9054
+ usageStatistics();
9055
+ };
9056
+
9057
+ let statsJob;
9058
+
9059
+ const registered = new Set();
9060
+
8565
9061
  /**
8566
- * Returns true if the element is focusable, otherwise false.
8567
- *
8568
- * The list of focusable elements is taken from http://stackoverflow.com/a/1600194/4228703.
8569
- * However, there isn't a definite list, it's up to the browser.
8570
- * The only standard we have is DOM Level 2 HTML https://www.w3.org/TR/DOM-Level-2-HTML/html.html,
8571
- * according to which the only elements that have a `focus()` method are:
8572
- * - HTMLInputElement
8573
- * - HTMLSelectElement
8574
- * - HTMLTextAreaElement
8575
- * - HTMLAnchorElement
8576
- *
8577
- * This notably omits HTMLButtonElement and HTMLAreaElement.
8578
- * Referring to these tests with tabbables in different browsers
8579
- * http://allyjs.io/data-tables/focusable.html
8580
- *
8581
- * @param {HTMLElement} element
8582
- * @return {boolean}
9062
+ * @polymerMixin
9063
+ * @mixes DirMixin
8583
9064
  */
8584
- function isElementFocusable(element) {
8585
- // The element cannot be focused if its `tabindex` attribute is set to `-1`.
8586
- if (element.matches('[tabindex="-1"]')) {
8587
- return false;
8588
- }
9065
+ const ElementMixin = (superClass) =>
9066
+ class VaadinElementMixin extends DirMixin(superClass) {
9067
+ static get version() {
9068
+ return '24.2.3';
9069
+ }
8589
9070
 
8590
- // Elements that cannot be focused if they have a `disabled` attribute.
8591
- if (element.matches('input, select, textarea, button, object')) {
8592
- return element.matches(':not([disabled])');
8593
- }
9071
+ /** @protected */
9072
+ static finalize() {
9073
+ super.finalize();
8594
9074
 
8595
- // Elements that can be focused even if they have a `disabled` attribute.
8596
- return element.matches('a[href], area[href], iframe, [tabindex], [contentEditable]');
8597
- }
9075
+ const { is } = this;
9076
+
9077
+ // Registers a class prototype for telemetry purposes.
9078
+ if (is && !registered.has(is)) {
9079
+ window.Vaadin.registrations.push(this);
9080
+ registered.add(is);
9081
+
9082
+ if (window.Vaadin.developmentModeCallback) {
9083
+ statsJob = Debouncer.debounce(statsJob, idlePeriod, () => {
9084
+ window.Vaadin.developmentModeCallback['vaadin-usage-statistics']();
9085
+ });
9086
+ enqueueDebouncer(statsJob);
9087
+ }
9088
+ }
9089
+ }
9090
+
9091
+ constructor() {
9092
+ super();
9093
+
9094
+ if (document.doctype === null) {
9095
+ console.warn(
9096
+ 'Vaadin components require the "standards mode" declaration. Please add <!DOCTYPE html> to the HTML document.',
9097
+ );
9098
+ }
9099
+ }
9100
+ };
8598
9101
 
8599
9102
  /**
8600
- * Returns true if the element is focused, false otherwise.
8601
- *
8602
- * @param {HTMLElement} element
8603
- * @return {boolean}
9103
+ * @license
9104
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
9105
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8604
9106
  */
8605
- function isElementFocused(element) {
8606
- return element.getRootNode().activeElement === element;
8607
- }
8608
9107
 
8609
9108
  /**
8610
- * Returns the normalized element tabindex. If not focusable, returns -1.
8611
- * It checks for the attribute "tabindex" instead of the element property
8612
- * `tabIndex` since browsers assign different values to it.
8613
- * e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
9109
+ * Returns an array of ancestor root nodes for the given node.
8614
9110
  *
8615
- * @param {HTMLElement} element
8616
- * @return {number}
9111
+ * A root node is either a document node or a document fragment node (Shadow Root).
9112
+ * The array is collected by a bottom-up DOM traversing that starts with the given node
9113
+ * and involves both the light DOM and ancestor shadow DOM trees.
9114
+ *
9115
+ * @param {Node} node
9116
+ * @return {Node[]}
8617
9117
  */
8618
- function normalizeTabIndex(element) {
8619
- if (!isElementFocusable(element)) {
8620
- return -1;
9118
+ function getAncestorRootNodes(node) {
9119
+ const result = [];
9120
+
9121
+ while (node) {
9122
+ if (node.nodeType === Node.DOCUMENT_NODE) {
9123
+ result.push(node);
9124
+ break;
9125
+ }
9126
+
9127
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
9128
+ result.push(node);
9129
+ node = node.host;
9130
+ continue;
9131
+ }
9132
+
9133
+ if (node.assignedSlot) {
9134
+ node = node.assignedSlot;
9135
+ continue;
9136
+ }
9137
+
9138
+ node = node.parentNode;
8621
9139
  }
8622
9140
 
8623
- const tabIndex = element.getAttribute('tabindex') || 0;
8624
- return Number(tabIndex);
9141
+ return result;
8625
9142
  }
8626
9143
 
8627
9144
  /**
8628
- * Searches for nodes that are tabbable and adds them to the `result` array.
8629
- * Returns if the `result` array needs to be sorted by tabindex.
9145
+ * Takes a string with values separated by space and returns a set the values
8630
9146
  *
8631
- * @param {Node} node The starting point for the search; added to `result` if tabbable.
8632
- * @param {HTMLElement[]} result
8633
- * @return {boolean}
8634
- * @private
9147
+ * @param {string} value
9148
+ * @return {Set<string>}
8635
9149
  */
8636
- function collectFocusableNodes(node, result) {
8637
- if (node.nodeType !== Node.ELEMENT_NODE || isElementHiddenDirectly(node)) {
8638
- // Don't traverse children if the node is not an HTML element or not visible.
8639
- return false;
8640
- }
8641
-
8642
- const element = /** @type {HTMLElement} */ (node);
8643
- const tabIndex = normalizeTabIndex(element);
8644
- let needsSort = tabIndex > 0;
8645
- if (tabIndex >= 0) {
8646
- result.push(element);
9150
+ function deserializeAttributeValue(value) {
9151
+ if (!value) {
9152
+ return new Set();
8647
9153
  }
8648
9154
 
8649
- let children = [];
8650
- if (element.localName === 'slot') {
8651
- children = element.assignedNodes({ flatten: true });
8652
- } else {
8653
- // Use shadow root if possible, will check for distributed nodes.
8654
- children = (element.shadowRoot || element).children;
8655
- }
8656
- [...children].forEach((child) => {
8657
- // Ensure method is always invoked to collect focusable children.
8658
- needsSort = collectFocusableNodes(child, result) || needsSort;
8659
- });
8660
- return needsSort;
9155
+ return new Set(value.split(' '));
8661
9156
  }
8662
9157
 
8663
9158
  /**
8664
- * Returns a tab-ordered array of focusable elements for a root element.
8665
- * The resulting array will include the root element if it is focusable.
8666
- *
8667
- * The method traverses nodes in shadow DOM trees too if any.
9159
+ * Takes a set of string values and returns a string with values separated by space
8668
9160
  *
8669
- * @param {HTMLElement} element
8670
- * @return {HTMLElement[]}
9161
+ * @param {Set<string>} values
9162
+ * @return {string}
8671
9163
  */
8672
- function getFocusableElements(element) {
8673
- const focusableElements = [];
8674
- const needsSortByTabIndex = collectFocusableNodes(element, focusableElements);
8675
- // If there is at least one element with tabindex > 0,
8676
- // we need to sort the final array by tabindex.
8677
- if (needsSortByTabIndex) {
8678
- return sortElementsByTabIndex(focusableElements);
8679
- }
8680
- return focusableElements;
9164
+ function serializeAttributeValue(values) {
9165
+ return values ? [...values].join(' ') : '';
8681
9166
  }
8682
9167
 
8683
9168
  /**
8684
- * @license
8685
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
8686
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9169
+ * Adds a value to an attribute containing space-delimited values.
9170
+ *
9171
+ * @param {HTMLElement} element
9172
+ * @param {string} attr
9173
+ * @param {string} value
8687
9174
  */
9175
+ function addValueToAttribute(element, attr, value) {
9176
+ const values = deserializeAttributeValue(element.getAttribute(attr));
9177
+ values.add(value);
9178
+ element.setAttribute(attr, serializeAttributeValue(values));
9179
+ }
8688
9180
 
8689
9181
  /**
8690
- * A mixin to handle `focused` and `focus-ring` attributes based on focus.
9182
+ * Removes a value from an attribute containing space-delimited values.
9183
+ * If the value is the last one, the whole attribute is removed.
8691
9184
  *
8692
- * @polymerMixin
9185
+ * @param {HTMLElement} element
9186
+ * @param {string} attr
9187
+ * @param {string} value
8693
9188
  */
8694
- const FocusMixin = dedupingMixin(
8695
- (superclass) =>
8696
- class FocusMixinClass extends superclass {
8697
- /**
8698
- * @protected
8699
- * @return {boolean}
8700
- */
8701
- get _keyboardActive() {
8702
- return isKeyboardActive();
8703
- }
9189
+ function removeValueFromAttribute(element, attr, value) {
9190
+ const values = deserializeAttributeValue(element.getAttribute(attr));
9191
+ values.delete(value);
9192
+ if (values.size === 0) {
9193
+ element.removeAttribute(attr);
9194
+ return;
9195
+ }
9196
+ element.setAttribute(attr, serializeAttributeValue(values));
9197
+ }
8704
9198
 
8705
- /** @protected */
8706
- ready() {
8707
- this.addEventListener('focusin', (e) => {
8708
- if (this._shouldSetFocus(e)) {
8709
- this._setFocused(true);
8710
- }
8711
- });
9199
+ /**
9200
+ * Returns true if the given node is an empty text node, false otherwise.
9201
+ *
9202
+ * @param {Node} node
9203
+ * @return {boolean}
9204
+ */
9205
+ function isEmptyTextNode(node) {
9206
+ return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '';
9207
+ }
8712
9208
 
8713
- this.addEventListener('focusout', (e) => {
8714
- if (this._shouldRemoveFocus(e)) {
8715
- this._setFocused(false);
8716
- }
8717
- });
9209
+ /**
9210
+ * @license
9211
+ * Copyright (c) 2023 Vaadin Ltd.
9212
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9213
+ */
8718
9214
 
8719
- // In super.ready() other 'focusin' and 'focusout' listeners might be
8720
- // added, so we call it after our own ones to ensure they execute first.
8721
- // Issue to watch out: when incorrect, <vaadin-combo-box> refocuses the
8722
- // input field on iOS after "Done" is pressed.
8723
- super.ready();
8724
- }
9215
+ /**
9216
+ * A helper for observing slot changes.
9217
+ */
9218
+ class SlotObserver {
9219
+ constructor(slot, callback) {
9220
+ /** @type HTMLSlotElement */
9221
+ this.slot = slot;
8725
9222
 
8726
- /** @protected */
8727
- disconnectedCallback() {
8728
- super.disconnectedCallback();
9223
+ /** @type Function */
9224
+ this.callback = callback;
8729
9225
 
8730
- // In non-Chrome browsers, blur does not fire on the element when it is disconnected.
8731
- // reproducible in `<vaadin-date-picker>` when closing on `Cancel` or `Today` click.
8732
- if (this.hasAttribute('focused')) {
8733
- this._setFocused(false);
8734
- }
8735
- }
9226
+ /** @type {Node[]} */
9227
+ this._storedNodes = [];
8736
9228
 
8737
- /**
8738
- * Override to change how focused and focus-ring attributes are set.
8739
- *
8740
- * @param {boolean} focused
8741
- * @protected
8742
- */
8743
- _setFocused(focused) {
8744
- this.toggleAttribute('focused', focused);
9229
+ this._connected = false;
9230
+ this._scheduled = false;
8745
9231
 
8746
- // Focus-ring is true when the element was focused from the keyboard.
8747
- // Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI
8748
- this.toggleAttribute('focus-ring', focused && this._keyboardActive);
8749
- }
9232
+ this._boundSchedule = () => {
9233
+ this._schedule();
9234
+ };
8750
9235
 
8751
- /**
8752
- * Override to define if the field receives focus based on the event.
8753
- *
8754
- * @param {FocusEvent} _event
8755
- * @return {boolean}
8756
- * @protected
8757
- */
8758
- _shouldSetFocus(_event) {
8759
- return true;
8760
- }
9236
+ this.connect();
9237
+ this._schedule();
9238
+ }
8761
9239
 
8762
- /**
8763
- * Override to define if the field loses focus based on the event.
8764
- *
8765
- * @param {FocusEvent} _event
8766
- * @return {boolean}
8767
- * @protected
8768
- */
8769
- _shouldRemoveFocus(_event) {
8770
- return true;
8771
- }
8772
- },
8773
- );
9240
+ /**
9241
+ * Activates an observer. This method is automatically called when
9242
+ * a `SlotObserver` is created. It should only be called to re-activate
9243
+ * an observer that has been deactivated via the `disconnect` method.
9244
+ */
9245
+ connect() {
9246
+ this.slot.addEventListener('slotchange', this._boundSchedule);
9247
+ this._connected = true;
9248
+ }
8774
9249
 
8775
- /**
8776
- * @license
8777
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
8778
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8779
- */
9250
+ /**
9251
+ * Deactivates the observer. After calling this method the observer callback
9252
+ * will not be called when changes to slotted nodes occur. The `connect` method
9253
+ * may be subsequently called to reactivate the observer.
9254
+ */
9255
+ disconnect() {
9256
+ this.slot.removeEventListener('slotchange', this._boundSchedule);
9257
+ this._connected = false;
9258
+ }
8780
9259
 
8781
- /**
8782
- * A mixin to toggle the `tabindex` attribute.
8783
- *
8784
- * The attribute is set to -1 whenever the user disables the element
8785
- * and restored with the last known value once the element is enabled.
8786
- *
8787
- * @polymerMixin
8788
- * @mixes DisabledMixin
8789
- */
8790
- const TabindexMixin = (superclass) =>
8791
- class TabindexMixinClass extends DisabledMixin(superclass) {
8792
- static get properties() {
8793
- return {
8794
- /**
8795
- * Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
8796
- *
8797
- * @protected
8798
- */
8799
- tabindex: {
8800
- type: Number,
8801
- reflectToAttribute: true,
8802
- observer: '_tabindexChanged',
8803
- },
9260
+ /** @private */
9261
+ _schedule() {
9262
+ if (!this._scheduled) {
9263
+ this._scheduled = true;
8804
9264
 
8805
- /**
8806
- * Stores the last known tabindex since the element has been disabled.
8807
- *
8808
- * @protected
8809
- */
8810
- _lastTabIndex: {
8811
- type: Number,
8812
- },
8813
- };
9265
+ queueMicrotask(() => {
9266
+ this.flush();
9267
+ });
8814
9268
  }
9269
+ }
8815
9270
 
8816
- /**
8817
- * When the element gets disabled, the observer saves the last known tabindex
8818
- * and makes the element not focusable by setting tabindex to -1.
8819
- * As soon as the element gets enabled, the observer restores the last known tabindex
8820
- * so that the element can be focusable again.
8821
- *
8822
- * @protected
8823
- * @override
8824
- */
8825
- _disabledChanged(disabled, oldDisabled) {
8826
- super._disabledChanged(disabled, oldDisabled);
9271
+ /**
9272
+ * Run the observer callback synchronously.
9273
+ */
9274
+ flush() {
9275
+ if (!this._connected) {
9276
+ return;
9277
+ }
8827
9278
 
8828
- if (disabled) {
8829
- if (this.tabindex !== undefined) {
8830
- this._lastTabIndex = this.tabindex;
9279
+ this._scheduled = false;
9280
+
9281
+ this._processNodes();
9282
+ }
9283
+
9284
+ /** @private */
9285
+ _processNodes() {
9286
+ const currentNodes = this.slot.assignedNodes({ flatten: true });
9287
+
9288
+ let addedNodes = [];
9289
+ const removedNodes = [];
9290
+ const movedNodes = [];
9291
+
9292
+ if (currentNodes.length) {
9293
+ addedNodes = currentNodes.filter((node) => !this._storedNodes.includes(node));
9294
+ }
9295
+
9296
+ if (this._storedNodes.length) {
9297
+ this._storedNodes.forEach((node, index) => {
9298
+ const idx = currentNodes.indexOf(node);
9299
+ if (idx === -1) {
9300
+ removedNodes.push(node);
9301
+ } else if (idx !== index) {
9302
+ movedNodes.push(node);
8831
9303
  }
8832
- this.tabindex = -1;
8833
- } else if (oldDisabled) {
8834
- this.tabindex = this._lastTabIndex;
8835
- }
9304
+ });
8836
9305
  }
8837
9306
 
8838
- /**
8839
- * When the user has changed tabindex while the element is disabled,
8840
- * the observer reverts tabindex to -1 and rather saves the new tabindex value to apply it later.
8841
- * The new value will be applied as soon as the element becomes enabled.
8842
- *
8843
- * @protected
8844
- */
8845
- _tabindexChanged(tabindex) {
8846
- if (this.disabled && tabindex !== -1) {
8847
- this._lastTabIndex = tabindex;
8848
- this.tabindex = -1;
8849
- }
9307
+ if (addedNodes.length || removedNodes.length || movedNodes.length) {
9308
+ this.callback({ addedNodes, movedNodes, removedNodes });
8850
9309
  }
8851
- };
9310
+
9311
+ this._storedNodes = currentNodes;
9312
+ }
9313
+ }
8852
9314
 
8853
9315
  /**
8854
9316
  * @license
@@ -8856,230 +9318,818 @@ const TabindexMixin = (superclass) =>
8856
9318
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
8857
9319
  */
8858
9320
 
9321
+ let uniqueId = 0;
9322
+
8859
9323
  /**
8860
- * A mixin to forward focus to an element in the light DOM.
9324
+ * Returns a unique integer id.
8861
9325
  *
8862
- * @polymerMixin
8863
- * @mixes FocusMixin
8864
- * @mixes TabindexMixin
9326
+ * @return {number}
8865
9327
  */
8866
- const DelegateFocusMixin = dedupingMixin(
8867
- (superclass) =>
8868
- class DelegateFocusMixinClass extends FocusMixin(TabindexMixin(superclass)) {
8869
- static get properties() {
8870
- return {
8871
- /**
8872
- * Specify that this control should have input focus when the page loads.
8873
- */
8874
- autofocus: {
8875
- type: Boolean,
8876
- },
8877
-
8878
- /**
8879
- * A reference to the focusable element controlled by the mixin.
8880
- * It can be an input, textarea, button or any element with tabindex > -1.
8881
- *
8882
- * Any component implementing this mixin is expected to provide it
8883
- * by using `this._setFocusElement(input)` Polymer API.
8884
- *
8885
- * Toggling `tabindex` attribute on the host element propagates its value to `focusElement`.
8886
- *
8887
- * @protected
8888
- * @type {!HTMLElement}
8889
- */
8890
- focusElement: {
8891
- type: Object,
8892
- readOnly: true,
8893
- observer: '_focusElementChanged',
8894
- },
8895
-
8896
- /**
8897
- * Override the property from `TabIndexMixin`
8898
- * to ensure the `tabindex` attribute of the focus element
8899
- * will be restored to `0` after re-enabling the element.
8900
- *
8901
- * @protected
8902
- * @override
8903
- */
8904
- _lastTabIndex: {
8905
- value: 0,
8906
- },
8907
- };
8908
- }
9328
+ function generateUniqueId() {
9329
+ // eslint-disable-next-line no-plusplus
9330
+ return uniqueId++;
9331
+ }
8909
9332
 
8910
- constructor() {
8911
- super();
9333
+ /**
9334
+ * @license
9335
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
9336
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9337
+ */
8912
9338
 
8913
- this._boundOnBlur = this._onBlur.bind(this);
8914
- this._boundOnFocus = this._onFocus.bind(this);
8915
- }
9339
+ /**
9340
+ * A controller for providing content to slot element and observing changes.
9341
+ */
9342
+ class SlotController extends EventTarget {
9343
+ /**
9344
+ * Ensure that every instance has unique ID.
9345
+ *
9346
+ * @param {HTMLElement} host
9347
+ * @param {string} slotName
9348
+ * @return {string}
9349
+ * @protected
9350
+ */
9351
+ static generateId(host, slotName) {
9352
+ const prefix = slotName || 'default';
9353
+ return `${prefix}-${host.localName}-${generateUniqueId()}`;
9354
+ }
8916
9355
 
8917
- /** @protected */
8918
- ready() {
8919
- super.ready();
9356
+ constructor(host, slotName, tagName, config = {}) {
9357
+ super();
8920
9358
 
8921
- if (this.autofocus && !this.disabled) {
8922
- requestAnimationFrame(() => {
8923
- this.focus();
8924
- this.setAttribute('focus-ring', '');
8925
- });
8926
- }
8927
- }
9359
+ const { initializer, multiple, observe, useUniqueId } = config;
8928
9360
 
8929
- /**
8930
- * @protected
8931
- * @override
8932
- */
8933
- focus() {
8934
- if (this.focusElement && !this.disabled) {
8935
- this.focusElement.focus();
8936
- }
8937
- }
9361
+ this.host = host;
9362
+ this.slotName = slotName;
9363
+ this.tagName = tagName;
9364
+ this.observe = typeof observe === 'boolean' ? observe : true;
9365
+ this.multiple = typeof multiple === 'boolean' ? multiple : false;
9366
+ this.slotInitializer = initializer;
8938
9367
 
8939
- /**
8940
- * @protected
8941
- * @override
8942
- */
8943
- blur() {
8944
- if (this.focusElement) {
8945
- this.focusElement.blur();
8946
- }
8947
- }
9368
+ if (multiple) {
9369
+ this.nodes = [];
9370
+ }
8948
9371
 
8949
- /**
8950
- * @protected
8951
- * @override
8952
- */
8953
- click() {
8954
- if (this.focusElement && !this.disabled) {
8955
- this.focusElement.click();
8956
- }
8957
- }
9372
+ // Only generate the default ID if requested by the controller.
9373
+ if (useUniqueId) {
9374
+ this.defaultId = this.constructor.generateId(host, slotName);
9375
+ }
9376
+ }
8958
9377
 
8959
- /** @protected */
8960
- _focusElementChanged(element, oldElement) {
8961
- if (element) {
8962
- element.disabled = this.disabled;
8963
- this._addFocusListeners(element);
8964
- this.__forwardTabIndex(this.tabindex);
8965
- } else if (oldElement) {
8966
- this._removeFocusListeners(oldElement);
8967
- }
9378
+ hostConnected() {
9379
+ if (!this.initialized) {
9380
+ if (this.multiple) {
9381
+ this.initMultiple();
9382
+ } else {
9383
+ this.initSingle();
8968
9384
  }
8969
9385
 
8970
- /**
8971
- * @param {HTMLElement} element
8972
- * @protected
8973
- */
8974
- _addFocusListeners(element) {
8975
- element.addEventListener('blur', this._boundOnBlur);
8976
- element.addEventListener('focus', this._boundOnFocus);
9386
+ if (this.observe) {
9387
+ this.observeSlot();
8977
9388
  }
8978
9389
 
8979
- /**
8980
- * @param {HTMLElement} element
8981
- * @protected
8982
- */
8983
- _removeFocusListeners(element) {
8984
- element.removeEventListener('blur', this._boundOnBlur);
8985
- element.removeEventListener('focus', this._boundOnFocus);
8986
- }
9390
+ this.initialized = true;
9391
+ }
9392
+ }
8987
9393
 
8988
- /**
8989
- * Focus event does not bubble, so we dispatch it manually
8990
- * on the host element to support adding focus listeners
8991
- * when the focusable element is placed in light DOM.
8992
- * @param {FocusEvent} event
8993
- * @protected
8994
- */
8995
- _onFocus(event) {
8996
- event.stopPropagation();
8997
- this.dispatchEvent(new Event('focus'));
8998
- }
9394
+ /** @protected */
9395
+ initSingle() {
9396
+ let node = this.getSlotChild();
8999
9397
 
9000
- /**
9001
- * Blur event does not bubble, so we dispatch it manually
9002
- * on the host element to support adding blur listeners
9003
- * when the focusable element is placed in light DOM.
9004
- * @param {FocusEvent} event
9005
- * @protected
9006
- */
9007
- _onBlur(event) {
9008
- event.stopPropagation();
9009
- this.dispatchEvent(new Event('blur'));
9010
- }
9398
+ if (!node) {
9399
+ node = this.attachDefaultNode();
9400
+ this.initNode(node);
9401
+ } else {
9402
+ this.node = node;
9403
+ this.initAddedNode(node);
9404
+ }
9405
+ }
9011
9406
 
9012
- /**
9013
- * @param {FocusEvent} event
9014
- * @return {boolean}
9015
- * @protected
9016
- * @override
9017
- */
9018
- _shouldSetFocus(event) {
9019
- return event.target === this.focusElement;
9020
- }
9407
+ /** @protected */
9408
+ initMultiple() {
9409
+ const children = this.getSlotChildren();
9021
9410
 
9022
- /**
9023
- * @param {FocusEvent} event
9024
- * @return {boolean}
9025
- * @protected
9026
- * @override
9027
- */
9028
- _shouldRemoveFocus(event) {
9029
- return event.target === this.focusElement;
9411
+ if (children.length === 0) {
9412
+ const defaultNode = this.attachDefaultNode();
9413
+ if (defaultNode) {
9414
+ this.nodes = [defaultNode];
9415
+ this.initNode(defaultNode);
9030
9416
  }
9417
+ } else {
9418
+ this.nodes = children;
9419
+ children.forEach((node) => {
9420
+ this.initAddedNode(node);
9421
+ });
9422
+ }
9423
+ }
9031
9424
 
9032
- /**
9033
- * @param {boolean} disabled
9034
- * @param {boolean} oldDisabled
9035
- * @protected
9036
- * @override
9037
- */
9038
- _disabledChanged(disabled, oldDisabled) {
9039
- super._disabledChanged(disabled, oldDisabled);
9425
+ /**
9426
+ * Create and attach default node using the provided tag name, if any.
9427
+ * @return {Node | undefined}
9428
+ * @protected
9429
+ */
9430
+ attachDefaultNode() {
9431
+ const { host, slotName, tagName } = this;
9040
9432
 
9041
- if (this.focusElement) {
9042
- this.focusElement.disabled = disabled;
9043
- }
9433
+ // Check if the node was created previously and if so, reuse it.
9434
+ let node = this.defaultNode;
9044
9435
 
9045
- if (disabled) {
9046
- this.blur();
9436
+ // Tag name is optional, sometimes we don't init default content.
9437
+ if (!node && tagName) {
9438
+ node = document.createElement(tagName);
9439
+ if (node instanceof Element) {
9440
+ if (slotName !== '') {
9441
+ node.setAttribute('slot', slotName);
9047
9442
  }
9443
+ this.node = node;
9444
+ this.defaultNode = node;
9048
9445
  }
9446
+ }
9049
9447
 
9050
- /**
9051
- * Override an observer from `TabindexMixin`.
9052
- * Do not call super to remove tabindex attribute
9053
- * from the host after it has been forwarded.
9054
- * @param {string} tabindex
9055
- * @protected
9056
- * @override
9448
+ if (node) {
9449
+ host.appendChild(node);
9450
+ }
9451
+
9452
+ return node;
9453
+ }
9454
+
9455
+ /**
9456
+ * Return the list of nodes matching the slot managed by the controller.
9457
+ * @return {Node}
9458
+ */
9459
+ getSlotChildren() {
9460
+ const { slotName } = this;
9461
+ return Array.from(this.host.childNodes).filter((node) => {
9462
+ // Either an element (any slot) or a text node (only un-named slot).
9463
+ return (
9464
+ (node.nodeType === Node.ELEMENT_NODE && node.slot === slotName) ||
9465
+ (node.nodeType === Node.TEXT_NODE && node.textContent.trim() && slotName === '')
9466
+ );
9467
+ });
9468
+ }
9469
+
9470
+ /**
9471
+ * Return a reference to the node managed by the controller.
9472
+ * @return {Node}
9473
+ */
9474
+ getSlotChild() {
9475
+ return this.getSlotChildren()[0];
9476
+ }
9477
+
9478
+ /**
9479
+ * Run `slotInitializer` for the node managed by the controller.
9480
+ *
9481
+ * @param {Node} node
9482
+ * @protected
9483
+ */
9484
+ initNode(node) {
9485
+ const { slotInitializer } = this;
9486
+ // Don't try to bind `this` to initializer (normally it's arrow function).
9487
+ // Instead, pass the host as a first argument to access component's state.
9488
+ if (slotInitializer) {
9489
+ slotInitializer(node, this.host);
9490
+ }
9491
+ }
9492
+
9493
+ /**
9494
+ * Override to initialize the newly added custom node.
9495
+ *
9496
+ * @param {Node} _node
9497
+ * @protected
9498
+ */
9499
+ initCustomNode(_node) {}
9500
+
9501
+ /**
9502
+ * Override to teardown slotted node when it's removed.
9503
+ *
9504
+ * @param {Node} _node
9505
+ * @protected
9506
+ */
9507
+ teardownNode(_node) {}
9508
+
9509
+ /**
9510
+ * Run both `initCustomNode` and `initNode` for a custom slotted node.
9511
+ *
9512
+ * @param {Node} node
9513
+ * @protected
9514
+ */
9515
+ initAddedNode(node) {
9516
+ if (node !== this.defaultNode) {
9517
+ this.initCustomNode(node);
9518
+ this.initNode(node);
9519
+ }
9520
+ }
9521
+
9522
+ /**
9523
+ * Setup the observer to manage slot content changes.
9524
+ * @protected
9525
+ */
9526
+ observeSlot() {
9527
+ const { slotName } = this;
9528
+ const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
9529
+ const slot = this.host.shadowRoot.querySelector(selector);
9530
+
9531
+ this.__slotObserver = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
9532
+ const current = this.multiple ? this.nodes : [this.node];
9533
+
9534
+ // Calling `slot.assignedNodes()` includes whitespace text nodes in case of default slot:
9535
+ // unlike comment nodes, they are not filtered out. So we need to manually ignore them.
9536
+ const newNodes = addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
9537
+
9538
+ if (removedNodes.length) {
9539
+ this.nodes = current.filter((node) => !removedNodes.includes(node));
9540
+
9541
+ removedNodes.forEach((node) => {
9542
+ this.teardownNode(node);
9543
+ });
9544
+ }
9545
+
9546
+ if (newNodes && newNodes.length > 0) {
9547
+ if (this.multiple) {
9548
+ // Remove default node if exists
9549
+ if (this.defaultNode) {
9550
+ this.defaultNode.remove();
9551
+ }
9552
+ this.nodes = [...current, ...newNodes].filter((node) => node !== this.defaultNode);
9553
+ newNodes.forEach((node) => {
9554
+ this.initAddedNode(node);
9555
+ });
9556
+ } else {
9557
+ // Remove previous node if exists
9558
+ if (this.node) {
9559
+ this.node.remove();
9560
+ }
9561
+ this.node = newNodes[0];
9562
+ this.initAddedNode(this.node);
9563
+ }
9564
+ }
9565
+ });
9566
+ }
9567
+ }
9568
+
9569
+ /**
9570
+ * @license
9571
+ * Copyright (c) 2022 - 2023 Vaadin Ltd.
9572
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9573
+ */
9574
+
9575
+ /**
9576
+ * A controller that manages the slotted tooltip element.
9577
+ */
9578
+ class TooltipController extends SlotController {
9579
+ constructor(host) {
9580
+ // Do not provide slot factory to create tooltip lazily.
9581
+ super(host, 'tooltip');
9582
+
9583
+ this.setTarget(host);
9584
+ }
9585
+
9586
+ /**
9587
+ * Override to initialize the newly added custom tooltip.
9588
+ *
9589
+ * @param {Node} tooltipNode
9590
+ * @protected
9591
+ * @override
9592
+ */
9593
+ initCustomNode(tooltipNode) {
9594
+ tooltipNode.target = this.target;
9595
+
9596
+ if (this.ariaTarget !== undefined) {
9597
+ tooltipNode.ariaTarget = this.ariaTarget;
9598
+ }
9599
+
9600
+ if (this.context !== undefined) {
9601
+ tooltipNode.context = this.context;
9602
+ }
9603
+
9604
+ if (this.manual !== undefined) {
9605
+ tooltipNode.manual = this.manual;
9606
+ }
9607
+
9608
+ if (this.opened !== undefined) {
9609
+ tooltipNode.opened = this.opened;
9610
+ }
9611
+
9612
+ if (this.position !== undefined) {
9613
+ tooltipNode._position = this.position;
9614
+ }
9615
+
9616
+ if (this.shouldShow !== undefined) {
9617
+ tooltipNode.shouldShow = this.shouldShow;
9618
+ }
9619
+
9620
+ this.__notifyChange();
9621
+ }
9622
+
9623
+ /**
9624
+ * Override to notify the host when the tooltip is removed.
9625
+ *
9626
+ * @param {Node} tooltipNode
9627
+ * @protected
9628
+ * @override
9629
+ */
9630
+ teardownNode() {
9631
+ this.__notifyChange();
9632
+ }
9633
+
9634
+ /**
9635
+ * Set an HTML element for linking with the tooltip overlay
9636
+ * via `aria-describedby` attribute used by screen readers.
9637
+ * @param {HTMLElement} ariaTarget
9638
+ */
9639
+ setAriaTarget(ariaTarget) {
9640
+ this.ariaTarget = ariaTarget;
9641
+
9642
+ const tooltipNode = this.node;
9643
+ if (tooltipNode) {
9644
+ tooltipNode.ariaTarget = ariaTarget;
9645
+ }
9646
+ }
9647
+
9648
+ /**
9649
+ * Set a context object to be used by generator.
9650
+ * @param {object} context
9651
+ */
9652
+ setContext(context) {
9653
+ this.context = context;
9654
+
9655
+ const tooltipNode = this.node;
9656
+ if (tooltipNode) {
9657
+ tooltipNode.context = context;
9658
+ }
9659
+ }
9660
+
9661
+ /**
9662
+ * Toggle manual state on the slotted tooltip.
9663
+ * @param {boolean} manual
9664
+ */
9665
+ setManual(manual) {
9666
+ this.manual = manual;
9667
+
9668
+ const tooltipNode = this.node;
9669
+ if (tooltipNode) {
9670
+ tooltipNode.manual = manual;
9671
+ }
9672
+ }
9673
+
9674
+ /**
9675
+ * Toggle opened state on the slotted tooltip.
9676
+ * @param {boolean} opened
9677
+ */
9678
+ setOpened(opened) {
9679
+ this.opened = opened;
9680
+
9681
+ const tooltipNode = this.node;
9682
+ if (tooltipNode) {
9683
+ tooltipNode.opened = opened;
9684
+ }
9685
+ }
9686
+
9687
+ /**
9688
+ * Set default position for the slotted tooltip.
9689
+ * This can be overridden by setting the position
9690
+ * using corresponding property or attribute.
9691
+ * @param {string} position
9692
+ */
9693
+ setPosition(position) {
9694
+ this.position = position;
9695
+
9696
+ const tooltipNode = this.node;
9697
+ if (tooltipNode) {
9698
+ tooltipNode._position = position;
9699
+ }
9700
+ }
9701
+
9702
+ /**
9703
+ * Set function used to detect whether to show
9704
+ * the tooltip based on a condition.
9705
+ * @param {Function} shouldShow
9706
+ */
9707
+ setShouldShow(shouldShow) {
9708
+ this.shouldShow = shouldShow;
9709
+
9710
+ const tooltipNode = this.node;
9711
+ if (tooltipNode) {
9712
+ tooltipNode.shouldShow = shouldShow;
9713
+ }
9714
+ }
9715
+
9716
+ /**
9717
+ * Set an HTML element to attach the tooltip to.
9718
+ * @param {HTMLElement} target
9719
+ */
9720
+ setTarget(target) {
9721
+ this.target = target;
9722
+
9723
+ const tooltipNode = this.node;
9724
+ if (tooltipNode) {
9725
+ tooltipNode.target = target;
9726
+ }
9727
+ }
9728
+
9729
+ /** @private */
9730
+ __notifyChange() {
9731
+ this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node: this.node } }));
9732
+ }
9733
+ }
9734
+
9735
+ /**
9736
+ * @license
9737
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
9738
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9739
+ */
9740
+
9741
+ /**
9742
+ * A mixin to provide disabled property for field components.
9743
+ *
9744
+ * @polymerMixin
9745
+ */
9746
+ const DisabledMixin = dedupingMixin(
9747
+ (superclass) =>
9748
+ class DisabledMixinClass extends superclass {
9749
+ static get properties() {
9750
+ return {
9751
+ /**
9752
+ * If true, the user cannot interact with this element.
9753
+ */
9754
+ disabled: {
9755
+ type: Boolean,
9756
+ value: false,
9757
+ observer: '_disabledChanged',
9758
+ reflectToAttribute: true,
9759
+ },
9760
+ };
9761
+ }
9762
+
9763
+ /**
9764
+ * @param {boolean} disabled
9765
+ * @protected
9766
+ */
9767
+ _disabledChanged(disabled) {
9768
+ this._setAriaDisabled(disabled);
9769
+ }
9770
+
9771
+ /**
9772
+ * @param {boolean} disabled
9773
+ * @protected
9774
+ */
9775
+ _setAriaDisabled(disabled) {
9776
+ if (disabled) {
9777
+ this.setAttribute('aria-disabled', 'true');
9778
+ } else {
9779
+ this.removeAttribute('aria-disabled');
9780
+ }
9781
+ }
9782
+
9783
+ /**
9784
+ * Overrides the default element `click` method in order to prevent
9785
+ * firing the `click` event when the element is disabled.
9786
+ * @protected
9787
+ * @override
9788
+ */
9789
+ click() {
9790
+ if (!this.disabled) {
9791
+ super.click();
9792
+ }
9793
+ }
9794
+ },
9795
+ );
9796
+
9797
+ /**
9798
+ * @license
9799
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
9800
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9801
+ */
9802
+
9803
+ /**
9804
+ * A mixin that manages keyboard handling.
9805
+ * The mixin subscribes to the keyboard events while an actual implementation
9806
+ * for the event handlers is left to the client (a component or another mixin).
9807
+ *
9808
+ * @polymerMixin
9809
+ */
9810
+ const KeyboardMixin = dedupingMixin(
9811
+ (superclass) =>
9812
+ class KeyboardMixinClass extends superclass {
9813
+ /** @protected */
9814
+ ready() {
9815
+ super.ready();
9816
+
9817
+ this.addEventListener('keydown', (event) => {
9818
+ this._onKeyDown(event);
9819
+ });
9820
+
9821
+ this.addEventListener('keyup', (event) => {
9822
+ this._onKeyUp(event);
9823
+ });
9824
+ }
9825
+
9826
+ /**
9827
+ * A handler for the `keydown` event. By default, it calls
9828
+ * separate methods for handling "Enter" and "Escape" keys.
9829
+ * Override the method to implement your own behavior.
9830
+ *
9831
+ * @param {KeyboardEvent} event
9832
+ * @protected
9833
+ */
9834
+ _onKeyDown(event) {
9835
+ switch (event.key) {
9836
+ case 'Enter':
9837
+ this._onEnter(event);
9838
+ break;
9839
+ case 'Escape':
9840
+ this._onEscape(event);
9841
+ break;
9842
+ }
9843
+ }
9844
+
9845
+ /**
9846
+ * A handler for the `keyup` event. By default, it does nothing.
9847
+ * Override the method to implement your own behavior.
9848
+ *
9849
+ * @param {KeyboardEvent} _event
9850
+ * @protected
9057
9851
  */
9058
- _tabindexChanged(tabindex) {
9059
- this.__forwardTabIndex(tabindex);
9852
+ _onKeyUp(_event) {
9853
+ // To be implemented.
9060
9854
  }
9061
9855
 
9062
- /** @private */
9063
- __forwardTabIndex(tabindex) {
9064
- if (tabindex !== undefined && this.focusElement) {
9065
- this.focusElement.tabIndex = tabindex;
9856
+ /**
9857
+ * A handler for the "Enter" key. By default, it does nothing.
9858
+ * Override the method to implement your own behavior.
9859
+ *
9860
+ * @param {KeyboardEvent} _event
9861
+ * @protected
9862
+ */
9863
+ _onEnter(_event) {
9864
+ // To be implemented.
9865
+ }
9866
+
9867
+ /**
9868
+ * A handler for the "Escape" key. By default, it does nothing.
9869
+ * Override the method to implement your own behavior.
9870
+ *
9871
+ * @param {KeyboardEvent} _event
9872
+ * @protected
9873
+ */
9874
+ _onEscape(_event) {
9875
+ // To be implemented.
9876
+ }
9877
+ },
9878
+ );
9879
+
9880
+ /**
9881
+ * @license
9882
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
9883
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9884
+ */
9885
+
9886
+ // We consider the keyboard to be active if the window has received a keydown
9887
+ // event since the last mousedown event.
9888
+ let keyboardActive = false;
9889
+
9890
+ // Listen for top-level keydown and mousedown events.
9891
+ // Use capture phase so we detect events even if they're handled.
9892
+ window.addEventListener(
9893
+ 'keydown',
9894
+ () => {
9895
+ keyboardActive = true;
9896
+ },
9897
+ { capture: true },
9898
+ );
9899
+
9900
+ window.addEventListener(
9901
+ 'mousedown',
9902
+ () => {
9903
+ keyboardActive = false;
9904
+ },
9905
+ { capture: true },
9906
+ );
9907
+
9908
+ /**
9909
+ * Returns the actually focused element by traversing shadow
9910
+ * trees recursively to ensure it's the leaf element.
9911
+ *
9912
+ * @return {Element}
9913
+ */
9914
+ function getDeepActiveElement() {
9915
+ let host = document.activeElement || document.body;
9916
+ while (host.shadowRoot && host.shadowRoot.activeElement) {
9917
+ host = host.shadowRoot.activeElement;
9918
+ }
9919
+ return host;
9920
+ }
9921
+
9922
+ /**
9923
+ * Returns true if the window has received a keydown
9924
+ * event since the last mousedown event.
9925
+ *
9926
+ * @return {boolean}
9927
+ */
9928
+ function isKeyboardActive() {
9929
+ return keyboardActive;
9930
+ }
9931
+
9932
+ /**
9933
+ * Returns true if the element is hidden directly with `display: none` or `visibility: hidden`,
9934
+ * false otherwise.
9935
+ *
9936
+ * The method doesn't traverse the element's ancestors, it only checks for the CSS properties
9937
+ * set directly to or inherited by the element.
9938
+ *
9939
+ * @param {HTMLElement} element
9940
+ * @return {boolean}
9941
+ */
9942
+ function isElementHiddenDirectly(element) {
9943
+ // Check inline style first to save a re-flow.
9944
+ const style = element.style;
9945
+ if (style.visibility === 'hidden' || style.display === 'none') {
9946
+ return true;
9947
+ }
9948
+
9949
+ const computedStyle = window.getComputedStyle(element);
9950
+ if (computedStyle.visibility === 'hidden' || computedStyle.display === 'none') {
9951
+ return true;
9952
+ }
9953
+
9954
+ return false;
9955
+ }
9956
+
9957
+ /**
9958
+ * Returns if element `a` has lower tab order compared to element `b`
9959
+ * (both elements are assumed to be focusable and tabbable).
9960
+ * Elements with tabindex = 0 have lower tab order compared to elements
9961
+ * with tabindex > 0.
9962
+ * If both have same tabindex, it returns false.
9963
+ *
9964
+ * @param {HTMLElement} a
9965
+ * @param {HTMLElement} b
9966
+ * @return {boolean}
9967
+ */
9968
+ function hasLowerTabOrder(a, b) {
9969
+ // Normalize tabIndexes
9970
+ // e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
9971
+ const ati = Math.max(a.tabIndex, 0);
9972
+ const bti = Math.max(b.tabIndex, 0);
9973
+ return ati === 0 || bti === 0 ? bti > ati : ati > bti;
9974
+ }
9975
+
9976
+ /**
9977
+ * Merge sort iterator, merges the two arrays into one, sorted by tabindex.
9978
+ *
9979
+ * @param {HTMLElement[]} left
9980
+ * @param {HTMLElement[]} right
9981
+ * @return {HTMLElement[]}
9982
+ */
9983
+ function mergeSortByTabIndex(left, right) {
9984
+ const result = [];
9985
+ while (left.length > 0 && right.length > 0) {
9986
+ if (hasLowerTabOrder(left[0], right[0])) {
9987
+ result.push(right.shift());
9988
+ } else {
9989
+ result.push(left.shift());
9990
+ }
9991
+ }
9992
+
9993
+ return result.concat(left, right);
9994
+ }
9995
+
9996
+ /**
9997
+ * Sorts an array of elements by tabindex. Returns a new array.
9998
+ *
9999
+ * @param {HTMLElement[]} elements
10000
+ * @return {HTMLElement[]}
10001
+ */
10002
+ function sortElementsByTabIndex(elements) {
10003
+ // Implement a merge sort as Array.prototype.sort does a non-stable sort
10004
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
10005
+ const len = elements.length;
10006
+ if (len < 2) {
10007
+ return elements;
10008
+ }
10009
+ const pivot = Math.ceil(len / 2);
10010
+ const left = sortElementsByTabIndex(elements.slice(0, pivot));
10011
+ const right = sortElementsByTabIndex(elements.slice(pivot));
10012
+
10013
+ return mergeSortByTabIndex(left, right);
10014
+ }
10015
+
10016
+ /**
10017
+ * Returns true if the element is focusable, otherwise false.
10018
+ *
10019
+ * The list of focusable elements is taken from http://stackoverflow.com/a/1600194/4228703.
10020
+ * However, there isn't a definite list, it's up to the browser.
10021
+ * The only standard we have is DOM Level 2 HTML https://www.w3.org/TR/DOM-Level-2-HTML/html.html,
10022
+ * according to which the only elements that have a `focus()` method are:
10023
+ * - HTMLInputElement
10024
+ * - HTMLSelectElement
10025
+ * - HTMLTextAreaElement
10026
+ * - HTMLAnchorElement
10027
+ *
10028
+ * This notably omits HTMLButtonElement and HTMLAreaElement.
10029
+ * Referring to these tests with tabbables in different browsers
10030
+ * http://allyjs.io/data-tables/focusable.html
10031
+ *
10032
+ * @param {HTMLElement} element
10033
+ * @return {boolean}
10034
+ */
10035
+ function isElementFocusable(element) {
10036
+ // The element cannot be focused if its `tabindex` attribute is set to `-1`.
10037
+ if (element.matches('[tabindex="-1"]')) {
10038
+ return false;
10039
+ }
10040
+
10041
+ // Elements that cannot be focused if they have a `disabled` attribute.
10042
+ if (element.matches('input, select, textarea, button, object')) {
10043
+ return element.matches(':not([disabled])');
10044
+ }
10045
+
10046
+ // Elements that can be focused even if they have a `disabled` attribute.
10047
+ return element.matches('a[href], area[href], iframe, [tabindex], [contentEditable]');
10048
+ }
10049
+
10050
+ /**
10051
+ * Returns true if the element is focused, false otherwise.
10052
+ *
10053
+ * @param {HTMLElement} element
10054
+ * @return {boolean}
10055
+ */
10056
+ function isElementFocused(element) {
10057
+ return element.getRootNode().activeElement === element;
10058
+ }
10059
+
10060
+ /**
10061
+ * Returns the normalized element tabindex. If not focusable, returns -1.
10062
+ * It checks for the attribute "tabindex" instead of the element property
10063
+ * `tabIndex` since browsers assign different values to it.
10064
+ * e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
10065
+ *
10066
+ * @param {HTMLElement} element
10067
+ * @return {number}
10068
+ */
10069
+ function normalizeTabIndex(element) {
10070
+ if (!isElementFocusable(element)) {
10071
+ return -1;
10072
+ }
10073
+
10074
+ const tabIndex = element.getAttribute('tabindex') || 0;
10075
+ return Number(tabIndex);
10076
+ }
10077
+
10078
+ /**
10079
+ * Searches for nodes that are tabbable and adds them to the `result` array.
10080
+ * Returns if the `result` array needs to be sorted by tabindex.
10081
+ *
10082
+ * @param {Node} node The starting point for the search; added to `result` if tabbable.
10083
+ * @param {HTMLElement[]} result
10084
+ * @return {boolean}
10085
+ * @private
10086
+ */
10087
+ function collectFocusableNodes(node, result) {
10088
+ if (node.nodeType !== Node.ELEMENT_NODE || isElementHiddenDirectly(node)) {
10089
+ // Don't traverse children if the node is not an HTML element or not visible.
10090
+ return false;
10091
+ }
10092
+
10093
+ const element = /** @type {HTMLElement} */ (node);
10094
+ const tabIndex = normalizeTabIndex(element);
10095
+ let needsSort = tabIndex > 0;
10096
+ if (tabIndex >= 0) {
10097
+ result.push(element);
10098
+ }
9066
10099
 
9067
- // Preserve tabindex="-1" on the host element
9068
- if (tabindex !== -1) {
9069
- this.tabindex = undefined;
9070
- }
9071
- }
10100
+ let children = [];
10101
+ if (element.localName === 'slot') {
10102
+ children = element.assignedNodes({ flatten: true });
10103
+ } else {
10104
+ // Use shadow root if possible, will check for distributed nodes.
10105
+ children = (element.shadowRoot || element).children;
10106
+ }
10107
+ [...children].forEach((child) => {
10108
+ // Ensure method is always invoked to collect focusable children.
10109
+ needsSort = collectFocusableNodes(child, result) || needsSort;
10110
+ });
10111
+ return needsSort;
10112
+ }
9072
10113
 
9073
- if (this.disabled && tabindex) {
9074
- // If tabindex attribute was changed while component was disabled
9075
- if (tabindex !== -1) {
9076
- this._lastTabIndex = tabindex;
9077
- }
9078
- this.tabindex = undefined;
9079
- }
9080
- }
9081
- },
9082
- );
10114
+ /**
10115
+ * Returns a tab-ordered array of focusable elements for a root element.
10116
+ * The resulting array will include the root element if it is focusable.
10117
+ *
10118
+ * The method traverses nodes in shadow DOM trees too if any.
10119
+ *
10120
+ * @param {HTMLElement} element
10121
+ * @return {HTMLElement[]}
10122
+ */
10123
+ function getFocusableElements(element) {
10124
+ const focusableElements = [];
10125
+ const needsSortByTabIndex = collectFocusableNodes(element, focusableElements);
10126
+ // If there is at least one element with tabindex > 0,
10127
+ // we need to sort the final array by tabindex.
10128
+ if (needsSortByTabIndex) {
10129
+ return sortElementsByTabIndex(focusableElements);
10130
+ }
10131
+ return focusableElements;
10132
+ }
9083
10133
 
9084
10134
  /**
9085
10135
  * @license
@@ -9088,123 +10138,168 @@ const DelegateFocusMixin = dedupingMixin(
9088
10138
  */
9089
10139
 
9090
10140
  /**
9091
- * A mixin to delegate properties and attributes to a target element.
10141
+ * A mixin to handle `focused` and `focus-ring` attributes based on focus.
9092
10142
  *
9093
10143
  * @polymerMixin
9094
10144
  */
9095
- const DelegateStateMixin = dedupingMixin(
10145
+ const FocusMixin = dedupingMixin(
9096
10146
  (superclass) =>
9097
- class DelegateStateMixinClass extends superclass {
9098
- static get properties() {
9099
- return {
9100
- /**
9101
- * A target element to which attributes and properties are delegated.
9102
- * @protected
9103
- */
9104
- stateTarget: {
9105
- type: Object,
9106
- observer: '_stateTargetChanged',
9107
- },
9108
- };
9109
- }
9110
-
9111
- /**
9112
- * An array of the host attributes to delegate to the target element.
9113
- */
9114
- static get delegateAttrs() {
9115
- return [];
9116
- }
9117
-
10147
+ class FocusMixinClass extends superclass {
9118
10148
  /**
9119
- * An array of the host properties to delegate to the target element.
10149
+ * @protected
10150
+ * @return {boolean}
9120
10151
  */
9121
- static get delegateProps() {
9122
- return [];
10152
+ get _keyboardActive() {
10153
+ return isKeyboardActive();
9123
10154
  }
9124
10155
 
9125
10156
  /** @protected */
9126
10157
  ready() {
9127
- super.ready();
10158
+ this.addEventListener('focusin', (e) => {
10159
+ if (this._shouldSetFocus(e)) {
10160
+ this._setFocused(true);
10161
+ }
10162
+ });
9128
10163
 
9129
- this._createDelegateAttrsObserver();
9130
- this._createDelegatePropsObserver();
10164
+ this.addEventListener('focusout', (e) => {
10165
+ if (this._shouldRemoveFocus(e)) {
10166
+ this._setFocused(false);
10167
+ }
10168
+ });
10169
+
10170
+ // In super.ready() other 'focusin' and 'focusout' listeners might be
10171
+ // added, so we call it after our own ones to ensure they execute first.
10172
+ // Issue to watch out: when incorrect, <vaadin-combo-box> refocuses the
10173
+ // input field on iOS after "Done" is pressed.
10174
+ super.ready();
9131
10175
  }
9132
10176
 
9133
10177
  /** @protected */
9134
- _stateTargetChanged(target) {
9135
- if (target) {
9136
- this._ensureAttrsDelegated();
9137
- this._ensurePropsDelegated();
10178
+ disconnectedCallback() {
10179
+ super.disconnectedCallback();
10180
+
10181
+ // In non-Chrome browsers, blur does not fire on the element when it is disconnected.
10182
+ // reproducible in `<vaadin-date-picker>` when closing on `Cancel` or `Today` click.
10183
+ if (this.hasAttribute('focused')) {
10184
+ this._setFocused(false);
9138
10185
  }
9139
10186
  }
9140
10187
 
9141
- /** @protected */
9142
- _createDelegateAttrsObserver() {
9143
- this._createMethodObserver(`_delegateAttrsChanged(${this.constructor.delegateAttrs.join(', ')})`);
9144
- }
10188
+ /**
10189
+ * Override to change how focused and focus-ring attributes are set.
10190
+ *
10191
+ * @param {boolean} focused
10192
+ * @protected
10193
+ */
10194
+ _setFocused(focused) {
10195
+ this.toggleAttribute('focused', focused);
9145
10196
 
9146
- /** @protected */
9147
- _createDelegatePropsObserver() {
9148
- this._createMethodObserver(`_delegatePropsChanged(${this.constructor.delegateProps.join(', ')})`);
10197
+ // Focus-ring is true when the element was focused from the keyboard.
10198
+ // Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI
10199
+ this.toggleAttribute('focus-ring', focused && this._keyboardActive);
9149
10200
  }
9150
10201
 
9151
- /** @protected */
9152
- _ensureAttrsDelegated() {
9153
- this.constructor.delegateAttrs.forEach((name) => {
9154
- this._delegateAttribute(name, this[name]);
9155
- });
10202
+ /**
10203
+ * Override to define if the field receives focus based on the event.
10204
+ *
10205
+ * @param {FocusEvent} _event
10206
+ * @return {boolean}
10207
+ * @protected
10208
+ */
10209
+ _shouldSetFocus(_event) {
10210
+ return true;
9156
10211
  }
9157
10212
 
9158
- /** @protected */
9159
- _ensurePropsDelegated() {
9160
- this.constructor.delegateProps.forEach((name) => {
9161
- this._delegateProperty(name, this[name]);
9162
- });
10213
+ /**
10214
+ * Override to define if the field loses focus based on the event.
10215
+ *
10216
+ * @param {FocusEvent} _event
10217
+ * @return {boolean}
10218
+ * @protected
10219
+ */
10220
+ _shouldRemoveFocus(_event) {
10221
+ return true;
9163
10222
  }
10223
+ },
10224
+ );
9164
10225
 
9165
- /** @protected */
9166
- _delegateAttrsChanged(...values) {
9167
- this.constructor.delegateAttrs.forEach((name, index) => {
9168
- this._delegateAttribute(name, values[index]);
9169
- });
9170
- }
10226
+ /**
10227
+ * @license
10228
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
10229
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
10230
+ */
9171
10231
 
9172
- /** @protected */
9173
- _delegatePropsChanged(...values) {
9174
- this.constructor.delegateProps.forEach((name, index) => {
9175
- this._delegateProperty(name, values[index]);
9176
- });
9177
- }
10232
+ /**
10233
+ * A mixin to toggle the `tabindex` attribute.
10234
+ *
10235
+ * The attribute is set to -1 whenever the user disables the element
10236
+ * and restored with the last known value once the element is enabled.
10237
+ *
10238
+ * @polymerMixin
10239
+ * @mixes DisabledMixin
10240
+ */
10241
+ const TabindexMixin = (superclass) =>
10242
+ class TabindexMixinClass extends DisabledMixin(superclass) {
10243
+ static get properties() {
10244
+ return {
10245
+ /**
10246
+ * Indicates whether the element can be focused and where it participates in sequential keyboard navigation.
10247
+ *
10248
+ * @protected
10249
+ */
10250
+ tabindex: {
10251
+ type: Number,
10252
+ reflectToAttribute: true,
10253
+ observer: '_tabindexChanged',
10254
+ },
9178
10255
 
9179
- /** @protected */
9180
- _delegateAttribute(name, value) {
9181
- if (!this.stateTarget) {
9182
- return;
9183
- }
10256
+ /**
10257
+ * Stores the last known tabindex since the element has been disabled.
10258
+ *
10259
+ * @protected
10260
+ */
10261
+ _lastTabIndex: {
10262
+ type: Number,
10263
+ },
10264
+ };
10265
+ }
9184
10266
 
9185
- if (name === 'invalid') {
9186
- this._delegateAttribute('aria-invalid', value ? 'true' : false);
9187
- }
10267
+ /**
10268
+ * When the element gets disabled, the observer saves the last known tabindex
10269
+ * and makes the element not focusable by setting tabindex to -1.
10270
+ * As soon as the element gets enabled, the observer restores the last known tabindex
10271
+ * so that the element can be focusable again.
10272
+ *
10273
+ * @protected
10274
+ * @override
10275
+ */
10276
+ _disabledChanged(disabled, oldDisabled) {
10277
+ super._disabledChanged(disabled, oldDisabled);
9188
10278
 
9189
- if (typeof value === 'boolean') {
9190
- this.stateTarget.toggleAttribute(name, value);
9191
- } else if (value) {
9192
- this.stateTarget.setAttribute(name, value);
9193
- } else {
9194
- this.stateTarget.removeAttribute(name);
10279
+ if (disabled) {
10280
+ if (this.tabindex !== undefined) {
10281
+ this._lastTabIndex = this.tabindex;
9195
10282
  }
10283
+ this.tabindex = -1;
10284
+ } else if (oldDisabled) {
10285
+ this.tabindex = this._lastTabIndex;
9196
10286
  }
10287
+ }
9197
10288
 
9198
- /** @protected */
9199
- _delegateProperty(name, value) {
9200
- if (!this.stateTarget) {
9201
- return;
9202
- }
9203
-
9204
- this.stateTarget[name] = value;
10289
+ /**
10290
+ * When the user has changed tabindex while the element is disabled,
10291
+ * the observer reverts tabindex to -1 and rather saves the new tabindex value to apply it later.
10292
+ * The new value will be applied as soon as the element becomes enabled.
10293
+ *
10294
+ * @protected
10295
+ */
10296
+ _tabindexChanged(tabindex) {
10297
+ if (this.disabled && tabindex !== -1) {
10298
+ this._lastTabIndex = tabindex;
10299
+ this.tabindex = -1;
9205
10300
  }
9206
- },
9207
- );
10301
+ }
10302
+ };
9208
10303
 
9209
10304
  /**
9210
10305
  * @license
@@ -9213,63 +10308,52 @@ const DelegateStateMixin = dedupingMixin(
9213
10308
  */
9214
10309
 
9215
10310
  /**
9216
- * A mixin to store the reference to an input element
9217
- * and add input and change event listeners to it.
10311
+ * A mixin to forward focus to an element in the light DOM.
9218
10312
  *
9219
10313
  * @polymerMixin
10314
+ * @mixes FocusMixin
10315
+ * @mixes TabindexMixin
9220
10316
  */
9221
- const InputMixin = dedupingMixin(
10317
+ const DelegateFocusMixin = dedupingMixin(
9222
10318
  (superclass) =>
9223
- class InputMixinClass extends superclass {
10319
+ class DelegateFocusMixinClass extends FocusMixin(TabindexMixin(superclass)) {
9224
10320
  static get properties() {
9225
10321
  return {
9226
10322
  /**
9227
- * A reference to the input element controlled by the mixin.
10323
+ * Specify that this control should have input focus when the page loads.
10324
+ */
10325
+ autofocus: {
10326
+ type: Boolean,
10327
+ },
10328
+
10329
+ /**
10330
+ * A reference to the focusable element controlled by the mixin.
10331
+ * It can be an input, textarea, button or any element with tabindex > -1.
10332
+ *
9228
10333
  * Any component implementing this mixin is expected to provide it
9229
- * by using `this._setInputElement(input)` Polymer API.
10334
+ * by using `this._setFocusElement(input)` Polymer API.
9230
10335
  *
9231
- * A typical case is using `InputController` that does this automatically.
9232
- * However, the input element does not have to always be native <input>:
9233
- * as an example, <vaadin-combo-box-light> accepts other components.
10336
+ * Toggling `tabindex` attribute on the host element propagates its value to `focusElement`.
9234
10337
  *
9235
10338
  * @protected
9236
10339
  * @type {!HTMLElement}
9237
10340
  */
9238
- inputElement: {
10341
+ focusElement: {
9239
10342
  type: Object,
9240
10343
  readOnly: true,
9241
- observer: '_inputElementChanged',
9242
- },
9243
-
9244
- /**
9245
- * String used to define input type.
9246
- * @protected
9247
- */
9248
- type: {
9249
- type: String,
9250
- readOnly: true,
9251
- },
9252
-
9253
- /**
9254
- * The value of the field.
9255
- */
9256
- value: {
9257
- type: String,
9258
- value: '',
9259
- observer: '_valueChanged',
9260
- notify: true,
9261
- sync: true,
10344
+ observer: '_focusElementChanged',
9262
10345
  },
9263
10346
 
9264
10347
  /**
9265
- * Whether the input element has a non-empty value.
10348
+ * Override the property from `TabIndexMixin`
10349
+ * to ensure the `tabindex` attribute of the focus element
10350
+ * will be restored to `0` after re-enabling the element.
9266
10351
  *
9267
10352
  * @protected
10353
+ * @override
9268
10354
  */
9269
- _hasInputValue: {
9270
- type: Boolean,
9271
- value: false,
9272
- observer: '_hasInputValueChanged',
10355
+ _lastTabIndex: {
10356
+ value: 0,
9273
10357
  },
9274
10358
  };
9275
10359
  }
@@ -9277,212 +10361,173 @@ const InputMixin = dedupingMixin(
9277
10361
  constructor() {
9278
10362
  super();
9279
10363
 
9280
- this._boundOnInput = this.__onInput.bind(this);
9281
- this._boundOnChange = this._onChange.bind(this);
10364
+ this._boundOnBlur = this._onBlur.bind(this);
10365
+ this._boundOnFocus = this._onFocus.bind(this);
9282
10366
  }
9283
10367
 
9284
- /**
9285
- * Indicates whether the value is different from the default one.
9286
- * Override if the `value` property has a type other than `string`.
9287
- *
9288
- * @protected
9289
- */
9290
- get _hasValue() {
9291
- return this.value != null && this.value !== '';
10368
+ /** @protected */
10369
+ ready() {
10370
+ super.ready();
10371
+
10372
+ if (this.autofocus && !this.disabled) {
10373
+ requestAnimationFrame(() => {
10374
+ this.focus();
10375
+ this.setAttribute('focus-ring', '');
10376
+ });
10377
+ }
9292
10378
  }
9293
10379
 
9294
10380
  /**
9295
- * A property for accessing the input element's value.
9296
- *
9297
- * Override this getter if the property is different from the default `value` one.
9298
- *
9299
10381
  * @protected
9300
- * @return {string}
10382
+ * @override
9301
10383
  */
9302
- get _inputElementValueProperty() {
9303
- return 'value';
10384
+ focus() {
10385
+ if (this.focusElement && !this.disabled) {
10386
+ this.focusElement.focus();
10387
+ }
9304
10388
  }
9305
10389
 
9306
10390
  /**
9307
- * The input element's value.
9308
- *
9309
10391
  * @protected
9310
- * @return {string}
10392
+ * @override
9311
10393
  */
9312
- get _inputElementValue() {
9313
- return this.inputElement ? this.inputElement[this._inputElementValueProperty] : undefined;
10394
+ blur() {
10395
+ if (this.focusElement) {
10396
+ this.focusElement.blur();
10397
+ }
9314
10398
  }
9315
10399
 
9316
10400
  /**
9317
- * The input element's value.
9318
- *
9319
10401
  * @protected
10402
+ * @override
9320
10403
  */
9321
- set _inputElementValue(value) {
9322
- if (this.inputElement) {
9323
- this.inputElement[this._inputElementValueProperty] = value;
10404
+ click() {
10405
+ if (this.focusElement && !this.disabled) {
10406
+ this.focusElement.click();
9324
10407
  }
9325
10408
  }
9326
10409
 
9327
- /**
9328
- * Clear the value of the field.
9329
- */
9330
- clear() {
9331
- this._hasInputValue = false;
9332
-
9333
- this.value = '';
9334
-
9335
- // Clear the input immediately without waiting for the observer.
9336
- // Otherwise, when using Lit, the old value would be restored.
9337
- this._inputElementValue = '';
10410
+ /** @protected */
10411
+ _focusElementChanged(element, oldElement) {
10412
+ if (element) {
10413
+ element.disabled = this.disabled;
10414
+ this._addFocusListeners(element);
10415
+ this.__forwardTabIndex(this.tabindex);
10416
+ } else if (oldElement) {
10417
+ this._removeFocusListeners(oldElement);
10418
+ }
9338
10419
  }
9339
10420
 
9340
10421
  /**
9341
- * Add event listeners to the input element instance.
9342
- * Override this method to add custom listeners.
9343
- * @param {!HTMLElement} input
10422
+ * @param {HTMLElement} element
9344
10423
  * @protected
9345
10424
  */
9346
- _addInputListeners(input) {
9347
- input.addEventListener('input', this._boundOnInput);
9348
- input.addEventListener('change', this._boundOnChange);
10425
+ _addFocusListeners(element) {
10426
+ element.addEventListener('blur', this._boundOnBlur);
10427
+ element.addEventListener('focus', this._boundOnFocus);
9349
10428
  }
9350
10429
 
9351
10430
  /**
9352
- * Remove event listeners from the input element instance.
9353
- * @param {!HTMLElement} input
10431
+ * @param {HTMLElement} element
9354
10432
  * @protected
9355
10433
  */
9356
- _removeInputListeners(input) {
9357
- input.removeEventListener('input', this._boundOnInput);
9358
- input.removeEventListener('change', this._boundOnChange);
10434
+ _removeFocusListeners(element) {
10435
+ element.removeEventListener('blur', this._boundOnBlur);
10436
+ element.removeEventListener('focus', this._boundOnFocus);
9359
10437
  }
9360
10438
 
9361
10439
  /**
9362
- * A method to forward the value property set on the field
9363
- * programmatically back to the input element value.
9364
- * Override this method to perform additional checks,
9365
- * for example to skip this in certain conditions.
9366
- * @param {string} value
10440
+ * Focus event does not bubble, so we dispatch it manually
10441
+ * on the host element to support adding focus listeners
10442
+ * when the focusable element is placed in light DOM.
10443
+ * @param {FocusEvent} event
9367
10444
  * @protected
9368
10445
  */
9369
- _forwardInputValue(value) {
9370
- // Value might be set before an input element is initialized.
9371
- // This case should be handled separately by a component that
9372
- // implements this mixin, for example in `connectedCallback`.
9373
- if (!this.inputElement) {
9374
- return;
9375
- }
9376
-
9377
- this._inputElementValue = value != null ? value : '';
10446
+ _onFocus(event) {
10447
+ event.stopPropagation();
10448
+ this.dispatchEvent(new Event('focus'));
9378
10449
  }
9379
10450
 
9380
10451
  /**
9381
- * @param {HTMLElement | undefined} input
9382
- * @param {HTMLElement | undefined} oldInput
10452
+ * Blur event does not bubble, so we dispatch it manually
10453
+ * on the host element to support adding blur listeners
10454
+ * when the focusable element is placed in light DOM.
10455
+ * @param {FocusEvent} event
9383
10456
  * @protected
9384
10457
  */
9385
- _inputElementChanged(input, oldInput) {
9386
- if (input) {
9387
- this._addInputListeners(input);
9388
- } else if (oldInput) {
9389
- this._removeInputListeners(oldInput);
9390
- }
9391
- }
9392
-
9393
- /**
9394
- * Observer to notify about the change of private property.
9395
- *
9396
- * @private
9397
- */
9398
- _hasInputValueChanged(hasValue, oldHasValue) {
9399
- if (hasValue || oldHasValue) {
9400
- this.dispatchEvent(new CustomEvent('has-input-value-changed'));
9401
- }
9402
- }
9403
-
9404
- /**
9405
- * An input event listener used to update `_hasInputValue` property.
9406
- * Do not override this method.
9407
- *
9408
- * @param {Event} event
9409
- * @private
9410
- */
9411
- __onInput(event) {
9412
- this._setHasInputValue(event);
9413
- this._onInput(event);
10458
+ _onBlur(event) {
10459
+ event.stopPropagation();
10460
+ this.dispatchEvent(new Event('blur'));
9414
10461
  }
9415
10462
 
9416
10463
  /**
9417
- * An input event listener used to update the field value.
9418
- *
9419
- * @param {Event} event
10464
+ * @param {FocusEvent} event
10465
+ * @return {boolean}
9420
10466
  * @protected
10467
+ * @override
9421
10468
  */
9422
- _onInput(event) {
9423
- // In the case a custom web component is passed as `inputElement`,
9424
- // the actual native input element, on which the event occurred,
9425
- // can be inside shadow trees.
9426
- const target = event.composedPath()[0];
9427
- // Ignore fake input events e.g. used by clear button.
9428
- this.__userInput = event.isTrusted;
9429
- this.value = target.value;
9430
- this.__userInput = false;
10469
+ _shouldSetFocus(event) {
10470
+ return event.target === this.focusElement;
9431
10471
  }
9432
10472
 
9433
10473
  /**
9434
- * A change event listener.
9435
- * Override this method with an actual implementation.
9436
- * @param {Event} _event
9437
- * @protected
9438
- */
9439
- _onChange(_event) {}
9440
-
9441
- /**
9442
- * Toggle the has-value attribute based on the value property.
9443
- *
9444
- * @param {boolean} hasValue
10474
+ * @param {FocusEvent} event
10475
+ * @return {boolean}
9445
10476
  * @protected
10477
+ * @override
9446
10478
  */
9447
- _toggleHasValue(hasValue) {
9448
- this.toggleAttribute('has-value', hasValue);
10479
+ _shouldRemoveFocus(event) {
10480
+ return event.target === this.focusElement;
9449
10481
  }
9450
10482
 
9451
10483
  /**
9452
- * Observer called when a value property changes.
9453
- * @param {string | undefined} newVal
9454
- * @param {string | undefined} oldVal
10484
+ * @param {boolean} disabled
10485
+ * @param {boolean} oldDisabled
9455
10486
  * @protected
10487
+ * @override
9456
10488
  */
9457
- _valueChanged(newVal, oldVal) {
9458
- this._toggleHasValue(this._hasValue);
10489
+ _disabledChanged(disabled, oldDisabled) {
10490
+ super._disabledChanged(disabled, oldDisabled);
9459
10491
 
9460
- // Setting initial value to empty string, do nothing.
9461
- if (newVal === '' && oldVal === undefined) {
9462
- return;
10492
+ if (this.focusElement) {
10493
+ this.focusElement.disabled = disabled;
9463
10494
  }
9464
10495
 
9465
- // Value is set by the user, no need to sync it back to input.
9466
- if (this.__userInput) {
9467
- return;
10496
+ if (disabled) {
10497
+ this.blur();
9468
10498
  }
9469
-
9470
- // Setting a value programmatically, sync it to input element.
9471
- this._forwardInputValue(newVal);
9472
10499
  }
9473
10500
 
9474
10501
  /**
9475
- * Sets the `_hasInputValue` property based on the `input` event.
9476
- *
9477
- * @param {InputEvent} event
10502
+ * Override an observer from `TabindexMixin`.
10503
+ * Do not call super to remove tabindex attribute
10504
+ * from the host after it has been forwarded.
10505
+ * @param {string} tabindex
9478
10506
  * @protected
10507
+ * @override
9479
10508
  */
9480
- _setHasInputValue(event) {
9481
- // In the case a custom web component is passed as `inputElement`,
9482
- // the actual native input element, on which the event occurred,
9483
- // can be inside shadow trees.
9484
- const target = event.composedPath()[0];
9485
- this._hasInputValue = target.value.length > 0;
10509
+ _tabindexChanged(tabindex) {
10510
+ this.__forwardTabIndex(tabindex);
10511
+ }
10512
+
10513
+ /** @private */
10514
+ __forwardTabIndex(tabindex) {
10515
+ if (tabindex !== undefined && this.focusElement) {
10516
+ this.focusElement.tabIndex = tabindex;
10517
+
10518
+ // Preserve tabindex="-1" on the host element
10519
+ if (tabindex !== -1) {
10520
+ this.tabindex = undefined;
10521
+ }
10522
+ }
10523
+
10524
+ if (this.disabled && tabindex) {
10525
+ // If tabindex attribute was changed while component was disabled
10526
+ if (tabindex !== -1) {
10527
+ this._lastTabIndex = tabindex;
10528
+ }
10529
+ this.tabindex = undefined;
10530
+ }
9486
10531
  }
9487
10532
  },
9488
10533
  );
@@ -9494,138 +10539,123 @@ const InputMixin = dedupingMixin(
9494
10539
  */
9495
10540
 
9496
10541
  /**
9497
- * Returns true if the given node is an empty text node, false otherwise.
10542
+ * A mixin to delegate properties and attributes to a target element.
9498
10543
  *
9499
- * @param {Node} node
9500
- * @return {boolean}
9501
- */
9502
- function isEmptyTextNode(node) {
9503
- return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '';
9504
- }
9505
-
9506
- /**
9507
- * @license
9508
- * Copyright (c) 2023 Vaadin Ltd.
9509
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9510
- */
9511
-
9512
- /**
9513
- * A helper for observing slot changes.
10544
+ * @polymerMixin
9514
10545
  */
9515
- class SlotObserver {
9516
- constructor(slot, callback) {
9517
- /** @type HTMLSlotElement */
9518
- this.slot = slot;
9519
-
9520
- /** @type Function */
9521
- this.callback = callback;
9522
-
9523
- /** @type {Node[]} */
9524
- this._storedNodes = [];
9525
-
9526
- this._connected = false;
9527
- this._scheduled = false;
9528
-
9529
- this._boundSchedule = () => {
9530
- this._schedule();
9531
- };
10546
+ const DelegateStateMixin = dedupingMixin(
10547
+ (superclass) =>
10548
+ class DelegateStateMixinClass extends superclass {
10549
+ static get properties() {
10550
+ return {
10551
+ /**
10552
+ * A target element to which attributes and properties are delegated.
10553
+ * @protected
10554
+ */
10555
+ stateTarget: {
10556
+ type: Object,
10557
+ observer: '_stateTargetChanged',
10558
+ },
10559
+ };
10560
+ }
9532
10561
 
9533
- this.connect();
9534
- this._schedule();
9535
- }
10562
+ /**
10563
+ * An array of the host attributes to delegate to the target element.
10564
+ */
10565
+ static get delegateAttrs() {
10566
+ return [];
10567
+ }
9536
10568
 
9537
- /**
9538
- * Activates an observer. This method is automatically called when
9539
- * a `SlotObserver` is created. It should only be called to re-activate
9540
- * an observer that has been deactivated via the `disconnect` method.
9541
- */
9542
- connect() {
9543
- this.slot.addEventListener('slotchange', this._boundSchedule);
9544
- this._connected = true;
9545
- }
10569
+ /**
10570
+ * An array of the host properties to delegate to the target element.
10571
+ */
10572
+ static get delegateProps() {
10573
+ return [];
10574
+ }
9546
10575
 
9547
- /**
9548
- * Deactivates the observer. After calling this method the observer callback
9549
- * will not be called when changes to slotted nodes occur. The `connect` method
9550
- * may be subsequently called to reactivate the observer.
9551
- */
9552
- disconnect() {
9553
- this.slot.removeEventListener('slotchange', this._boundSchedule);
9554
- this._connected = false;
9555
- }
10576
+ /** @protected */
10577
+ ready() {
10578
+ super.ready();
9556
10579
 
9557
- /** @private */
9558
- _schedule() {
9559
- if (!this._scheduled) {
9560
- this._scheduled = true;
10580
+ this._createDelegateAttrsObserver();
10581
+ this._createDelegatePropsObserver();
10582
+ }
9561
10583
 
9562
- queueMicrotask(() => {
9563
- this.flush();
9564
- });
9565
- }
9566
- }
10584
+ /** @protected */
10585
+ _stateTargetChanged(target) {
10586
+ if (target) {
10587
+ this._ensureAttrsDelegated();
10588
+ this._ensurePropsDelegated();
10589
+ }
10590
+ }
9567
10591
 
9568
- /**
9569
- * Run the observer callback synchronously.
9570
- */
9571
- flush() {
9572
- if (!this._connected) {
9573
- return;
9574
- }
10592
+ /** @protected */
10593
+ _createDelegateAttrsObserver() {
10594
+ this._createMethodObserver(`_delegateAttrsChanged(${this.constructor.delegateAttrs.join(', ')})`);
10595
+ }
9575
10596
 
9576
- this._scheduled = false;
10597
+ /** @protected */
10598
+ _createDelegatePropsObserver() {
10599
+ this._createMethodObserver(`_delegatePropsChanged(${this.constructor.delegateProps.join(', ')})`);
10600
+ }
9577
10601
 
9578
- this._processNodes();
9579
- }
10602
+ /** @protected */
10603
+ _ensureAttrsDelegated() {
10604
+ this.constructor.delegateAttrs.forEach((name) => {
10605
+ this._delegateAttribute(name, this[name]);
10606
+ });
10607
+ }
9580
10608
 
9581
- /** @private */
9582
- _processNodes() {
9583
- const currentNodes = this.slot.assignedNodes({ flatten: true });
10609
+ /** @protected */
10610
+ _ensurePropsDelegated() {
10611
+ this.constructor.delegateProps.forEach((name) => {
10612
+ this._delegateProperty(name, this[name]);
10613
+ });
10614
+ }
9584
10615
 
9585
- let addedNodes = [];
9586
- const removedNodes = [];
9587
- const movedNodes = [];
10616
+ /** @protected */
10617
+ _delegateAttrsChanged(...values) {
10618
+ this.constructor.delegateAttrs.forEach((name, index) => {
10619
+ this._delegateAttribute(name, values[index]);
10620
+ });
10621
+ }
9588
10622
 
9589
- if (currentNodes.length) {
9590
- addedNodes = currentNodes.filter((node) => !this._storedNodes.includes(node));
9591
- }
10623
+ /** @protected */
10624
+ _delegatePropsChanged(...values) {
10625
+ this.constructor.delegateProps.forEach((name, index) => {
10626
+ this._delegateProperty(name, values[index]);
10627
+ });
10628
+ }
9592
10629
 
9593
- if (this._storedNodes.length) {
9594
- this._storedNodes.forEach((node, index) => {
9595
- const idx = currentNodes.indexOf(node);
9596
- if (idx === -1) {
9597
- removedNodes.push(node);
9598
- } else if (idx !== index) {
9599
- movedNodes.push(node);
10630
+ /** @protected */
10631
+ _delegateAttribute(name, value) {
10632
+ if (!this.stateTarget) {
10633
+ return;
9600
10634
  }
9601
- });
9602
- }
9603
-
9604
- if (addedNodes.length || removedNodes.length || movedNodes.length) {
9605
- this.callback({ addedNodes, movedNodes, removedNodes });
9606
- }
9607
10635
 
9608
- this._storedNodes = currentNodes;
9609
- }
9610
- }
10636
+ if (name === 'invalid') {
10637
+ this._delegateAttribute('aria-invalid', value ? 'true' : false);
10638
+ }
9611
10639
 
9612
- /**
9613
- * @license
9614
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
9615
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
9616
- */
10640
+ if (typeof value === 'boolean') {
10641
+ this.stateTarget.toggleAttribute(name, value);
10642
+ } else if (value) {
10643
+ this.stateTarget.setAttribute(name, value);
10644
+ } else {
10645
+ this.stateTarget.removeAttribute(name);
10646
+ }
10647
+ }
9617
10648
 
9618
- let uniqueId = 0;
10649
+ /** @protected */
10650
+ _delegateProperty(name, value) {
10651
+ if (!this.stateTarget) {
10652
+ return;
10653
+ }
9619
10654
 
9620
- /**
9621
- * Returns a unique integer id.
9622
- *
9623
- * @return {number}
9624
- */
9625
- function generateUniqueId() {
9626
- // eslint-disable-next-line no-plusplus
9627
- return uniqueId++;
9628
- }
10655
+ this.stateTarget[name] = value;
10656
+ }
10657
+ },
10658
+ );
9629
10659
 
9630
10660
  /**
9631
10661
  * @license
@@ -9634,234 +10664,279 @@ function generateUniqueId() {
9634
10664
  */
9635
10665
 
9636
10666
  /**
9637
- * A controller for providing content to slot element and observing changes.
10667
+ * A mixin to store the reference to an input element
10668
+ * and add input and change event listeners to it.
10669
+ *
10670
+ * @polymerMixin
9638
10671
  */
9639
- class SlotController extends EventTarget {
9640
- /**
9641
- * Ensure that every instance has unique ID.
9642
- *
9643
- * @param {HTMLElement} host
9644
- * @param {string} slotName
9645
- * @return {string}
9646
- * @protected
9647
- */
9648
- static generateId(host, slotName) {
9649
- const prefix = slotName || 'default';
9650
- return `${prefix}-${host.localName}-${generateUniqueId()}`;
9651
- }
9652
-
9653
- constructor(host, slotName, tagName, config = {}) {
9654
- super();
10672
+ const InputMixin = dedupingMixin(
10673
+ (superclass) =>
10674
+ class InputMixinClass extends superclass {
10675
+ static get properties() {
10676
+ return {
10677
+ /**
10678
+ * A reference to the input element controlled by the mixin.
10679
+ * Any component implementing this mixin is expected to provide it
10680
+ * by using `this._setInputElement(input)` Polymer API.
10681
+ *
10682
+ * A typical case is using `InputController` that does this automatically.
10683
+ * However, the input element does not have to always be native <input>:
10684
+ * as an example, <vaadin-combo-box-light> accepts other components.
10685
+ *
10686
+ * @protected
10687
+ * @type {!HTMLElement}
10688
+ */
10689
+ inputElement: {
10690
+ type: Object,
10691
+ readOnly: true,
10692
+ observer: '_inputElementChanged',
10693
+ },
9655
10694
 
9656
- const { initializer, multiple, observe, useUniqueId } = config;
10695
+ /**
10696
+ * String used to define input type.
10697
+ * @protected
10698
+ */
10699
+ type: {
10700
+ type: String,
10701
+ readOnly: true,
10702
+ },
9657
10703
 
9658
- this.host = host;
9659
- this.slotName = slotName;
9660
- this.tagName = tagName;
9661
- this.observe = typeof observe === 'boolean' ? observe : true;
9662
- this.multiple = typeof multiple === 'boolean' ? multiple : false;
9663
- this.slotInitializer = initializer;
10704
+ /**
10705
+ * The value of the field.
10706
+ */
10707
+ value: {
10708
+ type: String,
10709
+ value: '',
10710
+ observer: '_valueChanged',
10711
+ notify: true,
10712
+ sync: true,
10713
+ },
9664
10714
 
9665
- if (multiple) {
9666
- this.nodes = [];
9667
- }
10715
+ /**
10716
+ * Whether the input element has a non-empty value.
10717
+ *
10718
+ * @protected
10719
+ */
10720
+ _hasInputValue: {
10721
+ type: Boolean,
10722
+ value: false,
10723
+ observer: '_hasInputValueChanged',
10724
+ },
10725
+ };
10726
+ }
9668
10727
 
9669
- // Only generate the default ID if requested by the controller.
9670
- if (useUniqueId) {
9671
- this.defaultId = this.constructor.generateId(host, slotName);
9672
- }
9673
- }
10728
+ constructor() {
10729
+ super();
9674
10730
 
9675
- hostConnected() {
9676
- if (!this.initialized) {
9677
- if (this.multiple) {
9678
- this.initMultiple();
9679
- } else {
9680
- this.initSingle();
10731
+ this._boundOnInput = this.__onInput.bind(this);
10732
+ this._boundOnChange = this._onChange.bind(this);
9681
10733
  }
9682
10734
 
9683
- if (this.observe) {
9684
- this.observeSlot();
10735
+ /**
10736
+ * Indicates whether the value is different from the default one.
10737
+ * Override if the `value` property has a type other than `string`.
10738
+ *
10739
+ * @protected
10740
+ */
10741
+ get _hasValue() {
10742
+ return this.value != null && this.value !== '';
9685
10743
  }
9686
10744
 
9687
- this.initialized = true;
9688
- }
9689
- }
9690
-
9691
- /** @protected */
9692
- initSingle() {
9693
- let node = this.getSlotChild();
9694
-
9695
- if (!node) {
9696
- node = this.attachDefaultNode();
9697
- this.initNode(node);
9698
- } else {
9699
- this.node = node;
9700
- this.initAddedNode(node);
9701
- }
9702
- }
10745
+ /**
10746
+ * A property for accessing the input element's value.
10747
+ *
10748
+ * Override this getter if the property is different from the default `value` one.
10749
+ *
10750
+ * @protected
10751
+ * @return {string}
10752
+ */
10753
+ get _inputElementValueProperty() {
10754
+ return 'value';
10755
+ }
9703
10756
 
9704
- /** @protected */
9705
- initMultiple() {
9706
- const children = this.getSlotChildren();
10757
+ /**
10758
+ * The input element's value.
10759
+ *
10760
+ * @protected
10761
+ * @return {string}
10762
+ */
10763
+ get _inputElementValue() {
10764
+ return this.inputElement ? this.inputElement[this._inputElementValueProperty] : undefined;
10765
+ }
9707
10766
 
9708
- if (children.length === 0) {
9709
- const defaultNode = this.attachDefaultNode();
9710
- if (defaultNode) {
9711
- this.nodes = [defaultNode];
9712
- this.initNode(defaultNode);
10767
+ /**
10768
+ * The input element's value.
10769
+ *
10770
+ * @protected
10771
+ */
10772
+ set _inputElementValue(value) {
10773
+ if (this.inputElement) {
10774
+ this.inputElement[this._inputElementValueProperty] = value;
10775
+ }
9713
10776
  }
9714
- } else {
9715
- this.nodes = children;
9716
- children.forEach((node) => {
9717
- this.initAddedNode(node);
9718
- });
9719
- }
9720
- }
9721
10777
 
9722
- /**
9723
- * Create and attach default node using the provided tag name, if any.
9724
- * @return {Node | undefined}
9725
- * @protected
9726
- */
9727
- attachDefaultNode() {
9728
- const { host, slotName, tagName } = this;
10778
+ /**
10779
+ * Clear the value of the field.
10780
+ */
10781
+ clear() {
10782
+ this._hasInputValue = false;
9729
10783
 
9730
- // Check if the node was created previously and if so, reuse it.
9731
- let node = this.defaultNode;
10784
+ this.value = '';
9732
10785
 
9733
- // Tag name is optional, sometimes we don't init default content.
9734
- if (!node && tagName) {
9735
- node = document.createElement(tagName);
9736
- if (node instanceof Element) {
9737
- if (slotName !== '') {
9738
- node.setAttribute('slot', slotName);
9739
- }
9740
- this.node = node;
9741
- this.defaultNode = node;
10786
+ // Clear the input immediately without waiting for the observer.
10787
+ // Otherwise, when using Lit, the old value would be restored.
10788
+ this._inputElementValue = '';
9742
10789
  }
9743
- }
9744
10790
 
9745
- if (node) {
9746
- host.appendChild(node);
9747
- }
10791
+ /**
10792
+ * Add event listeners to the input element instance.
10793
+ * Override this method to add custom listeners.
10794
+ * @param {!HTMLElement} input
10795
+ * @protected
10796
+ */
10797
+ _addInputListeners(input) {
10798
+ input.addEventListener('input', this._boundOnInput);
10799
+ input.addEventListener('change', this._boundOnChange);
10800
+ }
9748
10801
 
9749
- return node;
9750
- }
10802
+ /**
10803
+ * Remove event listeners from the input element instance.
10804
+ * @param {!HTMLElement} input
10805
+ * @protected
10806
+ */
10807
+ _removeInputListeners(input) {
10808
+ input.removeEventListener('input', this._boundOnInput);
10809
+ input.removeEventListener('change', this._boundOnChange);
10810
+ }
9751
10811
 
9752
- /**
9753
- * Return the list of nodes matching the slot managed by the controller.
9754
- * @return {Node}
9755
- */
9756
- getSlotChildren() {
9757
- const { slotName } = this;
9758
- return Array.from(this.host.childNodes).filter((node) => {
9759
- // Either an element (any slot) or a text node (only un-named slot).
9760
- return (
9761
- (node.nodeType === Node.ELEMENT_NODE && node.slot === slotName) ||
9762
- (node.nodeType === Node.TEXT_NODE && node.textContent.trim() && slotName === '')
9763
- );
9764
- });
9765
- }
10812
+ /**
10813
+ * A method to forward the value property set on the field
10814
+ * programmatically back to the input element value.
10815
+ * Override this method to perform additional checks,
10816
+ * for example to skip this in certain conditions.
10817
+ * @param {string} value
10818
+ * @protected
10819
+ */
10820
+ _forwardInputValue(value) {
10821
+ // Value might be set before an input element is initialized.
10822
+ // This case should be handled separately by a component that
10823
+ // implements this mixin, for example in `connectedCallback`.
10824
+ if (!this.inputElement) {
10825
+ return;
10826
+ }
9766
10827
 
9767
- /**
9768
- * Return a reference to the node managed by the controller.
9769
- * @return {Node}
9770
- */
9771
- getSlotChild() {
9772
- return this.getSlotChildren()[0];
9773
- }
10828
+ this._inputElementValue = value != null ? value : '';
10829
+ }
9774
10830
 
9775
- /**
9776
- * Run `slotInitializer` for the node managed by the controller.
9777
- *
9778
- * @param {Node} node
9779
- * @protected
9780
- */
9781
- initNode(node) {
9782
- const { slotInitializer } = this;
9783
- // Don't try to bind `this` to initializer (normally it's arrow function).
9784
- // Instead, pass the host as a first argument to access component's state.
9785
- if (slotInitializer) {
9786
- slotInitializer(node, this.host);
9787
- }
9788
- }
10831
+ /**
10832
+ * @param {HTMLElement | undefined} input
10833
+ * @param {HTMLElement | undefined} oldInput
10834
+ * @protected
10835
+ */
10836
+ _inputElementChanged(input, oldInput) {
10837
+ if (input) {
10838
+ this._addInputListeners(input);
10839
+ } else if (oldInput) {
10840
+ this._removeInputListeners(oldInput);
10841
+ }
10842
+ }
9789
10843
 
9790
- /**
9791
- * Override to initialize the newly added custom node.
9792
- *
9793
- * @param {Node} _node
9794
- * @protected
9795
- */
9796
- initCustomNode(_node) {}
10844
+ /**
10845
+ * Observer to notify about the change of private property.
10846
+ *
10847
+ * @private
10848
+ */
10849
+ _hasInputValueChanged(hasValue, oldHasValue) {
10850
+ if (hasValue || oldHasValue) {
10851
+ this.dispatchEvent(new CustomEvent('has-input-value-changed'));
10852
+ }
10853
+ }
9797
10854
 
9798
- /**
9799
- * Override to teardown slotted node when it's removed.
9800
- *
9801
- * @param {Node} _node
9802
- * @protected
9803
- */
9804
- teardownNode(_node) {}
10855
+ /**
10856
+ * An input event listener used to update `_hasInputValue` property.
10857
+ * Do not override this method.
10858
+ *
10859
+ * @param {Event} event
10860
+ * @private
10861
+ */
10862
+ __onInput(event) {
10863
+ this._setHasInputValue(event);
10864
+ this._onInput(event);
10865
+ }
9805
10866
 
9806
- /**
9807
- * Run both `initCustomNode` and `initNode` for a custom slotted node.
9808
- *
9809
- * @param {Node} node
9810
- * @protected
9811
- */
9812
- initAddedNode(node) {
9813
- if (node !== this.defaultNode) {
9814
- this.initCustomNode(node);
9815
- this.initNode(node);
9816
- }
9817
- }
10867
+ /**
10868
+ * An input event listener used to update the field value.
10869
+ *
10870
+ * @param {Event} event
10871
+ * @protected
10872
+ */
10873
+ _onInput(event) {
10874
+ // In the case a custom web component is passed as `inputElement`,
10875
+ // the actual native input element, on which the event occurred,
10876
+ // can be inside shadow trees.
10877
+ const target = event.composedPath()[0];
10878
+ // Ignore fake input events e.g. used by clear button.
10879
+ this.__userInput = event.isTrusted;
10880
+ this.value = target.value;
10881
+ this.__userInput = false;
10882
+ }
9818
10883
 
9819
- /**
9820
- * Setup the observer to manage slot content changes.
9821
- * @protected
9822
- */
9823
- observeSlot() {
9824
- const { slotName } = this;
9825
- const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
9826
- const slot = this.host.shadowRoot.querySelector(selector);
10884
+ /**
10885
+ * A change event listener.
10886
+ * Override this method with an actual implementation.
10887
+ * @param {Event} _event
10888
+ * @protected
10889
+ */
10890
+ _onChange(_event) {}
9827
10891
 
9828
- this.__slotObserver = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
9829
- const current = this.multiple ? this.nodes : [this.node];
10892
+ /**
10893
+ * Toggle the has-value attribute based on the value property.
10894
+ *
10895
+ * @param {boolean} hasValue
10896
+ * @protected
10897
+ */
10898
+ _toggleHasValue(hasValue) {
10899
+ this.toggleAttribute('has-value', hasValue);
10900
+ }
9830
10901
 
9831
- // Calling `slot.assignedNodes()` includes whitespace text nodes in case of default slot:
9832
- // unlike comment nodes, they are not filtered out. So we need to manually ignore them.
9833
- const newNodes = addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
10902
+ /**
10903
+ * Observer called when a value property changes.
10904
+ * @param {string | undefined} newVal
10905
+ * @param {string | undefined} oldVal
10906
+ * @protected
10907
+ */
10908
+ _valueChanged(newVal, oldVal) {
10909
+ this._toggleHasValue(this._hasValue);
9834
10910
 
9835
- if (removedNodes.length) {
9836
- this.nodes = current.filter((node) => !removedNodes.includes(node));
10911
+ // Setting initial value to empty string, do nothing.
10912
+ if (newVal === '' && oldVal === undefined) {
10913
+ return;
10914
+ }
9837
10915
 
9838
- removedNodes.forEach((node) => {
9839
- this.teardownNode(node);
9840
- });
10916
+ // Value is set by the user, no need to sync it back to input.
10917
+ if (this.__userInput) {
10918
+ return;
10919
+ }
10920
+
10921
+ // Setting a value programmatically, sync it to input element.
10922
+ this._forwardInputValue(newVal);
9841
10923
  }
9842
10924
 
9843
- if (newNodes && newNodes.length > 0) {
9844
- if (this.multiple) {
9845
- // Remove default node if exists
9846
- if (this.defaultNode) {
9847
- this.defaultNode.remove();
9848
- }
9849
- this.nodes = [...current, ...newNodes].filter((node) => node !== this.defaultNode);
9850
- newNodes.forEach((node) => {
9851
- this.initAddedNode(node);
9852
- });
9853
- } else {
9854
- // Remove previous node if exists
9855
- if (this.node) {
9856
- this.node.remove();
9857
- }
9858
- this.node = newNodes[0];
9859
- this.initAddedNode(this.node);
9860
- }
10925
+ /**
10926
+ * Sets the `_hasInputValue` property based on the `input` event.
10927
+ *
10928
+ * @param {InputEvent} event
10929
+ * @protected
10930
+ */
10931
+ _setHasInputValue(event) {
10932
+ // In the case a custom web component is passed as `inputElement`,
10933
+ // the actual native input element, on which the event occurred,
10934
+ // can be inside shadow trees.
10935
+ const target = event.composedPath()[0];
10936
+ this._hasInputValue = target.value.length > 0;
9861
10937
  }
9862
- });
9863
- }
9864
- }
10938
+ },
10939
+ );
9865
10940
 
9866
10941
  /**
9867
10942
  * @license
@@ -10518,67 +11593,6 @@ const requiredField = i$1`
10518
11593
 
10519
11594
  registerStyles('', requiredField, { moduleId: 'lumo-required-field' });
10520
11595
 
10521
- /**
10522
- * @license
10523
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
10524
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
10525
- */
10526
-
10527
- /**
10528
- * Takes a string with values separated by space and returns a set the values
10529
- *
10530
- * @param {string} value
10531
- * @return {Set<string>}
10532
- */
10533
- function deserializeAttributeValue(value) {
10534
- if (!value) {
10535
- return new Set();
10536
- }
10537
-
10538
- return new Set(value.split(' '));
10539
- }
10540
-
10541
- /**
10542
- * Takes a set of string values and returns a string with values separated by space
10543
- *
10544
- * @param {Set<string>} values
10545
- * @return {string}
10546
- */
10547
- function serializeAttributeValue(values) {
10548
- return values ? [...values].join(' ') : '';
10549
- }
10550
-
10551
- /**
10552
- * Adds a value to an attribute containing space-delimited values.
10553
- *
10554
- * @param {HTMLElement} element
10555
- * @param {string} attr
10556
- * @param {string} value
10557
- */
10558
- function addValueToAttribute(element, attr, value) {
10559
- const values = deserializeAttributeValue(element.getAttribute(attr));
10560
- values.add(value);
10561
- element.setAttribute(attr, serializeAttributeValue(values));
10562
- }
10563
-
10564
- /**
10565
- * Removes a value from an attribute containing space-delimited values.
10566
- * If the value is the last one, the whole attribute is removed.
10567
- *
10568
- * @param {HTMLElement} element
10569
- * @param {string} attr
10570
- * @param {string} value
10571
- */
10572
- function removeValueFromAttribute(element, attr, value) {
10573
- const values = deserializeAttributeValue(element.getAttribute(attr));
10574
- values.delete(value);
10575
- if (values.size === 0) {
10576
- element.removeAttribute(attr);
10577
- return;
10578
- }
10579
- element.setAttribute(attr, serializeAttributeValue(values));
10580
- }
10581
-
10582
11596
  /**
10583
11597
  * @license
10584
11598
  * Copyright (c) 2023 Vaadin Ltd.
@@ -11409,4 +12423,4 @@ const FieldMixin = (superclass) =>
11409
12423
  }
11410
12424
  };
11411
12425
 
11412
- export { getFocusableElements as A, DelegateStateMixin as D, FieldMixin as F, InputMixin as I, KeyboardMixin as K, LabelMixin as L, PolymerElement as P, TabindexMixin as T, ValidateMixin as V, DisabledMixin as a, isElementFocused as b, DelegateFocusMixin as c, dedupingMixin as d, InputController as e, LabelledInputController as f, helper as g, html as h, i$1 as i, FocusMixin as j, PropertyEffects as k, legacyWarnings as l, legacyOptimizations as m, useShadow as n, o$3 as o, suppressTemplateNotifications as p, microTask as q, requiredField as r, strictTemplatePolicy as s, timeOut as t, usageStatistics as u, matches as v, wrap as w, translate as x, registerStyles as y, getDeepActiveElement as z };
12426
+ export { timeOut$1 as A, microTask$1 as B, matches as C, DelegateStateMixin as D, ElementMixin as E, FieldMixin as F, translate as G, SlotController as H, InputMixin as I, ControllerMixin as J, KeyboardMixin as K, LabelMixin as L, getDeepActiveElement as M, getFocusableElements as N, getAncestorRootNodes as O, PolymerElement as P, TabindexMixin as Q, idlePeriod as R, SlotObserver as S, TooltipController as T, animationFrame as U, ValidateMixin as V, flush as W, enqueueDebouncer as X, DisabledMixin as a, isElementFocused as b, DelegateFocusMixin as c, dedupingMixin as d, InputController as e, LabelledInputController as f, defineCustomElement as g, html as h, i$1 as i, ThemableMixin as j, requiredField as k, helper as l, FocusMixin as m, microTask as n, DirMixin as o, Debouncer as p, generateUniqueId as q, registerStyles as r, PropertyEffects as s, timeOut as t, strictTemplatePolicy as u, legacyWarnings as v, wrap as w, legacyOptimizations as x, useShadow as y, suppressTemplateNotifications as z };