@aquera/nile-elements 2.0.1 → 2.0.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.
Files changed (53) hide show
  1. package/README.md +5 -0
  2. package/demo/nxtgen-dark.css +2 -2
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.esm.js +1 -1
  5. package/dist/index.js +812 -465
  6. package/dist/nile-timestamp-builder/index.cjs.js +2 -0
  7. package/dist/nile-timestamp-builder/index.cjs.js.map +1 -0
  8. package/dist/nile-timestamp-builder/index.esm.js +1 -0
  9. package/dist/nile-timestamp-builder/nile-timestamp-builder-utils.cjs.js +2 -0
  10. package/dist/nile-timestamp-builder/nile-timestamp-builder-utils.cjs.js.map +1 -0
  11. package/dist/nile-timestamp-builder/nile-timestamp-builder-utils.esm.js +1 -0
  12. package/dist/nile-timestamp-builder/nile-timestamp-builder.cjs.js +2 -0
  13. package/dist/nile-timestamp-builder/nile-timestamp-builder.cjs.js.map +1 -0
  14. package/dist/nile-timestamp-builder/nile-timestamp-builder.css.cjs.js +2 -0
  15. package/dist/nile-timestamp-builder/nile-timestamp-builder.css.cjs.js.map +1 -0
  16. package/dist/nile-timestamp-builder/nile-timestamp-builder.css.esm.js +195 -0
  17. package/dist/nile-timestamp-builder/nile-timestamp-builder.esm.js +154 -0
  18. package/dist/nile-timestamp-builder/timezones.cjs.js +2 -0
  19. package/dist/nile-timestamp-builder/timezones.cjs.js.map +1 -0
  20. package/dist/nile-timestamp-builder/timezones.esm.js +1 -0
  21. package/dist/src/index.d.ts +1 -0
  22. package/dist/src/index.js +1 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/nile-timestamp-builder/index.d.ts +1 -0
  25. package/dist/src/nile-timestamp-builder/index.js +2 -0
  26. package/dist/src/nile-timestamp-builder/index.js.map +1 -0
  27. package/dist/src/nile-timestamp-builder/nile-timestamp-builder-utils.d.ts +54 -0
  28. package/dist/src/nile-timestamp-builder/nile-timestamp-builder-utils.js +254 -0
  29. package/dist/src/nile-timestamp-builder/nile-timestamp-builder-utils.js.map +1 -0
  30. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.css.d.ts +7 -0
  31. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.css.js +203 -0
  32. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.css.js.map +1 -0
  33. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.d.ts +94 -0
  34. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.js +578 -0
  35. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.js.map +1 -0
  36. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.test.d.ts +1 -0
  37. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.test.js +890 -0
  38. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.test.js.map +1 -0
  39. package/dist/src/nile-timestamp-builder/timezones.d.ts +8 -0
  40. package/dist/src/nile-timestamp-builder/timezones.js +357 -0
  41. package/dist/src/nile-timestamp-builder/timezones.js.map +1 -0
  42. package/dist/src/version.js +2 -2
  43. package/dist/src/version.js.map +1 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +1 -1
  46. package/src/index.ts +1 -0
  47. package/src/nile-timestamp-builder/index.ts +1 -0
  48. package/src/nile-timestamp-builder/nile-timestamp-builder-utils.ts +321 -0
  49. package/src/nile-timestamp-builder/nile-timestamp-builder.css.ts +204 -0
  50. package/src/nile-timestamp-builder/nile-timestamp-builder.test.ts +1089 -0
  51. package/src/nile-timestamp-builder/nile-timestamp-builder.ts +625 -0
  52. package/src/nile-timestamp-builder/timezones.ts +356 -0
  53. package/vscode-html-custom-data.json +92 -0
