@aurodesignsystem-dev/auro-datetime 0.0.0-pr82.5 → 0.0.0-pr82.6
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/api.md +7 -0
- package/demo/auro-datetime.min.js +74 -15
- package/dist/auro-datetime-Cml4cDmu.js +6 -0
- package/dist/index.js +1 -1
- package/dist/registered.js +1 -1
- package/package.json +1 -1
- package/dist/auro-datetime-BmzswYzJ.js +0 -6
package/demo/api.md
CHANGED
|
@@ -284,6 +284,13 @@ How the `timeZone` attribute composes with the input depends on whether `value`
|
|
|
284
284
|
- **`value` has an offset (or `Z`):** the offset anchors the absolute moment; `timeZone` is the display zone. Example: `value="2022-07-14T08:00:00-04:00" timezone="US/Pacific"` renders as `5:00 am` (8am Eastern → 5am Pacific).
|
|
285
285
|
- **`value` has no offset:** the wall-clock is interpreted as being in `timeZone`, so display in that same zone matches the input verbatim. Example: `value="2022-07-14T08:00:00" timezone="US/Eastern"` renders as `8:00 am` regardless of where the viewer is. This is the recommended shape when consumers know the source zone but don't have an offset readily available (e.g. flight-schedule data keyed by airport).
|
|
286
286
|
|
|
287
|
+
#### Invalid `timeZone` and `locale` values
|
|
288
|
+
|
|
289
|
+
Both attributes are validated up-front. Invalid inputs do **not** crash the component — they fall back gracefully and log a `console.warn` (deduplicated per element):
|
|
290
|
+
|
|
291
|
+
- **Invalid `locale`** (e.g. `"xx-INVALID-tag"`) → falls back to `"en-US"`.
|
|
292
|
+
- **Invalid `timeZone`** (e.g. `"US/Pacfic"` typo) → falls back to behaving as if `timeZone` was not specified. With `value` set, that means wall-clock display from the input components — visually the same string for every viewer. With `value` unset, that means the current time in the viewer's machine zone. The warning surfaces the typo so the developer can fix it.
|
|
293
|
+
|
|
287
294
|
## Slot Examples
|
|
288
295
|
|
|
289
296
|
### Pre and Post Slots
|
|
@@ -143,6 +143,13 @@ class AuroDatetime extends i {
|
|
|
143
143
|
*/
|
|
144
144
|
this._warnedLocales = new Set();
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Tracks invalid timeZone inputs already warned about. Same dedup
|
|
148
|
+
* pattern as `_warnedLocales`.
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
151
|
+
this._warnedTimeZones = new Set();
|
|
152
|
+
|
|
146
153
|
/**
|
|
147
154
|
* @private
|
|
148
155
|
*/
|
|
@@ -180,6 +187,46 @@ class AuroDatetime extends i {
|
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Validate an IANA timezone identifier. Invalid input warns once per
|
|
192
|
+
* unique value per element and resolves to `undefined`, which makes
|
|
193
|
+
* the rest of `_resolveInputDate` behave as if `timeZone` was never
|
|
194
|
+
* specified — i.e. wall-clock display from the input components.
|
|
195
|
+
*
|
|
196
|
+
* Without this guard, a typo like `"US/Pacfic"` would cause
|
|
197
|
+
* `Intl.DateTimeFormat` / `toLocaleString` to throw `RangeError` at
|
|
198
|
+
* render time and crash the component.
|
|
199
|
+
*
|
|
200
|
+
* Note: when `value` is also unset, the wall-clock branch resolves to
|
|
201
|
+
* "today's date in the viewer's machine zone," which is viewer-
|
|
202
|
+
* dependent. This narrow case is the only scenario where two viewers
|
|
203
|
+
* see different output for the same component; the warning surfaces
|
|
204
|
+
* the typo so the developer can fix it.
|
|
205
|
+
*
|
|
206
|
+
* @private
|
|
207
|
+
* @param {string | undefined} input - Consumer-supplied timezone.
|
|
208
|
+
* @returns {string | undefined} Valid timezone, or undefined on fallback.
|
|
209
|
+
*/
|
|
210
|
+
_resolveTimeZone(input) {
|
|
211
|
+
if (!input) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
// Constructing the formatter is enough to surface a RangeError;
|
|
216
|
+
// we don't need to format anything yet.
|
|
217
|
+
new Intl.DateTimeFormat(undefined, { timeZone: input });
|
|
218
|
+
return input;
|
|
219
|
+
} catch {
|
|
220
|
+
if (this._warnedTimeZones && !this._warnedTimeZones.has(input)) {
|
|
221
|
+
this._warnedTimeZones.add(input);
|
|
222
|
+
console.warn(
|
|
223
|
+
`auro-datetime: "${input}" is not a valid IANA timezone. Falling back to viewer-local.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
183
230
|
connectedCallback() {
|
|
184
231
|
super.connectedCallback();
|
|
185
232
|
|
|
@@ -328,8 +375,10 @@ class AuroDatetime extends i {
|
|
|
328
375
|
* @returns {{ date: Date | null, timeZoneOption: string | undefined }}
|
|
329
376
|
*/
|
|
330
377
|
_resolveInputDate() {
|
|
378
|
+
const resolvedTz = this._resolveTimeZone(this.timeZone);
|
|
379
|
+
|
|
331
380
|
if (!this.value) {
|
|
332
|
-
return { date: new Date(), timeZoneOption:
|
|
381
|
+
return { date: new Date(), timeZoneOption: resolvedTz };
|
|
333
382
|
}
|
|
334
383
|
|
|
335
384
|
const match = this.value.match(ISO_8601_REGEX);
|
|
@@ -346,23 +395,33 @@ class AuroDatetime extends i {
|
|
|
346
395
|
? localPart
|
|
347
396
|
: `${localPart}T00:00:00`;
|
|
348
397
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
398
|
+
// The regex only validates string shape, not value ranges — "2022-99-99"
|
|
399
|
+
// matches but produces an Invalid Date. Detect that and treat it like
|
|
400
|
+
// any other malformed input so render falls back to the documented
|
|
401
|
+
// empty output instead of "Invalid Date" or a thrown RangeError from
|
|
402
|
+
// downstream Intl calls.
|
|
403
|
+
const buildDate = () => {
|
|
404
|
+
if (resolvedTz && !hasOffset) {
|
|
405
|
+
return this._zonedWallClockToUtc(normalized, resolvedTz);
|
|
352
406
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
407
|
+
if (resolvedTz && hasOffset) {
|
|
408
|
+
return new Date(this.value);
|
|
409
|
+
}
|
|
410
|
+
// Wall-clock: keep the components in the input verbatim. JS parses
|
|
411
|
+
// bare "YYYY-MM-DD" as UTC midnight, which shifts the date in the
|
|
412
|
+
// viewer's timezone; appending a time forces local-time parsing.
|
|
413
|
+
return new Date(normalized);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const date = buildDate();
|
|
417
|
+
if (Number.isNaN(date.getTime())) {
|
|
418
|
+
console.warn(
|
|
419
|
+
`auro-datetime: "${this.value}" is not a valid ISO 8601 date.`,
|
|
420
|
+
);
|
|
421
|
+
return { date: null, timeZoneOption: undefined };
|
|
360
422
|
}
|
|
361
423
|
|
|
362
|
-
|
|
363
|
-
// bare "YYYY-MM-DD" as UTC midnight, which shifts the date in the
|
|
364
|
-
// viewer's timezone; appending a time forces local-time parsing.
|
|
365
|
-
return { date: new Date(normalized), timeZoneOption: undefined };
|
|
424
|
+
return { date, timeZoneOption: resolvedTz };
|
|
366
425
|
}
|
|
367
426
|
|
|
368
427
|
/**
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{LitElement as e,html as t}from"lit";class n{registerComponent(e,t){customElements.get(e)||customElements.define(e,class extends t{})}closestElement(e,t=this,n=(t,a=t&&t.closest(e))=>t&&t!==document&&t!==window?a||n(t.getRootNode().host):null){return n(t)}handleComponentTagRename(e,t){const n=t.toLowerCase();e.tagName.toLowerCase()!==n&&e.setAttribute(n,!0)}elementMatch(e,t){const n=t.toLowerCase();return e.tagName.toLowerCase()===n||e.hasAttribute(n)}getSlotText(e,t){const n=e.shadowRoot?.querySelector(`slot[name="${t}"]`);return(n?.assignedNodes({flatten:!0})||[]).map(e=>e.textContent?.trim()).join(" ").trim()||null}}const a=/^(\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)?)(Z|[+-]\d{2}:\d{2})?$/u;class i extends e{constructor(){super(),this._initializeDefaults()}_initializeDefaults(){this.weekday="short",this.month="short",this.locale="en-US",this._warnedLocales=new Set,this._warnedTimeZones=new Set,this._effectiveLocale=this._resolveLocale(this.locale),this.runtimeUtils=new n}_resolveLocale(e){if(!e)return"en-US";try{return Intl.getCanonicalLocales(e)[0]}catch{return this._warnedLocales&&!this._warnedLocales.has(e)&&(this._warnedLocales.add(e),console.warn(`auro-datetime: "${e}" is not a valid BCP 47 locale tag. Falling back to "en-US".`)),"en-US"}}_resolveTimeZone(e){if(e)try{return new Intl.DateTimeFormat(void 0,{timeZone:e}),e}catch{return void(this._warnedTimeZones&&!this._warnedTimeZones.has(e)&&(this._warnedTimeZones.add(e),console.warn(`auro-datetime: "${e}" is not a valid IANA timezone. Falling back to viewer-local.`)))}}connectedCallback(){super.connectedCallback(),this.dateTemplate={weekday:this.weekday,year:"numeric",month:this.month,day:"numeric"},this.timeTemplate={hour:"2-digit",minute:"2-digit"}}static get properties(){return{locale:{type:String},month:{type:String},timeZone:{type:String},type:{type:String},value:{type:String},weekday:{type:String}}}static register(e="auro-datetime"){n.prototype.registerComponent(e,i)}willUpdate(e){e.has("locale")&&(this._effectiveLocale=this._resolveLocale(this.locale))}firstUpdated(){this.runtimeUtils.handleComponentTagRename(this,"auro-datetime")}_zonedWallClockToUtc(e,t){const n=new Date(`${e}Z`),a=new Intl.DateTimeFormat("en-US",{timeZone:t,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}).formatToParts(n),i=Object.fromEntries(a.filter(e=>"literal"!==e.type).map(e=>[e.type,e.value])),o=new Date(`${i.year}-${i.month}-${i.day}T${i.hour}:${i.minute}:${i.second}Z`),s=n.getTime()-o.getTime();return new Date(n.getTime()+s)}_resolveInputDate(){const e=this._resolveTimeZone(this.timeZone);if(!this.value)return{date:new Date,timeZoneOption:e};const t=this.value.match(a);if(!t)return console.warn(`auro-datetime: "${this.value}" is not a valid ISO 8601 string.`),{date:null,timeZoneOption:void 0};const n=t[1],i=Boolean(t[2]),o=n.includes("T")?n:`${n}T00:00:00`,s=(()=>e&&!i?this._zonedWallClockToUtc(o,e):e&&i?new Date(this.value):new Date(o))();return Number.isNaN(s.getTime())?(console.warn(`auro-datetime: "${this.value}" is not a valid ISO 8601 date.`),{date:null,timeZoneOption:void 0}):{date:s,timeZoneOption:e}}humanDate(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={...this.dateTemplate};return t&&(n.timeZone=t),e.toLocaleString(this._effectiveLocale,n)}humanDateConversion(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={};switch(t&&(n.timeZone=t),this.type){case"day":n.day="numeric";break;case"month":n.month=this.month;break;case"year":n.year="numeric";break;case"weekday":n.weekday=this.weekday}return e.toLocaleString(this._effectiveLocale,n)}numericDate(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={...this.dateTemplate,month:"numeric"};return Reflect.deleteProperty(n,"weekday"),t&&(n.timeZone=t),e.toLocaleString(this._effectiveLocale,n)}humanTime(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={...this.timeTemplate};t&&(n.timeZone=t);const a=e.toLocaleString(this._effectiveLocale,n);return/[ap]\.?m\.?/iu.test(a)?a.replace(/^0+/u,"").toLowerCase():a}whichDate(){switch(this.type){case"date":default:return this.humanDate();case"time":return this.humanTime();case"year":case"month":case"weekday":case"day":return this.humanDateConversion();case"numeric":return this.numericDate()}}render(){return t`
|
|
2
|
+
<slot name="pre"></slot>
|
|
3
|
+
<span class="yield">${this.whichDate()}</span>
|
|
4
|
+
<slot name="post"></slot>
|
|
5
|
+
<slot></slot>
|
|
6
|
+
`}}export{i as A};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{A as AuroDatetime}from"./auro-datetime-
|
|
1
|
+
export{A as AuroDatetime}from"./auro-datetime-Cml4cDmu.js";import"lit";
|
package/dist/registered.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as r}from"./auro-datetime-
|
|
1
|
+
import{A as r}from"./auro-datetime-Cml4cDmu.js";import"lit";r.register();
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"================================================================================"
|
|
8
8
|
],
|
|
9
9
|
"name": "@aurodesignsystem-dev/auro-datetime",
|
|
10
|
-
"version": "0.0.0-pr82.
|
|
10
|
+
"version": "0.0.0-pr82.6",
|
|
11
11
|
"description": "auro-datetime HTML custom element",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{LitElement as e,html as t}from"lit";class n{registerComponent(e,t){customElements.get(e)||customElements.define(e,class extends t{})}closestElement(e,t=this,n=(t,i=t&&t.closest(e))=>t&&t!==document&&t!==window?i||n(t.getRootNode().host):null){return n(t)}handleComponentTagRename(e,t){const n=t.toLowerCase();e.tagName.toLowerCase()!==n&&e.setAttribute(n,!0)}elementMatch(e,t){const n=t.toLowerCase();return e.tagName.toLowerCase()===n||e.hasAttribute(n)}getSlotText(e,t){const n=e.shadowRoot?.querySelector(`slot[name="${t}"]`);return(n?.assignedNodes({flatten:!0})||[]).map(e=>e.textContent?.trim()).join(" ").trim()||null}}const i=/^(\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)?)(Z|[+-]\d{2}:\d{2})?$/u;class a extends e{constructor(){super(),this._initializeDefaults()}_initializeDefaults(){this.weekday="short",this.month="short",this.locale="en-US",this._warnedLocales=new Set,this._effectiveLocale=this._resolveLocale(this.locale),this.runtimeUtils=new n}_resolveLocale(e){if(!e)return"en-US";try{return Intl.getCanonicalLocales(e)[0]}catch{return this._warnedLocales&&!this._warnedLocales.has(e)&&(this._warnedLocales.add(e),console.warn(`auro-datetime: "${e}" is not a valid BCP 47 locale tag. Falling back to "en-US".`)),"en-US"}}connectedCallback(){super.connectedCallback(),this.dateTemplate={weekday:this.weekday,year:"numeric",month:this.month,day:"numeric"},this.timeTemplate={hour:"2-digit",minute:"2-digit"}}static get properties(){return{locale:{type:String},month:{type:String},timeZone:{type:String},type:{type:String},value:{type:String},weekday:{type:String}}}static register(e="auro-datetime"){n.prototype.registerComponent(e,a)}willUpdate(e){e.has("locale")&&(this._effectiveLocale=this._resolveLocale(this.locale))}firstUpdated(){this.runtimeUtils.handleComponentTagRename(this,"auro-datetime")}_zonedWallClockToUtc(e,t){const n=new Date(`${e}Z`),i=new Intl.DateTimeFormat("en-US",{timeZone:t,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}).formatToParts(n),a=Object.fromEntries(i.filter(e=>"literal"!==e.type).map(e=>[e.type,e.value])),o=new Date(`${a.year}-${a.month}-${a.day}T${a.hour}:${a.minute}:${a.second}Z`),s=n.getTime()-o.getTime();return new Date(n.getTime()+s)}_resolveInputDate(){if(!this.value)return{date:new Date,timeZoneOption:this.timeZone||void 0};const e=this.value.match(i);if(!e)return console.warn(`auro-datetime: "${this.value}" is not a valid ISO 8601 string.`),{date:null,timeZoneOption:void 0};const t=e[1],n=Boolean(e[2]),a=t.includes("T")?t:`${t}T00:00:00`;return this.timeZone?n?{date:new Date(this.value),timeZoneOption:this.timeZone}:{date:this._zonedWallClockToUtc(a,this.timeZone),timeZoneOption:this.timeZone}:{date:new Date(a),timeZoneOption:void 0}}humanDate(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={...this.dateTemplate};return t&&(n.timeZone=t),e.toLocaleString(this._effectiveLocale,n)}humanDateConversion(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={};switch(t&&(n.timeZone=t),this.type){case"day":n.day="numeric";break;case"month":n.month=this.month;break;case"year":n.year="numeric";break;case"weekday":n.weekday=this.weekday}return e.toLocaleString(this._effectiveLocale,n)}numericDate(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={...this.dateTemplate,month:"numeric"};return Reflect.deleteProperty(n,"weekday"),t&&(n.timeZone=t),e.toLocaleString(this._effectiveLocale,n)}humanTime(){const{date:e,timeZoneOption:t}=this._resolveInputDate();if(!e)return"";const n={...this.timeTemplate};t&&(n.timeZone=t);const i=e.toLocaleString(this._effectiveLocale,n);return/[ap]\.?m\.?/iu.test(i)?i.replace(/^0+/u,"").toLowerCase():i}whichDate(){switch(this.type){case"date":default:return this.humanDate();case"time":return this.humanTime();case"year":case"month":case"weekday":case"day":return this.humanDateConversion();case"numeric":return this.numericDate()}}render(){return t`
|
|
2
|
-
<slot name="pre"></slot>
|
|
3
|
-
<span class="yield">${this.whichDate()}</span>
|
|
4
|
-
<slot name="post"></slot>
|
|
5
|
-
<slot></slot>
|
|
6
|
-
`}}export{a as A};
|