@aquera/nile-elements 2.0.0 → 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 (238) hide show
  1. package/README.md +11 -0
  2. package/demo/nxtgen-dark.css +2 -2
  3. package/dist/{index-ccff99d6.esm.js → index-0fe57384.esm.js} +1 -1
  4. package/dist/index-f6944844.cjs.js +2 -0
  5. package/dist/{index-e43755f5.cjs.js.map → index-f6944844.cjs.js.map} +1 -1
  6. package/dist/index.cjs.js +1 -1
  7. package/dist/index.esm.js +1 -1
  8. package/dist/index.js +1127 -741
  9. package/dist/nile-code-editor/extensionSetup.cjs.js +1 -1
  10. package/dist/nile-code-editor/extensionSetup.esm.js +1 -1
  11. package/dist/nile-code-editor/index.cjs.js +1 -1
  12. package/dist/nile-code-editor/index.esm.js +1 -1
  13. package/dist/nile-code-editor/nile-code-editor.cjs.js +2 -2
  14. package/dist/nile-code-editor/nile-code-editor.cjs.js.map +1 -1
  15. package/dist/nile-code-editor/nile-code-editor.css.cjs.js +1 -1
  16. package/dist/nile-code-editor/nile-code-editor.css.cjs.js.map +1 -1
  17. package/dist/nile-code-editor/nile-code-editor.css.esm.js +2 -2
  18. package/dist/nile-code-editor/nile-code-editor.esm.js +4 -4
  19. package/dist/nile-code-editor/theme.cjs.js +1 -1
  20. package/dist/nile-code-editor/theme.cjs.js.map +1 -1
  21. package/dist/nile-code-editor/theme.esm.js +1 -1
  22. package/dist/nile-combobox/nile-combobox.css.cjs.js +1 -1
  23. package/dist/nile-combobox/nile-combobox.css.cjs.js.map +1 -1
  24. package/dist/nile-combobox/nile-combobox.css.esm.js +1 -1
  25. package/dist/nile-detail/nile-detail.css.cjs.js +1 -1
  26. package/dist/nile-detail/nile-detail.css.cjs.js.map +1 -1
  27. package/dist/nile-detail/nile-detail.css.esm.js +1 -1
  28. package/dist/nile-side-bar-footer/nile-side-bar-footer.css.cjs.js +1 -1
  29. package/dist/nile-side-bar-footer/nile-side-bar-footer.css.cjs.js.map +1 -1
  30. package/dist/nile-side-bar-footer/nile-side-bar-footer.css.esm.js +3 -3
  31. package/dist/nile-side-bar-footer-item/nile-side-bar-footer-item.css.cjs.js +1 -1
  32. package/dist/nile-side-bar-footer-item/nile-side-bar-footer-item.css.cjs.js.map +1 -1
  33. package/dist/nile-side-bar-footer-item/nile-side-bar-footer-item.css.esm.js +13 -7
  34. package/dist/nile-timestamp-builder/index.cjs.js +2 -0
  35. package/dist/nile-timestamp-builder/index.cjs.js.map +1 -0
  36. package/dist/nile-timestamp-builder/index.esm.js +1 -0
  37. package/dist/nile-timestamp-builder/nile-timestamp-builder-utils.cjs.js +2 -0
  38. package/dist/nile-timestamp-builder/nile-timestamp-builder-utils.cjs.js.map +1 -0
  39. package/dist/nile-timestamp-builder/nile-timestamp-builder-utils.esm.js +1 -0
  40. package/dist/nile-timestamp-builder/nile-timestamp-builder.cjs.js +2 -0
  41. package/dist/nile-timestamp-builder/nile-timestamp-builder.cjs.js.map +1 -0
  42. package/dist/nile-timestamp-builder/nile-timestamp-builder.css.cjs.js +2 -0
  43. package/dist/nile-timestamp-builder/nile-timestamp-builder.css.cjs.js.map +1 -0
  44. package/dist/nile-timestamp-builder/nile-timestamp-builder.css.esm.js +195 -0
  45. package/dist/nile-timestamp-builder/nile-timestamp-builder.esm.js +154 -0
  46. package/dist/nile-timestamp-builder/timezones.cjs.js +2 -0
  47. package/dist/nile-timestamp-builder/timezones.cjs.js.map +1 -0
  48. package/dist/nile-timestamp-builder/timezones.esm.js +1 -0
  49. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.cjs.js +1 -1
  50. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.cjs.js.map +1 -1
  51. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.css.cjs.js +1 -1
  52. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.css.cjs.js.map +1 -1
  53. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.css.esm.js +91 -58
  54. package/dist/nile-wysiwyg-editor/nile-wysiwyg-editor.esm.js +1 -1
  55. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.cjs.js +1 -1
  56. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.cjs.js.map +1 -1
  57. package/dist/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.esm.js +19 -19
  58. package/dist/nile-wysiwyg-editor/ui/bookmark-panel.cjs.js +1 -1
  59. package/dist/nile-wysiwyg-editor/ui/bookmark-panel.cjs.js.map +1 -1
  60. package/dist/nile-wysiwyg-editor/ui/bookmark-panel.esm.js +14 -14
  61. package/dist/nile-wysiwyg-editor/ui/bubble-menu.cjs.js +1 -1
  62. package/dist/nile-wysiwyg-editor/ui/bubble-menu.cjs.js.map +1 -1
  63. package/dist/nile-wysiwyg-editor/ui/bubble-menu.esm.js +13 -13
  64. package/dist/nile-wysiwyg-editor/ui/code-block-menu.cjs.js +1 -1
  65. package/dist/nile-wysiwyg-editor/ui/code-block-menu.cjs.js.map +1 -1
  66. package/dist/nile-wysiwyg-editor/ui/code-block-menu.esm.js +12 -12
  67. package/dist/nile-wysiwyg-editor/ui/embed-menu.cjs.js +1 -1
  68. package/dist/nile-wysiwyg-editor/ui/embed-menu.cjs.js.map +1 -1
  69. package/dist/nile-wysiwyg-editor/ui/embed-menu.esm.js +10 -10
  70. package/dist/nile-wysiwyg-editor/ui/embed-panel.cjs.js +1 -1
  71. package/dist/nile-wysiwyg-editor/ui/embed-panel.cjs.js.map +1 -1
  72. package/dist/nile-wysiwyg-editor/ui/embed-panel.esm.js +4 -4
  73. package/dist/nile-wysiwyg-editor/ui/emoji-picker.cjs.js +1 -1
  74. package/dist/nile-wysiwyg-editor/ui/emoji-picker.cjs.js.map +1 -1
  75. package/dist/nile-wysiwyg-editor/ui/emoji-picker.esm.js +11 -11
  76. package/dist/nile-wysiwyg-editor/ui/find-replace.cjs.js +1 -1
  77. package/dist/nile-wysiwyg-editor/ui/find-replace.cjs.js.map +1 -1
  78. package/dist/nile-wysiwyg-editor/ui/find-replace.esm.js +9 -9
  79. package/dist/nile-wysiwyg-editor/ui/image-menu.cjs.js +1 -1
  80. package/dist/nile-wysiwyg-editor/ui/image-menu.cjs.js.map +1 -1
  81. package/dist/nile-wysiwyg-editor/ui/image-menu.esm.js +11 -11
  82. package/dist/nile-wysiwyg-editor/ui/image-panel.cjs.js +1 -1
  83. package/dist/nile-wysiwyg-editor/ui/image-panel.cjs.js.map +1 -1
  84. package/dist/nile-wysiwyg-editor/ui/image-panel.esm.js +6 -6
  85. package/dist/nile-wysiwyg-editor/ui/insert-menu.cjs.js +1 -1
  86. package/dist/nile-wysiwyg-editor/ui/insert-menu.cjs.js.map +1 -1
  87. package/dist/nile-wysiwyg-editor/ui/insert-menu.esm.js +14 -14
  88. package/dist/nile-wysiwyg-editor/ui/link-editor.cjs.js +1 -1
  89. package/dist/nile-wysiwyg-editor/ui/link-editor.cjs.js.map +1 -1
  90. package/dist/nile-wysiwyg-editor/ui/link-editor.esm.js +16 -16
  91. package/dist/nile-wysiwyg-editor/ui/markdown-panel.cjs.js +1 -1
  92. package/dist/nile-wysiwyg-editor/ui/markdown-panel.cjs.js.map +1 -1
  93. package/dist/nile-wysiwyg-editor/ui/markdown-panel.esm.js +8 -8
  94. package/dist/nile-wysiwyg-editor/ui/math-panel.cjs.js +1 -1
  95. package/dist/nile-wysiwyg-editor/ui/math-panel.cjs.js.map +1 -1
  96. package/dist/nile-wysiwyg-editor/ui/math-panel.esm.js +4 -4
  97. package/dist/nile-wysiwyg-editor/ui/shortcut-help.cjs.js +1 -1
  98. package/dist/nile-wysiwyg-editor/ui/shortcut-help.cjs.js.map +1 -1
  99. package/dist/nile-wysiwyg-editor/ui/shortcut-help.esm.js +16 -16
  100. package/dist/nile-wysiwyg-editor/ui/source-view.cjs.js +1 -1
  101. package/dist/nile-wysiwyg-editor/ui/source-view.cjs.js.map +1 -1
  102. package/dist/nile-wysiwyg-editor/ui/source-view.esm.js +29 -29
  103. package/dist/nile-wysiwyg-editor/ui/special-chars.cjs.js +1 -1
  104. package/dist/nile-wysiwyg-editor/ui/special-chars.cjs.js.map +1 -1
  105. package/dist/nile-wysiwyg-editor/ui/special-chars.esm.js +8 -8
  106. package/dist/nile-wysiwyg-editor/ui/suggest-list.cjs.js +1 -1
  107. package/dist/nile-wysiwyg-editor/ui/suggest-list.cjs.js.map +1 -1
  108. package/dist/nile-wysiwyg-editor/ui/suggest-list.esm.js +9 -9
  109. package/dist/nile-wysiwyg-editor/ui/table-grid-picker.cjs.js +1 -1
  110. package/dist/nile-wysiwyg-editor/ui/table-grid-picker.cjs.js.map +1 -1
  111. package/dist/nile-wysiwyg-editor/ui/table-grid-picker.esm.js +23 -23
  112. package/dist/nile-wysiwyg-editor/ui/table-menu.cjs.js +1 -1
  113. package/dist/nile-wysiwyg-editor/ui/table-menu.cjs.js.map +1 -1
  114. package/dist/nile-wysiwyg-editor/ui/table-menu.esm.js +7 -7
  115. package/dist/src/index.d.ts +1 -0
  116. package/dist/src/index.js +1 -0
  117. package/dist/src/index.js.map +1 -1
  118. package/dist/src/nile-code-editor/nile-code-editor.css.js +2 -2
  119. package/dist/src/nile-code-editor/nile-code-editor.css.js.map +1 -1
  120. package/dist/src/nile-code-editor/nile-code-editor.d.ts +15 -0
  121. package/dist/src/nile-code-editor/nile-code-editor.js +65 -6
  122. package/dist/src/nile-code-editor/nile-code-editor.js.map +1 -1
  123. package/dist/src/nile-code-editor/theme.d.ts +6 -117
  124. package/dist/src/nile-code-editor/theme.js +100 -147
  125. package/dist/src/nile-code-editor/theme.js.map +1 -1
  126. package/dist/src/nile-combobox/nile-combobox.css.js +1 -1
  127. package/dist/src/nile-combobox/nile-combobox.css.js.map +1 -1
  128. package/dist/src/nile-detail/nile-detail.css.js +1 -1
  129. package/dist/src/nile-detail/nile-detail.css.js.map +1 -1
  130. package/dist/src/nile-side-bar-footer/nile-side-bar-footer.css.js +3 -3
  131. package/dist/src/nile-side-bar-footer/nile-side-bar-footer.css.js.map +1 -1
  132. package/dist/src/nile-side-bar-footer-item/nile-side-bar-footer-item.css.js +11 -5
  133. package/dist/src/nile-side-bar-footer-item/nile-side-bar-footer-item.css.js.map +1 -1
  134. package/dist/src/nile-timestamp-builder/index.d.ts +1 -0
  135. package/dist/src/nile-timestamp-builder/index.js +2 -0
  136. package/dist/src/nile-timestamp-builder/index.js.map +1 -0
  137. package/dist/src/nile-timestamp-builder/nile-timestamp-builder-utils.d.ts +54 -0
  138. package/dist/src/nile-timestamp-builder/nile-timestamp-builder-utils.js +254 -0
  139. package/dist/src/nile-timestamp-builder/nile-timestamp-builder-utils.js.map +1 -0
  140. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.css.d.ts +7 -0
  141. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.css.js +203 -0
  142. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.css.js.map +1 -0
  143. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.d.ts +94 -0
  144. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.js +578 -0
  145. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.js.map +1 -0
  146. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.test.d.ts +1 -0
  147. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.test.js +890 -0
  148. package/dist/src/nile-timestamp-builder/nile-timestamp-builder.test.js.map +1 -0
  149. package/dist/src/nile-timestamp-builder/timezones.d.ts +8 -0
  150. package/dist/src/nile-timestamp-builder/timezones.js +357 -0
  151. package/dist/src/nile-timestamp-builder/timezones.js.map +1 -0
  152. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.css.js +91 -58
  153. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.css.js.map +1 -1
  154. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.d.ts +2 -1
  155. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.js +23 -1
  156. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-editor.js.map +1 -1
  157. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.js +19 -19
  158. package/dist/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.js.map +1 -1
  159. package/dist/src/nile-wysiwyg-editor/ui/bookmark-panel.js +10 -10
  160. package/dist/src/nile-wysiwyg-editor/ui/bookmark-panel.js.map +1 -1
  161. package/dist/src/nile-wysiwyg-editor/ui/bubble-menu.js +7 -7
  162. package/dist/src/nile-wysiwyg-editor/ui/bubble-menu.js.map +1 -1
  163. package/dist/src/nile-wysiwyg-editor/ui/code-block-menu.js +5 -5
  164. package/dist/src/nile-wysiwyg-editor/ui/code-block-menu.js.map +1 -1
  165. package/dist/src/nile-wysiwyg-editor/ui/embed-menu.js +6 -6
  166. package/dist/src/nile-wysiwyg-editor/ui/embed-menu.js.map +1 -1
  167. package/dist/src/nile-wysiwyg-editor/ui/embed-panel.js +4 -4
  168. package/dist/src/nile-wysiwyg-editor/ui/embed-panel.js.map +1 -1
  169. package/dist/src/nile-wysiwyg-editor/ui/emoji-picker.js +6 -6
  170. package/dist/src/nile-wysiwyg-editor/ui/emoji-picker.js.map +1 -1
  171. package/dist/src/nile-wysiwyg-editor/ui/find-replace.js +7 -7
  172. package/dist/src/nile-wysiwyg-editor/ui/find-replace.js.map +1 -1
  173. package/dist/src/nile-wysiwyg-editor/ui/image-menu.js +6 -6
  174. package/dist/src/nile-wysiwyg-editor/ui/image-menu.js.map +1 -1
  175. package/dist/src/nile-wysiwyg-editor/ui/image-panel.js +6 -6
  176. package/dist/src/nile-wysiwyg-editor/ui/image-panel.js.map +1 -1
  177. package/dist/src/nile-wysiwyg-editor/ui/insert-menu.js +7 -7
  178. package/dist/src/nile-wysiwyg-editor/ui/insert-menu.js.map +1 -1
  179. package/dist/src/nile-wysiwyg-editor/ui/link-editor.js +9 -9
  180. package/dist/src/nile-wysiwyg-editor/ui/link-editor.js.map +1 -1
  181. package/dist/src/nile-wysiwyg-editor/ui/markdown-panel.js +8 -8
  182. package/dist/src/nile-wysiwyg-editor/ui/markdown-panel.js.map +1 -1
  183. package/dist/src/nile-wysiwyg-editor/ui/math-panel.js +4 -4
  184. package/dist/src/nile-wysiwyg-editor/ui/math-panel.js.map +1 -1
  185. package/dist/src/nile-wysiwyg-editor/ui/shortcut-help.js +9 -9
  186. package/dist/src/nile-wysiwyg-editor/ui/shortcut-help.js.map +1 -1
  187. package/dist/src/nile-wysiwyg-editor/ui/source-view.js +24 -24
  188. package/dist/src/nile-wysiwyg-editor/ui/source-view.js.map +1 -1
  189. package/dist/src/nile-wysiwyg-editor/ui/special-chars.js +4 -4
  190. package/dist/src/nile-wysiwyg-editor/ui/special-chars.js.map +1 -1
  191. package/dist/src/nile-wysiwyg-editor/ui/suggest-list.js +7 -7
  192. package/dist/src/nile-wysiwyg-editor/ui/suggest-list.js.map +1 -1
  193. package/dist/src/nile-wysiwyg-editor/ui/table-grid-picker.js +16 -16
  194. package/dist/src/nile-wysiwyg-editor/ui/table-grid-picker.js.map +1 -1
  195. package/dist/src/nile-wysiwyg-editor/ui/table-menu.js +4 -4
  196. package/dist/src/nile-wysiwyg-editor/ui/table-menu.js.map +1 -1
  197. package/dist/src/version.js +2 -2
  198. package/dist/src/version.js.map +1 -1
  199. package/dist/tsconfig.tsbuildinfo +1 -1
  200. package/package.json +1 -1
  201. package/src/index.ts +1 -0
  202. package/src/nile-code-editor/nile-code-editor.css.ts +2 -2
  203. package/src/nile-code-editor/nile-code-editor.ts +68 -5
  204. package/src/nile-code-editor/theme.ts +129 -162
  205. package/src/nile-combobox/nile-combobox.css.ts +1 -1
  206. package/src/nile-detail/nile-detail.css.ts +1 -1
  207. package/src/nile-side-bar-footer/nile-side-bar-footer.css.ts +3 -3
  208. package/src/nile-side-bar-footer-item/nile-side-bar-footer-item.css.ts +11 -5
  209. package/src/nile-timestamp-builder/index.ts +1 -0
  210. package/src/nile-timestamp-builder/nile-timestamp-builder-utils.ts +321 -0
  211. package/src/nile-timestamp-builder/nile-timestamp-builder.css.ts +204 -0
  212. package/src/nile-timestamp-builder/nile-timestamp-builder.test.ts +1089 -0
  213. package/src/nile-timestamp-builder/nile-timestamp-builder.ts +625 -0
  214. package/src/nile-timestamp-builder/timezones.ts +356 -0
  215. package/src/nile-wysiwyg-editor/nile-wysiwyg-editor.css.ts +91 -58
  216. package/src/nile-wysiwyg-editor/nile-wysiwyg-editor.ts +21 -1
  217. package/src/nile-wysiwyg-editor/nile-wysiwyg-toolbar/nile-wysiwyg-toolbar.css.ts +19 -19
  218. package/src/nile-wysiwyg-editor/ui/bookmark-panel.ts +10 -10
  219. package/src/nile-wysiwyg-editor/ui/bubble-menu.ts +7 -7
  220. package/src/nile-wysiwyg-editor/ui/code-block-menu.ts +5 -5
  221. package/src/nile-wysiwyg-editor/ui/embed-menu.ts +6 -6
  222. package/src/nile-wysiwyg-editor/ui/embed-panel.ts +4 -4
  223. package/src/nile-wysiwyg-editor/ui/emoji-picker.ts +6 -6
  224. package/src/nile-wysiwyg-editor/ui/find-replace.ts +7 -7
  225. package/src/nile-wysiwyg-editor/ui/image-menu.ts +6 -6
  226. package/src/nile-wysiwyg-editor/ui/image-panel.ts +6 -6
  227. package/src/nile-wysiwyg-editor/ui/insert-menu.ts +7 -7
  228. package/src/nile-wysiwyg-editor/ui/link-editor.ts +9 -9
  229. package/src/nile-wysiwyg-editor/ui/markdown-panel.ts +8 -8
  230. package/src/nile-wysiwyg-editor/ui/math-panel.ts +4 -4
  231. package/src/nile-wysiwyg-editor/ui/shortcut-help.ts +9 -9
  232. package/src/nile-wysiwyg-editor/ui/source-view.ts +24 -24
  233. package/src/nile-wysiwyg-editor/ui/special-chars.ts +4 -4
  234. package/src/nile-wysiwyg-editor/ui/suggest-list.ts +7 -7
  235. package/src/nile-wysiwyg-editor/ui/table-grid-picker.ts +16 -16
  236. package/src/nile-wysiwyg-editor/ui/table-menu.ts +4 -4
  237. package/vscode-html-custom-data.json +94 -2
  238. package/dist/index-e43755f5.cjs.js +0 -2
@@ -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
+ }