@@ -0,0 +1,625 @@
1
+ /**
2
+ * Copyright Aquera Inc 2026
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { html, nothing } from 'lit';
9
+ import { customElement, property, query, state } from 'lit/decorators.js';
10
+ import { classMap } from 'lit/directives/class-map.js';
11
+ import { styles } from './nile-timestamp-builder.css';
12
+ import NileElement from '../internal/nile-element';
13
+ import type { CSSResultGroup, PropertyValues } from 'lit';
14
+ import type { NileFormControl } from '../internal/nile-element';
15
+ import { FormControlController } from '../internal/form';
16
+ import { defaultValue } from '../internal/default-value';
17
+ import {
18
+ EPOCH_WALLCLOCK,
19
+ TimestampField,
20
+ WallClock,
21
+ clampWallClock,
22
+ daysInMonth,
23
+ epochToFields,
24
+ fieldsToEpoch,
25
+ formatWallClock,
26
+ formatZonedValue,
27
+ getLocalTimeZone,
28
+ getTimezoneList,
29
+ isValidTimeZone,
30
+ normalizeValueInput,
31
+ parseZonedValue,
32
+ } from './nile-timestamp-builder-utils';
33
+
34
+ /**
35
+ * @event nile-init - Emitted when the component is added to the DOM.
36
+ * @event nile-destroy - Emitted when the component is removed from the DOM.
37
+ * @event nile-change - Emitted when the value is committed.
38
+ * @event nile-input - Emitted as fields change, before the value is committed.
39
+ * @event nile-expand - Emitted when the editor opens.
40
+ * @event nile-collapse - Emitted when the editor closes.
41
+ * @event nile-copy - Emitted after the value is written to the clipboard.
42
+ * @event nile-copy-error - Emitted when the clipboard write is refused.
43
+ */
44
+ @customElement('nile-timestamp-builder')
45
+ export class NileTimestampBuilder
46
+ extends NileElement
47
+ implements NileFormControl
48
+ {
49
+ static get styles(): CSSResultGroup {
50
+ return [styles];
51
+ }
52
+
53
+ private formControlController: FormControlController | null = null;
54
+ private internalValueUpdate = false;
55
+ private internalTimezoneUpdate = false;
56
+ @query('.value-input') valueInput!: HTMLInputElement;private _value: Date | null = null;
57
+ @property({
58
+ attribute: 'value',
59
+ converter: {
60
+ fromAttribute: (value: string | null) => value,
61
+ toAttribute: (value: Date | null) =>
62
+ value === null ? null : value.toISOString(),
63
+ },
64
+ })
65
+ get value(): Date | null {
66
+ return this._value;
67
+ }
68
+
69
+ set value(input: Date | number | string | null) {
70
+ if (typeof input === 'string') {
71
+ const zoned = parseZonedValue(input);
72
+ if (zoned && isValidTimeZone(zoned.timezone)) {
73
+ this.timezone = zoned.timezone;
74
+ }
75
+ }
76
+ const oldValue = this._value;
77
+ const next = normalizeValueInput(input);
78
+ if (next?.getTime() === oldValue?.getTime()) {
79
+ this.internalValueUpdate = false;
80
+ return;
81
+ }
82
+ this._value = next;
83
+ this.requestUpdate('value', oldValue);
84
+ }
85
+
86
+ @defaultValue() defaultValue: Date | null = null;
87
+ @property({ type: String, reflect: true }) timezone: string =
88
+ getLocalTimeZone();
89
+ @property({ type: String, reflect: true }) name = '';
90
+ @property({ type: Boolean, reflect: true }) disabled = false;
91
+ @property({ type: Boolean, reflect: true }) required = false;
92
+ @property({ type: Boolean, reflect: true }) readonly = false;
93
+ @property({ type: Boolean, reflect: true }) copyable = false;
94
+ @property({ type: String, reflect: true }) form = '';
95
+ @property({ type: Boolean, reflect: true }) open = false;
96
+ @property({ type: String, reflect: true }) label = '';
97
+ @property({ attribute: true, type: Number, reflect: true }) minYear = 1;
98
+ @property({ attribute: true, type: Number, reflect: true }) maxYear = 9999;
99
+ @state() wallClock: WallClock = EPOCH_WALLCLOCK;
100
+ @state() timezoneList: string[] = [];
101
+ @state() hasValue = false;
102
+ @state() justCopied = false;
103
+
104
+ get validity(): ValidityState {
105
+ return this.valueInput?.validity;
106
+ }
107
+
108
+ get validationMessage(): string {
109
+ return this.valueInput?.validationMessage ?? '';
110
+ }
111
+
112
+ connectedCallback() {
113
+ super.connectedCallback();
114
+
115
+ this.timezoneList = getTimezoneList();
116
+
117
+ if (!isValidTimeZone(this.timezone)) {
118
+ this.timezone = getLocalTimeZone();
119
+ }
120
+
121
+ if (this.value !== null) {
122
+ this.wallClock = epochToFields(this.value.getTime(), this.timezone);
123
+ this.hasValue = true;
124
+ } else {
125
+ this.wallClock = epochToFields(Date.now(), this.timezone);
126
+ this.hasValue = false;
127
+ }
128
+
129
+ this.formControlController = new FormControlController(this, {
130
+ assumeInteractionOn: ['nile-change'],
131
+ value: input => {
132
+ const el = input as NileTimestampBuilder;
133
+ return el.value === null ? undefined : (el.valueString() ?? undefined);
134
+ },
135
+ defaultValue: input => (input as NileTimestampBuilder).defaultValue,
136
+ setValue: (input, val) => {
137
+ (input as NileTimestampBuilder).value = val as string | null;
138
+ },
139
+ });
140
+
141
+ this.emit('nile-init');
142
+ }
143
+
144
+ disconnectedCallback() {
145
+ super.disconnectedCallback();
146
+ window.clearTimeout(this.copiedTimer);
147
+ this.emit('nile-destroy');
148
+ }
149
+
150
+ protected willUpdate(changedProperties: PropertyValues): void {
151
+ if (changedProperties.has('value')) {
152
+ if (this.internalValueUpdate) {
153
+ this.internalValueUpdate = false;
154
+ } else {
155
+ if (this.value === null) {
156
+ this.hasValue = false;
157
+ } else {
158
+ this.wallClock = epochToFields(this.value.getTime(), this.timezone);
159
+ this.hasValue = true;
160
+ }
161
+ }
162
+ } else if (this.isTimezoneReassigned(changedProperties) && !this.open) {
163
+ this.syncValueToTimezone();
164
+ }
165
+ }
166
+
167
+ protected updated(changedProperties: PropertyValues): void {
168
+ if (this.isTimezoneReassigned(changedProperties)) {
169
+ if (this.internalTimezoneUpdate) {
170
+ this.internalTimezoneUpdate = false;
171
+ } else {
172
+ this.emit('nile-input', this.liveDetail());
173
+ }
174
+ }
175
+ }
176
+
177
+ private isTimezoneReassigned(changedProperties: PropertyValues): boolean {
178
+ return (
179
+ changedProperties.has('timezone') &&
180
+ changedProperties.get('timezone') !== undefined
181
+ );
182
+ }
183
+
184
+ private syncValueToTimezone(): void {
185
+ if (!this.hasValue || this.value === null) return;
186
+ const epoch = fieldsToEpoch(this.wallClock, this.timezone);
187
+ if (Number.isNaN(epoch) || epoch === this.value.getTime()) return;
188
+ this.internalValueUpdate = true;
189
+ this.value = new Date(epoch);
190
+ this.syncHiddenInput();
191
+ }
192
+
193
+ private valueString(): string | null {
194
+ return this.value === null ? null : this.value.toISOString();
195
+ }
196
+
197
+ private syncHiddenInput(): void {
198
+ if (this.valueInput) {
199
+ this.valueInput.value = this.valueString() ?? '';
200
+ }
201
+ this.formControlController?.updateValidity();
202
+ }
203
+
204
+ private changeDetail(): {
205
+ value: string | null;
206
+ localTimezoneValue: Date | null;
207
+ unix: number | null;
208
+ timezone: string;
209
+ } {
210
+ if (this.value === null) {
211
+ return {
212
+ value: null,
213
+ localTimezoneValue: null,
214
+ unix: null,
215
+ timezone: this.timezone,
216
+ };
217
+ }
218
+ const epoch = this.value.getTime();
219
+ return {
220
+ value: formatZonedValue(this.wallClock, this.timezone),
221
+ localTimezoneValue: new Date(epoch),
222
+ // Unix time (epoch milliseconds, matching Date.getTime()).
223
+ unix: epoch,
224
+ timezone: this.timezone,
225
+ };
226
+ }
227
+
228
+
229
+ private liveDetail(): {
230
+ value: string | null;
231
+ localTimezoneValue: Date | null;
232
+ unix: number | null;
233
+ timezone: string;
234
+ } {
235
+ const epoch = fieldsToEpoch(this.wallClock, this.timezone);
236
+ if (Number.isNaN(epoch)) {
237
+ return {
238
+ value: null,
239
+ localTimezoneValue: null,
240
+ unix: null,
241
+ timezone: this.timezone,
242
+ };
243
+ }
244
+ return {
245
+ value: formatZonedValue(this.wallClock, this.timezone),
246
+ localTimezoneValue: new Date(epoch),
247
+ // Unix time (epoch milliseconds, matching Date.getTime()).
248
+ unix: epoch,
249
+ timezone: this.timezone,
250
+ };
251
+ }
252
+
253
+ private commit(): void {
254
+ const epoch = fieldsToEpoch(this.wallClock, this.timezone);
255
+ this.internalValueUpdate = true;
256
+ this.value = new Date(epoch);
257
+ this.hasValue = true;
258
+ this.syncHiddenInput();
259
+ this.emit('nile-change', this.changeDetail());
260
+ }
261
+
262
+
263
+ private onFieldChange(field: TimestampField) {
264
+ return (event: CustomEvent) => {
265
+ event.stopPropagation();
266
+ const raw = Number(event.detail?.value);
267
+ const next = clampWallClock(
268
+ { ...this.wallClock, [field]: raw },
269
+ this.minYear,
270
+ this.maxYear
271
+ );
272
+ this.wallClock = next;
273
+ this.emit('nile-input', this.liveDetail());
274
+ };
275
+ }
276
+
277
+ private onTimezoneChange(event: CustomEvent) {
278
+ event.stopPropagation();
279
+ const next = event.detail?.value;
280
+ if (typeof next === 'string' && isValidTimeZone(next)) {
281
+ this.internalTimezoneUpdate = true;
282
+ this.timezone = next;
283
+ this.emit('nile-input', this.liveDetail());
284
+ }
285
+ }
286
+
287
+ private handleSetNow = () => {
288
+ if (this.disabled || this.readonly) return;
289
+ const now = Date.now();
290
+ this.wallClock = epochToFields(now, this.timezone);
291
+ this.commit();
292
+ };
293
+
294
+ /** Copies the canonical zoned value */
295
+ private handleCopy = async () => {
296
+ if (this.disabled || !this.hasValue) return;
297
+ const text = formatZonedValue(this.wallClock, this.timezone);
298
+ try {
299
+ await navigator.clipboard.writeText(text);
300
+ } catch {
301
+ this.emit('nile-copy-error', { value: text });
302
+ return;
303
+ }
304
+ this.justCopied = true;
305
+ window.clearTimeout(this.copiedTimer);
306
+ this.copiedTimer = window.setTimeout(() => {
307
+ this.justCopied = false;
308
+ }, NileTimestampBuilder.COPIED_FEEDBACK_MS);
309
+ this.emit('nile-copy', { value: text });
310
+ };
311
+
312
+ private handleStageNow = () => {
313
+ if (this.disabled || this.readonly) return;
314
+ const now = Date.now();
315
+ this.wallClock = epochToFields(now, this.timezone);
316
+ this.emit('nile-input', this.liveDetail());
317
+ };
318
+
319
+ private handleUseLocalTz = () => {
320
+ if (this.disabled || this.readonly) return;
321
+ this.internalTimezoneUpdate = true;
322
+ this.timezone = getLocalTimeZone();
323
+ this.emit('nile-input', this.liveDetail());
324
+ };
325
+
326
+ private handleUseUtc = () => {
327
+ if (this.disabled || this.readonly) return;
328
+ this.internalTimezoneUpdate = true;
329
+ this.timezone = 'UTC';
330
+ this.emit('nile-input', this.liveDetail());
331
+ };
332
+
333
+ /** Touch input has no reliable `dblclick`, so double-taps are timed by hand. */
334
+ private static readonly DOUBLE_TAP_MS = 300;
335
+ private static readonly COPIED_FEEDBACK_MS = 1600;
336
+ private lastTapAt = 0;
337
+ private copiedTimer = 0;
338
+
339
+ private handleDisplayDoubleClick = () => {
340
+ this.handleEdit();
341
+ };
342
+
343
+ private handleDisplayPointerUp = (event: PointerEvent) => {
344
+ if (event.pointerType === 'mouse') return;
345
+ const now = Date.now();
346
+ if (now - this.lastTapAt <= NileTimestampBuilder.DOUBLE_TAP_MS) {
347
+ this.lastTapAt = 0;
348
+ this.handleEdit();
349
+ return;
350
+ }
351
+ this.lastTapAt = now;
352
+ };
353
+
354
+ /** Keyboard equivalent of the double-click/double-tap edit affordance. */
355
+ private handleDisplayKeyDown = (event: KeyboardEvent) => {
356
+ if (event.key !== 'Enter' && event.key !== ' ') return;
357
+ event.preventDefault();
358
+ this.handleEdit();
359
+ };
360
+
361
+ private handleEdit = () => {
362
+ if (this.disabled || this.readonly || this.open) return;
363
+ if (this.value === null) {
364
+ this.wallClock = epochToFields(Date.now(), this.timezone);
365
+ }
366
+ this.open = true;
367
+ this.emit('nile-expand', { open: true });
368
+ };
369
+
370
+ private handleDone = () => {
371
+ this.open = false;
372
+ this.commit();
373
+ this.emit('nile-collapse', { open: false });
374
+ };
375
+
376
+ checkValidity(): boolean {
377
+ return this.valueInput?.checkValidity() ?? true;
378
+ }
379
+
380
+ reportValidity(): boolean {
381
+ return this.valueInput?.reportValidity() ?? true;
382
+ }
383
+
384
+ setCustomValidity(message: string): void {
385
+ this.valueInput?.setCustomValidity(message);
386
+ this.formControlController?.updateValidity();
387
+ }
388
+
389
+ getForm(): HTMLFormElement | null {
390
+ return this.formControlController?.getForm() ?? null;
391
+ }
392
+
393
+ private renderField(
394
+ field: TimestampField,
395
+ fieldLabel: string,
396
+ val: number,
397
+ min: number,
398
+ max: number
399
+ ) {
400
+ return html`
401
+ <div class="tsb__field" part="field">
402
+ <nile-input
403
+ type="number"
404
+ exportparts="base:field__base, input:field__input"
405
+ size="medium"
406
+ label=${fieldLabel}
407
+ ?disabled=${this.disabled}
408
+ ?readonly=${this.readonly}
409
+ min=${min}
410
+ max=${max}
411
+ step="1"
412
+ .value=${String(val)}
413
+ @nile-change=${this.onFieldChange(field)}
414
+ ></nile-input>
415
+ </div>
416
+ `;
417
+ }
418
+
419
+ private renderCollapsed() {
420
+ const wc = this.wallClock;
421
+ const editable = !this.disabled && !this.readonly;
422
+ return html`
423
+ <div class="tsb__collapsed" part="display">
424
+ <div
425
+ class=${classMap({
426
+ tsb__display: true,
427
+ 'tsb__display--editable': editable,
428
+ })}
429
+ part="display-content"
430
+ role=${editable ? 'button' : nothing}
431
+ tabindex=${this.disabled ? nothing : '0'}
432
+ aria-labelledby=${this.label ? 'tsb-label tsb-display-value' : nothing}
433
+ aria-readonly=${this.readonly ? 'true' : nothing}
434
+ @dblclick=${this.handleDisplayDoubleClick}
435
+ @pointerup=${this.handleDisplayPointerUp}
436
+ @keydown=${this.handleDisplayKeyDown}
437
+ >
438
+ <span
439
+ id="tsb-display-value"
440
+ class=${classMap({
441
+ tsb__value: true,
442
+ 'tsb__value--empty': !this.hasValue,
443
+ })}
444
+ part="value"
445
+ >
446
+ ${this.hasValue ? formatWallClock(wc) : 'No timestamp set'}
447
+ </span>
448
+ <span class="tsb__tz" part="timezone-label">${this.timezone}</span>
449
+ </div>
450
+ ${editable ? this.renderEditActions() : nothing}
451
+ ${this.copyable && this.readonly && !this.disabled
452
+ ? this.renderCopyAction()
453
+ : nothing}
454
+ </div>
455
+ `;
456
+ }
457
+
458
+ private renderEditActions() {
459
+ return html`
460
+ <div class="tsb__actions" part="actions">
461
+ <nile-button
462
+ variant="secondary-grey"
463
+ ?disabled=${this.disabled}
464
+ @click=${this.handleSetNow}
465
+ >Now</nile-button
466
+ >
467
+ <nile-button
468
+ variant="primary"
469
+ title="Edit"
470
+ ?disabled=${this.disabled}
471
+ @click=${this.handleEdit}
472
+ ><nile-glyph
473
+ name="ng-pencil"
474
+ size="16"
475
+ method="stroke"
476
+ color="currentColor"
477
+ ></nile-glyph
478
+ ></nile-button
479
+ >
480
+ </div>
481
+ `;
482
+ }
483
+
484
+ private renderCopyAction() {
485
+ return html`
486
+ <div class="tsb__actions" part="actions">
487
+ <nile-button
488
+ variant="ghost"
489
+ title=${this.justCopied ? 'Copied' : 'Copy timestamp'}
490
+ aria-label=${this.justCopied ? 'Copied' : 'Copy timestamp'}
491
+ ?disabled=${!this.hasValue}
492
+ @click=${this.handleCopy}
493
+ ><nile-glyph
494
+ name=${this.justCopied ? 'ng-copy-check' : 'ng-copy'}
495
+ size="16"
496
+ method="stroke"
497
+ color="currentColor"
498
+ ></nile-glyph
499
+ ></nile-button>
500
+ </div>
501
+ `;
502
+ }
503
+
504
+ private renderExpanded() {
505
+ const wc = this.wallClock;
506
+ const maxDay = daysInMonth(wc.year, wc.month);
507
+ return html`
508
+ <div class="tsb__expanded" part="editor">
509
+ <div class="tsb__fields" part="fields">
510
+ ${this.renderField(
511
+ TimestampField.Year,
512
+ 'Year',
513
+ wc.year,
514
+ this.minYear,
515
+ this.maxYear
516
+ )}
517
+ ${this.renderField(TimestampField.Month, 'Month', wc.month, 1, 12)}
518
+ ${this.renderField(TimestampField.Day, 'Day', wc.day, 1, maxDay)}
519
+ ${this.renderField(TimestampField.Hour, 'Hour', wc.hour, 0, 23)}
520
+ ${this.renderField(
521
+ TimestampField.Minute,
522
+ 'Minute',
523
+ wc.minute,
524
+ 0,
525
+ 59
526
+ )}
527
+ ${this.renderField(
528
+ TimestampField.Second,
529
+ 'Second',
530
+ wc.second,
531
+ 0,
532
+ 59
533
+ )}
534
+ ${this.renderField(
535
+ TimestampField.Millisecond,
536
+ 'Milliseconds',
537
+ wc.millisecond,
538
+ 0,
539
+ 999
540
+ )}
541
+ </div>
542
+
543
+ <div class="tsb__timezone" part="timezone-select">
544
+ <nile-select
545
+ searchEnabled
546
+ label="Timezone"
547
+ size="medium"
548
+ ?disabled=${this.disabled}
549
+ .value=${this.timezone}
550
+ @nile-change=${this.onTimezoneChange}
551
+ >
552
+ ${this.timezoneList.map(
553
+ tz => html`<nile-option value=${tz}>${tz}</nile-option>`
554
+ )}
555
+ </nile-select>
556
+ </div>
557
+
558
+ <div class="tsb__actions" part="editor-actions">
559
+ <nile-button
560
+ variant="secondary-grey"
561
+ ?disabled=${this.disabled}
562
+ @click=${this.handleStageNow}
563
+ >Set to Now</nile-button
564
+ >
565
+ <nile-button
566
+ variant="secondary-grey"
567
+ ?disabled=${this.disabled}
568
+ @click=${this.handleUseLocalTz}
569
+ >Use Local Timezone</nile-button
570
+ >
571
+ <nile-button
572
+ variant="secondary-grey"
573
+ ?disabled=${this.disabled}
574
+ @click=${this.handleUseUtc}
575
+ >Use UTC</nile-button
576
+ >
577
+ <nile-button
578
+ variant="primary"
579
+ ?disabled=${this.disabled}
580
+ @click=${this.handleDone}
581
+ >Done</nile-button
582
+ >
583
+ </div>
584
+ </div>
585
+ `;
586
+ }
587
+
588
+ render() {
589
+ return html`
590
+ <div
591
+ class="tsb"
592
+ part="base"
593
+ role="group"
594
+ aria-labelledby=${this.label ? 'tsb-label' : nothing}
595
+ >
596
+ ${this.label
597
+ ? html`<label id="tsb-label" class="tsb__label" part="label"
598
+ >${this.label}</label
599
+ >`
600
+ : ''}
601
+ ${this.open && !this.readonly
602
+ ? this.renderExpanded()
603
+ : this.renderCollapsed()}
604
+
605
+ <input
606
+ class="value-input"
607
+ type="text"
608
+ ?required=${this.required}
609
+ ?disabled=${this.disabled}
610
+ .value=${this.valueString() ?? ''}
611
+ tabindex="-1"
612
+ aria-hidden="true"
613
+ />
614
+ </div>
615
+ `;
616
+ }
617
+ }
618
+
619
+ export default NileTimestampBuilder;
620
+
621
+ declare global {
622
+ interface HTMLElementTagNameMap {
623
+ 'nile-timestamp-builder': NileTimestampBuilder;
624
+ }
625
+ }