@aurodesignsystem-dev/auro-datetime 0.0.0-pr111.0 → 0.0.0-pr112.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/demo/index.min.js CHANGED
@@ -1,4 +1,598 @@
1
- import { A as AuroDatetime } from './auro-datetime.min.js';
1
+ // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
2
+ // See LICENSE in the project root for license information.
3
+
4
+ // ---------------------------------------------------------------------
5
+
6
+ /* eslint-disable line-comment-position, no-inline-comments, no-confusing-arrow, no-nested-ternary, implicit-arrow-linebreak */
7
+
8
+ class AuroLibraryRuntimeUtils {
9
+
10
+ /* eslint-disable jsdoc/require-param */
11
+
12
+ /**
13
+ * This will register a new custom element with the browser.
14
+ * @param {String} name - The name of the custom element.
15
+ * @param {Object} componentClass - The class to register as a custom element.
16
+ * @returns {void}
17
+ */
18
+ registerComponent(name, componentClass) {
19
+ if (!customElements.get(name)) {
20
+ customElements.define(name, class extends componentClass {});
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Finds and returns the closest HTML Element based on a selector.
26
+ * @returns {void}
27
+ */
28
+ closestElement(
29
+ selector, // selector like in .closest()
30
+ base = this, // extra functionality to skip a parent
31
+ __Closest = (el, found = el && el.closest(selector)) =>
32
+ !el || el === document || el === window
33
+ ? null // standard .closest() returns null for non-found selectors also
34
+ : found
35
+ ? found // found a selector INside this element
36
+ : __Closest(el.getRootNode().host) // recursion!! break out to parent DOM
37
+ ) {
38
+ return __Closest(base);
39
+ }
40
+ /* eslint-enable jsdoc/require-param */
41
+
42
+ /**
43
+ * If the element passed is registered with a different tag name than what is passed in, the tag name is added as an attribute to the element.
44
+ * @param {Object} elem - The element to check.
45
+ * @param {String} tagName - The name of the Auro component to check for or add as an attribute.
46
+ * @returns {void}
47
+ */
48
+ handleComponentTagRename(elem, tagName) {
49
+ const tag = tagName.toLowerCase();
50
+ const elemTag = elem.tagName.toLowerCase();
51
+
52
+ if (elemTag !== tag) {
53
+ elem.setAttribute(tag, true);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Validates if an element is a specific Auro component.
59
+ * @param {Object} elem - The element to validate.
60
+ * @param {String} tagName - The name of the Auro component to check against.
61
+ * @returns {Boolean} - Returns true if the element is the specified Auro component.
62
+ */
63
+ elementMatch(elem, tagName) {
64
+ const tag = tagName.toLowerCase();
65
+ const elemTag = elem.tagName.toLowerCase();
66
+
67
+ return elemTag === tag || elem.hasAttribute(tag);
68
+ }
69
+
70
+ /**
71
+ * Gets the text content of a named slot.
72
+ * @returns {String}
73
+ * @private
74
+ */
75
+ getSlotText(elem, name) {
76
+ const slot = elem.shadowRoot?.querySelector(`slot[name="${name}"]`);
77
+ const nodes = slot?.assignedNodes({ flatten: true }) || [];
78
+ const text = nodes.map(n => n.textContent?.trim()).join(' ').trim();
79
+
80
+ return text || null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * @license
86
+ * Copyright 2019 Google LLC
87
+ * SPDX-License-Identifier: BSD-3-Clause
88
+ */
89
+ const t$1=globalThis,e$2=t$1.ShadowRoot&&(void 0===t$1.ShadyCSS||t$1.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$3=new WeakMap;let n$2 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$2&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$3.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$3.set(s,t));}return t}toString(){return this.cssText}};const r$2=t=>new n$2("string"==typeof t?t:t+"",void 0,s$2),S$1=(s,o)=>{if(e$2)s.adoptedStyleSheets=o.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of o){const o=document.createElement("style"),n=t$1.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$2?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$2(e)})(t):t;
90
+
91
+ /**
92
+ * @license
93
+ * Copyright 2017 Google LLC
94
+ * SPDX-License-Identifier: BSD-3-Clause
95
+ */const{is:i$2,defineProperty:e$1,getOwnPropertyDescriptor:h$1,getOwnPropertyNames:r$1,getOwnPropertySymbols:o$2,getPrototypeOf:n$1}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$2(t,s),b$1={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b$1){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$1(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$1(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b$1}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$1(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$1(t),...o$2(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach(t=>t.hostConnected?.());}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.());}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i,e=false,h){if(void 0!==t){const r=this.constructor;if(false===e&&(h=this[t]),i??=r.getPropertyOptions(t),!((i.hasChanged??f$1)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(r._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return true}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.2");
96
+
97
+ /**
98
+ * @license
99
+ * Copyright 2017 Google LLC
100
+ * SPDX-License-Identifier: BSD-3-Clause
101
+ */
102
+ const t=globalThis,i$1=t=>t,s$1=t.trustedTypes,e=s$1?s$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,h="$lit$",o$1=`lit$${Math.random().toFixed(9).slice(2)}$`,n="?"+o$1,r=`<${n}>`,l=document,c=()=>l.createComment(""),a=t=>null===t||"object"!=typeof t&&"function"!=typeof t,u=Array.isArray,d=t=>u(t)||"function"==typeof t?.[Symbol.iterator],f="[ \t\n\f\r]",v=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),g=/'/g,$=/"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),E=Symbol.for("lit-noChange"),A=Symbol.for("lit-nothing"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==e?e.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?"<svg>":3===i?"<math>":"",c=v;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,f=0;for(;f<s.length&&(c.lastIndex=f,u=c.exec(s),null!==u);)f=c.lastIndex,c===v?"!--"===u[1]?c=_:void 0!==u[1]?c=m:void 0!==u[2]?(y.test(u[2])&&(n=RegExp("</"+u[2],"g")),c=p):void 0!==u[3]&&(c=p):c===p?">"===u[0]?(c=n??v,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?p:'"'===u[3]?$:g):c===$||c===g?c=p:c===_||c===m?c=v:(c=p,n=void 0);const x=c===p&&t[i+1].startsWith("/>")?" ":"";l+=c===v?s+r:d>=0?(e.push(a),s.slice(0,d)+h+s.slice(d)+o$1+x):s+o$1+(-2===d?i:x);}return [V(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=P.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(h)){const i=v[a++],s=r.getAttribute(t).split(o$1),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:l,name:e[2],strings:s,ctor:"."===e[1]?I:"?"===e[1]?L:"@"===e[1]?z:H}),r.removeAttribute(t);}else t.startsWith(o$1)&&(d.push({type:6,index:l}),r.removeAttribute(t));if(y.test(r.tagName)){const t=r.textContent.split(o$1),i=t.length-1;if(i>0){r.textContent=s$1?s$1.emptyScript:"";for(let s=0;s<i;s++)r.append(t[s],c()),P.nextNode(),d.push({type:2,index:++l});r.append(t[i],c());}}}else if(8===r.nodeType)if(r.data===n)d.push({type:2,index:l});else {let t=-1;for(;-1!==(t=r.data.indexOf(o$1,t+1));)d.push({type:7,index:l}),t+=o$1.length-1;}l++;}}static createElement(t,i){const s=l.createElement("template");return s.innerHTML=t,s}}function M(t,i,s=t,e){if(i===E)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=a(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=M(t,h._$AS(t,i.values),h,e)),i}class R{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){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??l).importNode(i,true);P.currentNode=e;let h=P.nextNode(),o=0,n=0,r=s[0];for(;void 0!==r;){if(o===r.index){let i;2===r.type?i=new k(h,h.nextSibling,this,t):1===r.type?i=new r.ctor(h,r.name,r.strings,this,t):6===r.type&&(i=new Z(h,this,t)),this._$AV.push(i),r=s[++n];}o!==r?.index&&(h=P.nextNode(),o++);}return P.currentNode=l,e}p(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 k{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=A,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=M(this,t,i),a(t)?t===A||null==t||""===t?(this._$AH!==A&&this._$AR(),this._$AH=A):t!==this._$AH&&t!==E&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):d(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==A&&a(this._$AH)?this._$AA.nextSibling.data=t:this.T(l.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=S.createElement(V(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new R(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=C.get(t.strings);return void 0===i&&C.set(t.strings,i=new S(t)),i}k(t){u(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new k(this.O(c()),this.O(c()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,s){for(this._$AP?.(false,true,s);t!==this._$AB;){const s=i$1(t).nextSibling;i$1(t).remove(),t=s;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class H{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=A,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=M(this,e[s+n],i,n),r===E&&(r=this._$AH[n]),o||=!a(r)||r!==this._$AH[n],r===A?t=A:t!==A&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===A?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class I extends H{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===A?void 0:t;}}class L extends H{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==A);}}class z extends H{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=M(this,t,i,0)??A)===E)return;const s=this._$AH,e=t===A&&s!==A||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==A&&(s===A||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??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){M(this,t);}}const B=t.litHtmlPolyfillSupport;B?.(S,k),(t.litHtmlVersions??=[]).push("3.3.2");const D=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new k(i.insertBefore(c(),t),t,void 0,s??{});}return h._$AI(t),h};
103
+
104
+ /**
105
+ * @license
106
+ * Copyright 2017 Google LLC
107
+ * SPDX-License-Identifier: BSD-3-Clause
108
+ */const s=globalThis;class i extends y$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=D(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return E}}i._$litElement$=true,i["finalized"]=true,s.litElementHydrateSupport?.({LitElement:i});const o=s.litElementPolyfillSupport;o?.({LitElement:i});(s.litElementVersions??=[]).push("4.2.2");
109
+
110
+ // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
111
+ // See LICENSE in the project root for license information.
112
+
113
+
114
+ const ISO_8601_REGEX =
115
+ /^(\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)?)(Z|[+-]\d{2}:\d{2})?$/u;
116
+
117
+ /**
118
+ * Shows a date or time as text, formatted for a language and region you choose and, optionally, a specific time zone.
119
+ *
120
+ * **Key capabilities**
121
+ * - **What to show**: `type` picks the full date, the time, a numeric date, or a single part, such as the year or weekday.
122
+ * - **Date and time to show**: `value` takes an ISO 8601 date or date-time; without it, the element shows the current date and time.
123
+ * - **Language formatting**: `locale` formats month names, separators, and the 12- or 24-hour clock for a language, such as `de-DE`.
124
+ * - **Time zones**: `timezone` shows the moment in a time zone, such as `America/Los_Angeles`.
125
+ * - **Name length**: `month="long"` and `weekday="long"` spell out the month and weekday names.
126
+ *
127
+ * **Example**
128
+ * ```html
129
+ * <auro-datetime type="date" value="2022-07-14T08:00:00"></auro-datetime>
130
+ * ```
131
+ *
132
+ * @summary Date or time text formatted by language and time zone, with full, numeric, and single-part formats.
133
+ * @customElement auro-datetime
134
+ *
135
+ * @slot pre - Content shown before the date or time, such as "Departs". The element doesn't currently let content sit right against the date or time: this content, or text right before the element, always shows with a space between it and the date or time. {@category ui}
136
+ * @slot post - Content shown after the date or time, such as "local time". The element doesn't currently let content sit right against the date or time: this content, or text right after the element, always shows with a space between them, so a period or comma can't follow the date or time directly. {@category ui}
137
+ * @slot - Content shown after the `post` slot's content, such as the end of a sentence that includes the date. {@category ui}
138
+ */
139
+
140
+ class AuroDatetime extends i {
141
+ constructor() {
142
+ super();
143
+
144
+ this._initializeDefaults();
145
+ }
146
+
147
+ /**
148
+ * Sets the element's default values.
149
+ * @private
150
+ * @returns {void}
151
+ */
152
+ _initializeDefaults() {
153
+ this.weekday = "short";
154
+ this.month = "short";
155
+ this.locale = "en-US";
156
+
157
+ /**
158
+ * Tracks invalid locale inputs already warned about, so a parent that
159
+ * re-renders with the same bad value doesn't flood the console.
160
+ * @private
161
+ */
162
+ this._warnedLocales = new Set();
163
+
164
+ /**
165
+ * Tracks invalid `timezone` values this element has already warned
166
+ * about, so the same bad value logs only one warning.
167
+ * @private
168
+ */
169
+ this._warnedTimeZones = new Set();
170
+
171
+ /**
172
+ * Tracks invalid `value` inputs this element has already warned about,
173
+ * so the same bad value logs only one warning.
174
+ * @private
175
+ */
176
+ this._warnedValues = new Set();
177
+
178
+ /**
179
+ * @private
180
+ */
181
+ this._effectiveLocale = this._resolveLocale(this.locale);
182
+
183
+ /**
184
+ * @private
185
+ */
186
+ this._effectiveTimeZone = this._resolveTimeZone(this.timezone);
187
+
188
+ /**
189
+ * @private
190
+ */
191
+ this.runtimeUtils = new AuroLibraryRuntimeUtils();
192
+ }
193
+
194
+ /**
195
+ * Checks a BCP 47 locale tag and returns its canonical form. Empty or invalid
196
+ * input falls back to "en-US". Each invalid value logs a console warning the
197
+ * first time this element sees it.
198
+ *
199
+ * @private
200
+ * @param {string} input - The locale tag set on the element.
201
+ * @returns {string} The canonical locale tag, such as "en-GB", or "en-US" for empty or invalid input.
202
+ */
203
+ _resolveLocale(input) {
204
+ if (!input) {
205
+ return "en-US";
206
+ }
207
+ try {
208
+ return Intl.getCanonicalLocales(input)[0];
209
+ } catch {
210
+ if (this._warnedLocales && !this._warnedLocales.has(input)) {
211
+ this._warnedLocales.add(input);
212
+ console.warn(
213
+ `auro-datetime: "${input}" is not a valid BCP 47 locale tag. Falling back to "en-US".`,
214
+ );
215
+ }
216
+ return "en-US";
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Checks an IANA time zone name. An invalid name logs a console warning
222
+ * the first time this element sees it and returns `undefined`, so the
223
+ * element acts as if `timezone` isn't set.
224
+ *
225
+ * @private
226
+ * @param {string | undefined} input - The time zone name set on the element.
227
+ * @returns {string | undefined} The time zone name if it's valid; otherwise, `undefined`.
228
+ */
229
+ _resolveTimeZone(input) {
230
+ if (!input) {
231
+ return undefined;
232
+ }
233
+ try {
234
+ // Creating the formatter is enough to catch an invalid name. Without
235
+ // this check, a typo such as "America/Los_Angles" makes
236
+ // Intl.DateTimeFormat and toLocaleString throw a RangeError at render
237
+ // time.
238
+ new Intl.DateTimeFormat(undefined, { timeZone: input });
239
+ return input;
240
+ } catch {
241
+ if (this._warnedTimeZones && !this._warnedTimeZones.has(input)) {
242
+ this._warnedTimeZones.add(input);
243
+ console.warn(
244
+ `auro-datetime: "${input}" is not a valid IANA timezone. Ignoring it.`,
245
+ );
246
+ }
247
+ return undefined;
248
+ }
249
+ }
250
+
251
+ static get properties() {
252
+ return {
253
+ /**
254
+ * Sets the language and region used to format the date or time, such as `en-GB`, `de-DE`, or `ja-JP` (a BCP 47 language tag). It changes month and weekday names, separators, and whether times use a 12- or 24-hour clock, but not the time zone. A malformed tag logs a console warning and falls back to `en-US`; a well-formed tag the browser doesn't support, such as `zz-ZZ`, uses the browser's default language instead, with no warning.
255
+ * @type {string}
256
+ * @default "en-US"
257
+ * @category ui
258
+ */
259
+ locale: { type: String },
260
+
261
+ /**
262
+ * Sets how the month name is written: `short`, such as "Jul", or `long`, such as "July". It applies to the full date and to `type="month"`.
263
+ * @type {"short" | "long"}
264
+ * @default "short"
265
+ * @category ui
266
+ */
267
+ month: { type: String },
268
+
269
+ /**
270
+ * Sets the time zone to show the date or time in, such as `America/Los_Angeles` or `UTC` (an IANA time zone name). When `value` has an offset or `Z`, the element converts that moment into this zone; when it has none, the element reads `value` as a time already in this zone. Without it, or with an invalid name, which logs a console warning, the element shows the date and time in `value` as written, or the current date and time in the viewer's own time zone when `value` isn't set.
271
+ * @type {string}
272
+ * @category behavior
273
+ */
274
+ timezone: { type: String },
275
+
276
+ /**
277
+ * Sets which part of the date or time to show: `date` for the full date, such as "Thu, Jul 14, 2022", `numeric` for a numeric date, such as "7/14/2022", `time` for the hour and minute, such as "8:00 am", or `year`, `month`, `weekday`, or `day` for that part alone. Without it, or with any other value, the element shows the full date.
278
+ * @type {"date" | "time" | "year" | "month" | "weekday" | "day" | "numeric"}
279
+ * @category ui
280
+ */
281
+ type: { type: String },
282
+
283
+ /**
284
+ * Sets the date or time to show, as an ISO 8601 date or date-time, such as `2022-07-14`, `2022-07-14T08:00:00`, or `2022-07-14T08:00:00-07:00`. Without it, the element shows the current date and time from when it last rendered; it doesn't currently update as time passes. Invalid input, including an empty value, logs a console warning and shows nothing.
285
+ * @type {string}
286
+ * @category behavior
287
+ */
288
+ value: { type: String },
289
+
290
+ /**
291
+ * Sets how the weekday name is written: `short`, such as "Thu", or `long`, such as "Thursday". It applies to the full date and to `type="weekday"`.
292
+ * @type {"short" | "long"}
293
+ * @default "short"
294
+ * @category ui
295
+ */
296
+ weekday: { type: String },
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Registers the element with the browser under a tag name. Use a custom name to run more than one version of the element on the same page. If the tag name is already registered, it does nothing.
302
+ * @param {string} [name="auro-datetime"] - The tag name to register the element under, such as `custom-datetime`.
303
+ * @returns {void}
304
+ *
305
+ * @example
306
+ * AuroDatetime.register("custom-datetime"); // registers <custom-datetime>
307
+ */
308
+ static register(name = "auro-datetime") {
309
+ AuroLibraryRuntimeUtils.prototype.registerComponent(name, AuroDatetime);
310
+ }
311
+
312
+ willUpdate(changedProperties) {
313
+ if (changedProperties.has("locale")) {
314
+ this._effectiveLocale = this._resolveLocale(this.locale);
315
+ }
316
+ if (changedProperties.has("timezone")) {
317
+ this._effectiveTimeZone = this._resolveTimeZone(this.timezone);
318
+ }
319
+ }
320
+
321
+ firstUpdated() {
322
+ // Adds the tag name as an attribute when it differs from auro-datetime.
323
+ this.runtimeUtils.handleComponentTagRename(this, "auro-datetime");
324
+ }
325
+
326
+ /**
327
+ * Creates a Date that shows the given wall-clock time when displayed in
328
+ * the given IANA time zone. `_resolveInputDate` uses it when `value` has
329
+ * no offset and `timezone` is set, so the time is read as already being
330
+ * in that zone rather than the viewer's.
331
+ *
332
+ * It parses the wall-clock time as if it were UTC, asks `Intl` what that
333
+ * moment looks like in the target zone, and uses the difference between
334
+ * the two to find the real moment.
335
+ *
336
+ * @private
337
+ * @param {string} localISO - The ISO 8601 date-time with no offset, such as "2022-07-14T08:00:00".
338
+ * @param {string} timeZone - The IANA time zone name, such as "America/New_York".
339
+ * @returns {Date} The moment that reads as `localISO` in `timeZone`.
340
+ */
341
+ _zonedWallClockToUtc(localISO, timeZone) {
342
+ const naiveUtc = new Date(`${localISO}Z`);
343
+ const parts = new Intl.DateTimeFormat("en-US", {
344
+ timeZone,
345
+ hourCycle: "h23",
346
+ year: "numeric",
347
+ month: "2-digit",
348
+ day: "2-digit",
349
+ hour: "2-digit",
350
+ minute: "2-digit",
351
+ second: "2-digit",
352
+ }).formatToParts(naiveUtc);
353
+ const lookup = Object.fromEntries(
354
+ parts.filter((p) => p.type !== "literal").map((p) => [p.type, p.value]),
355
+ );
356
+ const seenInZone = new Date(
357
+ `${lookup.year}-${lookup.month}-${lookup.day}T${lookup.hour}:${lookup.minute}:${lookup.second}Z`,
358
+ );
359
+ const offsetMs = naiveUtc.getTime() - seenInZone.getTime();
360
+ return new Date(naiveUtc.getTime() + offsetMs);
361
+ }
362
+
363
+ /**
364
+ * Gets the Date to show and the `timeZone` option for `Intl.DateTimeFormat`,
365
+ * from `value` and `timezone`. Returns `{ date: null }` for invalid input,
366
+ * including an empty string.
367
+ *
368
+ * @private
369
+ * @returns {{ date: Date | null, timeZoneOption: string | undefined }} The Date to show, or `null` for invalid input, and the `timeZone` option to format it with.
370
+ */
371
+ _resolveInputDate() {
372
+ // How each case resolves:
373
+ // - `value` not set (null or undefined): the current date and time, in
374
+ // `timezone` if it's set.
375
+ // - `value` invalid, including an empty string: logs a warning and
376
+ // returns { date: null }. An empty string isn't the same as not set:
377
+ // Lit gives value="" as an empty string, which isn't valid ISO 8601,
378
+ // so it's reported as a likely mistake instead of showing the
379
+ // current date.
380
+ // - `timezone` set and `value` has an offset or Z: reads `value` as an
381
+ // exact moment and has toLocaleString convert it into `timezone`.
382
+ // - `timezone` set and `value` has no offset: reads `value` as a time
383
+ // already in `timezone`, so every viewer sees it as written.
384
+ // - `timezone` not set: drops any offset or Z and reads the rest as
385
+ // local time, so it shows as written.
386
+ const resolvedTz = this._effectiveTimeZone;
387
+
388
+ // Only true "omitted" inputs (null/undefined) get the current-date
389
+ // treatment. Empty string falls through to ISO validation, which
390
+ // fails and produces the documented warning and empty output.
391
+ if (this.value == null) {
392
+ return { date: new Date(), timeZoneOption: resolvedTz };
393
+ }
394
+
395
+ const match = this.value.match(ISO_8601_REGEX);
396
+ if (!match) {
397
+ if (!this._warnedValues.has(this.value)) {
398
+ this._warnedValues.add(this.value);
399
+ console.warn(
400
+ `auro-datetime: "${this.value}" is not a valid ISO 8601 string.`,
401
+ );
402
+ }
403
+ return { date: null, timeZoneOption: undefined };
404
+ }
405
+
406
+ const localPart = match[1];
407
+ const hasOffset = Boolean(match[2]);
408
+
409
+ // The regex permits an offset/Z suffix on a bare date (such as "2020-09-22Z"
410
+ // or "2020-09-22-07:00"). Those forms are not valid ISO 8601: an offset
411
+ // is only meaningful with a time component, and the wall-clock branch
412
+ // would silently drop the offset, producing surprising output. Reject
413
+ // them up front so the consumer sees a warning.
414
+ if (hasOffset && !localPart.includes("T")) {
415
+ if (!this._warnedValues.has(this.value)) {
416
+ this._warnedValues.add(this.value);
417
+ console.warn(
418
+ `auro-datetime: "${this.value}" is not a valid ISO 8601 string (offset/Z requires a time component).`,
419
+ );
420
+ }
421
+ return { date: null, timeZoneOption: undefined };
422
+ }
423
+
424
+ const normalized = localPart.includes("T")
425
+ ? localPart
426
+ : `${localPart}T00:00:00`;
427
+
428
+ // The regex only validates string shape, not value ranges: "2022-99-99"
429
+ // matches but produces an Invalid Date. Detect that and treat it like
430
+ // any other malformed input so render falls back to the documented
431
+ // empty output instead of "Invalid Date" or a thrown RangeError from
432
+ // downstream Intl calls.
433
+ const buildDate = () => {
434
+ if (resolvedTz && !hasOffset) {
435
+ return this._zonedWallClockToUtc(normalized, resolvedTz);
436
+ }
437
+ if (resolvedTz && hasOffset) {
438
+ return new Date(this.value);
439
+ }
440
+ // Wall-clock: keep the components in the input verbatim. JS parses
441
+ // bare "YYYY-MM-DD" as UTC midnight, which shifts the date in the
442
+ // viewer's timezone; appending a time forces local-time parsing.
443
+ return new Date(normalized);
444
+ };
445
+
446
+ const date = buildDate();
447
+ if (Number.isNaN(date.getTime())) {
448
+ if (!this._warnedValues.has(this.value)) {
449
+ this._warnedValues.add(this.value);
450
+ console.warn(
451
+ `auro-datetime: "${this.value}" is not a valid ISO 8601 date.`,
452
+ );
453
+ }
454
+ return { date: null, timeZoneOption: undefined };
455
+ }
456
+
457
+ return { date, timeZoneOption: resolvedTz };
458
+ }
459
+
460
+ /**
461
+ * Formats the full date, such as "Thu, Jul 14, 2022", with the current `month` and `weekday` settings.
462
+ * @private
463
+ * @returns {string} The formatted date, or an empty string when `value` is invalid.
464
+ */
465
+ humanDate() {
466
+ const { date, timeZoneOption } = this._resolveInputDate();
467
+ if (!date) {
468
+ return "";
469
+ }
470
+ // Read month and weekday on every render so later changes show up.
471
+ const template = {
472
+ weekday: this.weekday || "short",
473
+ year: "numeric",
474
+ month: this.month || "short",
475
+ day: "numeric",
476
+ };
477
+ if (timeZoneOption) {
478
+ template.timeZone = timeZoneOption;
479
+ }
480
+ return date.toLocaleString(this._effectiveLocale, template);
481
+ }
482
+
483
+ /**
484
+ * Formats the single date part that `type` names: the year, month, weekday, or day.
485
+ * @private
486
+ * @returns {string} The formatted date, or an empty string when `value` is invalid.
487
+ */
488
+ humanDateConversion() {
489
+ const { date, timeZoneOption } = this._resolveInputDate();
490
+ if (!date) {
491
+ return "";
492
+ }
493
+ const template = {};
494
+ if (timeZoneOption) {
495
+ template.timeZone = timeZoneOption;
496
+ }
497
+
498
+ switch (this.type) {
499
+ case "day":
500
+ template.day = "numeric";
501
+ break;
502
+ case "month":
503
+ template.month = this.month || "short";
504
+ break;
505
+ case "year":
506
+ template.year = "numeric";
507
+ break;
508
+ case "weekday":
509
+ template.weekday = this.weekday || "short";
510
+ break;
511
+ }
512
+
513
+ return date.toLocaleString(this._effectiveLocale, template);
514
+ }
515
+
516
+ /**
517
+ * Formats the date in numbers, in the order and with the separators `locale` uses, such as "7/14/2022" for `en-US`.
518
+ * @private
519
+ * @returns {string} The formatted date, or an empty string when `value` is invalid.
520
+ */
521
+ numericDate() {
522
+ const { date, timeZoneOption } = this._resolveInputDate();
523
+ if (!date) {
524
+ return "";
525
+ }
526
+ const template = { year: "numeric", month: "numeric", day: "numeric" };
527
+ if (timeZoneOption) {
528
+ template.timeZone = timeZoneOption;
529
+ }
530
+ return date.toLocaleString(this._effectiveLocale, template);
531
+ }
532
+
533
+ /**
534
+ * Formats the hour and minute. When the time ends in AM or PM, it lowercases the period and drops a leading zero.
535
+ * @private
536
+ * @returns {string} The formatted time, or an empty string when `value` is invalid.
537
+ */
538
+ humanTime() {
539
+ const { date, timeZoneOption } = this._resolveInputDate();
540
+ if (!date) {
541
+ return "";
542
+ }
543
+ const template = { hour: "2-digit", minute: "2-digit" };
544
+ if (timeZoneOption) {
545
+ template.timeZone = timeZoneOption;
546
+ }
547
+
548
+ const formatted = date.toLocaleString(this._effectiveLocale, template);
549
+
550
+ // Detect a 12-hour rendered output by the presence of a Latin-script
551
+ // period marker. The pattern matches `am`, `pm`, `a.m.`, `p.m.`
552
+ // (case-insensitive), so it catches en-US (`PM`), en-AU/hi-IN (`pm`),
553
+ // and en-CA/es-MX (`p.m.`) alike. Non-Latin period markers (ko `오후`,
554
+ // ar `م`, zh-TW `下午`) have no ASCII a/p+m chars and correctly fall
555
+ // through unchanged. 24h locales (en-GB, de-DE, fr-FR, ja-JP, and
556
+ // others) also fall through, preserving their leading zeros.
557
+ if (!/[ap]\.?m\.?/iu.test(formatted)) {
558
+ return formatted;
559
+ }
560
+
561
+ return formatted.replace(/^0+/u, "").toLowerCase();
562
+ }
563
+
564
+ /**
565
+ * Gets the text to show for the current `type`.
566
+ * @private
567
+ * @returns {string} The formatted text; the full date when `type` isn't set or isn't recognized.
568
+ */
569
+ whichDate() {
570
+ switch (this.type) {
571
+ case "date":
572
+ return this.humanDate();
573
+ case "time":
574
+ return this.humanTime();
575
+ case "year":
576
+ case "month":
577
+ case "weekday":
578
+ case "day":
579
+ return this.humanDateConversion();
580
+ case "numeric":
581
+ return this.numericDate();
582
+ default:
583
+ return this.humanDate();
584
+ }
585
+ }
586
+
587
+ render() {
588
+ return b`
589
+ <slot name="pre"></slot>
590
+ <span class="yield">${this.whichDate()}</span>
591
+ <slot name="post"></slot>
592
+ <slot></slot>
593
+ `;
594
+ }
595
+ }
2
596
 
3
597
  /* eslint-disable jsdoc/require-jsdoc, no-magic-numbers, no-param-reassign */
4
598