@aurodesignsystem-dev/auro-datetime 0.0.0-pr82.5 → 0.0.0-pr82.7
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 +98 -18
- package/dist/auro-datetime-Cq0WDk5L.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
|
|
|
@@ -314,8 +361,13 @@ class AuroDatetime extends i {
|
|
|
314
361
|
* timeZone option for `Intl.DateTimeFormat`.
|
|
315
362
|
*
|
|
316
363
|
* Behavior:
|
|
317
|
-
* -
|
|
318
|
-
*
|
|
364
|
+
* - `value` omitted (null/undefined): returns today's date. `timeZone`
|
|
365
|
+
* (if any) is honored.
|
|
366
|
+
* - `value` invalid (including empty string ""): warns and returns
|
|
367
|
+
* `{ date: null }`. Empty is *not* the same as omitted — Lit reflects
|
|
368
|
+
* `value=""` as the empty string, which is not a valid ISO 8601
|
|
369
|
+
* form, so we surface it as a typo rather than silently rendering
|
|
370
|
+
* today's date.
|
|
319
371
|
* - `timeZone` set + input has offset/Z: parses as an absolute moment
|
|
320
372
|
* (offset honored) and asks `toLocaleString` to convert into the target zone.
|
|
321
373
|
* - `timeZone` set + input has no offset: interprets the wall-clock as
|
|
@@ -328,8 +380,13 @@ class AuroDatetime extends i {
|
|
|
328
380
|
* @returns {{ date: Date | null, timeZoneOption: string | undefined }}
|
|
329
381
|
*/
|
|
330
382
|
_resolveInputDate() {
|
|
331
|
-
|
|
332
|
-
|
|
383
|
+
const resolvedTz = this._resolveTimeZone(this.timeZone);
|
|
384
|
+
|
|
385
|
+
// Only true "omitted" inputs (null/undefined) get the today's-date
|
|
386
|
+
// treatment. Empty string falls through to ISO validation, which will
|
|
387
|
+
// fail and produce the documented warn + empty render.
|
|
388
|
+
if (this.value == null) {
|
|
389
|
+
return { date: new Date(), timeZoneOption: resolvedTz };
|
|
333
390
|
}
|
|
334
391
|
|
|
335
392
|
const match = this.value.match(ISO_8601_REGEX);
|
|
@@ -342,27 +399,50 @@ class AuroDatetime extends i {
|
|
|
342
399
|
|
|
343
400
|
const localPart = match[1];
|
|
344
401
|
const hasOffset = Boolean(match[2]);
|
|
402
|
+
|
|
403
|
+
// The regex permits an offset/Z suffix on a bare date (e.g. "2020-09-22Z"
|
|
404
|
+
// or "2020-09-22-07:00"). Those forms are not valid ISO 8601 — an offset
|
|
405
|
+
// is only meaningful with a time component — and the wall-clock branch
|
|
406
|
+
// would silently drop the offset, producing surprising output. Reject
|
|
407
|
+
// them up front so the consumer sees a warning.
|
|
408
|
+
if (hasOffset && !localPart.includes("T")) {
|
|
409
|
+
console.warn(
|
|
410
|
+
`auro-datetime: "${this.value}" is not a valid ISO 8601 string (offset/Z requires a time component).`,
|
|
411
|
+
);
|
|
412
|
+
return { date: null, timeZoneOption: undefined };
|
|
413
|
+
}
|
|
414
|
+
|
|
345
415
|
const normalized = localPart.includes("T")
|
|
346
416
|
? localPart
|
|
347
417
|
: `${localPart}T00:00:00`;
|
|
348
418
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
419
|
+
// The regex only validates string shape, not value ranges — "2022-99-99"
|
|
420
|
+
// matches but produces an Invalid Date. Detect that and treat it like
|
|
421
|
+
// any other malformed input so render falls back to the documented
|
|
422
|
+
// empty output instead of "Invalid Date" or a thrown RangeError from
|
|
423
|
+
// downstream Intl calls.
|
|
424
|
+
const buildDate = () => {
|
|
425
|
+
if (resolvedTz && !hasOffset) {
|
|
426
|
+
return this._zonedWallClockToUtc(normalized, resolvedTz);
|
|
352
427
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
428
|
+
if (resolvedTz && hasOffset) {
|
|
429
|
+
return new Date(this.value);
|
|
430
|
+
}
|
|
431
|
+
// Wall-clock: keep the components in the input verbatim. JS parses
|
|
432
|
+
// bare "YYYY-MM-DD" as UTC midnight, which shifts the date in the
|
|
433
|
+
// viewer's timezone; appending a time forces local-time parsing.
|
|
434
|
+
return new Date(normalized);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const date = buildDate();
|
|
438
|
+
if (Number.isNaN(date.getTime())) {
|
|
439
|
+
console.warn(
|
|
440
|
+
`auro-datetime: "${this.value}" is not a valid ISO 8601 date.`,
|
|
441
|
+
);
|
|
442
|
+
return { date: null, timeZoneOption: undefined };
|
|
360
443
|
}
|
|
361
444
|
|
|
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 };
|
|
445
|
+
return { date, timeZoneOption: resolvedTz };
|
|
366
446
|
}
|
|
367
447
|
|
|
368
448
|
/**
|
|
@@ -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,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._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,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(){const e=this._resolveTimeZone(this.timeZone);if(null==this.value)return{date:new Date,timeZoneOption:e};const t=this.value.match(i);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],a=Boolean(t[2]);if(a&&!n.includes("T"))return console.warn(`auro-datetime: "${this.value}" is not a valid ISO 8601 string (offset/Z requires a time component).`),{date:null,timeZoneOption:void 0};const o=n.includes("T")?n:`${n}T00:00:00`,s=(()=>e&&!a?this._zonedWallClockToUtc(o,e):e&&a?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 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};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{A as AuroDatetime}from"./auro-datetime-
|
|
1
|
+
export{A as AuroDatetime}from"./auro-datetime-Cq0WDk5L.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-Cq0WDk5L.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.7",
|
|
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};
|