@honua/app-platform 0.1.1-beta.0 → 0.1.4-beta.0

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 (383) hide show
  1. package/collaboration/client.d.ts.map +1 -1
  2. package/connect-url-safety.d.ts +53 -0
  3. package/connect-url-safety.js +189 -0
  4. package/contract/discovery.d.ts +34 -0
  5. package/contract/discovery.d.ts.map +1 -1
  6. package/contract/index.d.ts +7 -5
  7. package/contract/index.d.ts.map +1 -1
  8. package/contract/index.js +3 -2
  9. package/contract/pmtiles.d.ts +10 -1
  10. package/contract/pmtiles.d.ts.map +1 -1
  11. package/contract/pmtiles.js +10 -2
  12. package/contract/protocol-module.d.ts +84 -9
  13. package/contract/protocol-module.d.ts.map +1 -1
  14. package/contract/query-filter-fes.d.ts +18 -0
  15. package/contract/query-filter-fes.d.ts.map +1 -0
  16. package/contract/query-filter-fes.js +122 -0
  17. package/contract/query-filter.d.ts +300 -0
  18. package/contract/query-filter.d.ts.map +1 -0
  19. package/contract/query-filter.js +883 -0
  20. package/contract/schema.d.ts +12 -0
  21. package/contract/schema.d.ts.map +1 -1
  22. package/contract/schema.js +34 -0
  23. package/contract/source-capability-support.d.ts.map +1 -1
  24. package/contract/source-capability-support.js +15 -3
  25. package/contract/source.d.ts +45 -25
  26. package/contract/source.d.ts.map +1 -1
  27. package/contract/source.js +719 -479
  28. package/contract/types.d.ts +87 -18
  29. package/contract/types.d.ts.map +1 -1
  30. package/contract/widget-source.d.ts.map +1 -1
  31. package/controls/basemap-switcher.d.ts +19 -11
  32. package/controls/basemap-switcher.d.ts.map +1 -1
  33. package/controls/basemap-switcher.js +58 -25
  34. package/controls/catalog.d.ts +105 -0
  35. package/controls/catalog.d.ts.map +1 -0
  36. package/controls/catalog.js +341 -0
  37. package/controls/element-utils.d.ts +9 -0
  38. package/controls/element-utils.d.ts.map +1 -1
  39. package/controls/element-utils.js +17 -0
  40. package/controls/index.d.ts +29 -8
  41. package/controls/index.d.ts.map +1 -1
  42. package/controls/index.js +26 -8
  43. package/controls/layer-list.d.ts +13 -5
  44. package/controls/layer-list.d.ts.map +1 -1
  45. package/controls/layer-list.js +72 -15
  46. package/controls/legend.d.ts +32 -5
  47. package/controls/legend.d.ts.map +1 -1
  48. package/controls/legend.js +95 -25
  49. package/controls/qualification.d.ts +152 -0
  50. package/controls/qualification.d.ts.map +1 -0
  51. package/controls/qualification.js +1265 -0
  52. package/controls/registry.d.ts +104 -0
  53. package/controls/registry.d.ts.map +1 -0
  54. package/controls/registry.js +150 -0
  55. package/controls/swipe-control.d.ts +6 -1
  56. package/controls/swipe-control.d.ts.map +1 -1
  57. package/controls/swipe-control.js +82 -20
  58. package/core/client.d.ts +45 -21
  59. package/core/client.d.ts.map +1 -1
  60. package/core/client.js +224 -46
  61. package/core/error-classifications.d.ts +6 -0
  62. package/core/error-classifications.d.ts.map +1 -1
  63. package/core/error-classifications.js +6 -0
  64. package/core/error-code-registry.d.ts +6 -0
  65. package/core/error-code-registry.d.ts.map +1 -1
  66. package/core/error-code-registry.js +6 -0
  67. package/core/error-envelope.js +2 -0
  68. package/core/errors.d.ts +1 -1
  69. package/core/errors.js +1 -1
  70. package/core/geoparquet-sql.d.ts +68 -4
  71. package/core/geoparquet-sql.d.ts.map +1 -1
  72. package/core/geoparquet-sql.js +325 -8
  73. package/core/odata-write-codec.d.ts.map +1 -1
  74. package/core/odata-write-codec.js +0 -1
  75. package/core/odata.d.ts +6 -0
  76. package/core/odata.d.ts.map +1 -1
  77. package/core/odata.js +6 -1
  78. package/core/ogc-processes.d.ts +182 -25
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +602 -30
  81. package/core/process-runner.d.ts +8 -1
  82. package/core/process-runner.d.ts.map +1 -1
  83. package/core/process-runner.js +1 -0
  84. package/core/protocol-transport.d.ts +2 -0
  85. package/core/protocol-transport.d.ts.map +1 -1
  86. package/core/stac.d.ts +31 -0
  87. package/core/stac.d.ts.map +1 -1
  88. package/core/stac.js +136 -19
  89. package/core/surfaces.d.ts.map +1 -1
  90. package/core/surfaces.js +273 -68
  91. package/core/types.d.ts +123 -9
  92. package/core/types.d.ts.map +1 -1
  93. package/core/wfs-axis-order.d.ts +18 -0
  94. package/core/wfs-axis-order.d.ts.map +1 -0
  95. package/core/wfs-axis-order.js +108 -0
  96. package/core/wfs-capabilities.d.ts +65 -24
  97. package/core/wfs-capabilities.d.ts.map +1 -1
  98. package/core/wfs-capabilities.js +237 -27
  99. package/core/wfs-filter.d.ts +26 -3
  100. package/core/wfs-filter.d.ts.map +1 -1
  101. package/core/wfs-filter.js +48 -53
  102. package/core/wfs-protocol-error.d.ts +12 -0
  103. package/core/wfs-protocol-error.d.ts.map +1 -0
  104. package/core/wfs-protocol-error.js +13 -0
  105. package/core/wfs.d.ts +53 -1
  106. package/core/wfs.d.ts.map +1 -1
  107. package/core/wfs.js +251 -29
  108. package/core/wms-feature-info.d.ts +92 -0
  109. package/core/wms-feature-info.d.ts.map +1 -0
  110. package/core/wms-feature-info.js +285 -0
  111. package/core/wms.d.ts +9 -0
  112. package/core/wms.d.ts.map +1 -1
  113. package/core/wms.js +17 -5
  114. package/esri-compat/event-bus.d.ts +20 -0
  115. package/esri-compat/event-bus.d.ts.map +1 -1
  116. package/esri-compat/feature-filter.d.ts +46 -1
  117. package/esri-compat/feature-filter.d.ts.map +1 -1
  118. package/esri-compat/feature-filter.js +49 -0
  119. package/esri-compat/geojson-layer.d.ts +4 -2
  120. package/esri-compat/geojson-layer.d.ts.map +1 -1
  121. package/esri-compat/geojson-layer.js +4 -2
  122. package/esri-compat/identity-manager.js +2 -2
  123. package/esri-compat/imagery-layer.d.ts +2 -1
  124. package/esri-compat/imagery-layer.d.ts.map +1 -1
  125. package/esri-compat/imagery-layer.js +2 -1
  126. package/esri-compat/layer-list.d.ts +8 -1
  127. package/esri-compat/layer-list.d.ts.map +1 -1
  128. package/esri-compat/layer-list.js +11 -4
  129. package/esri-compat/legend.d.ts.map +1 -1
  130. package/esri-compat/legend.js +1 -1
  131. package/esri-compat/locator.d.ts +297 -0
  132. package/esri-compat/locator.d.ts.map +1 -0
  133. package/esri-compat/locator.js +579 -0
  134. package/esri-compat/map-image-layer.d.ts.map +1 -1
  135. package/esri-compat/map-view.d.ts.map +1 -1
  136. package/esri-compat/map-view.js +1 -1
  137. package/esri-compat/scene-view.d.ts.map +1 -1
  138. package/esri-compat/time-slider.d.ts +29 -0
  139. package/esri-compat/time-slider.d.ts.map +1 -1
  140. package/esri-compat/time-slider.js +117 -0
  141. package/esri-compat/wfs-layer.d.ts +2 -1
  142. package/esri-compat/wfs-layer.d.ts.map +1 -1
  143. package/esri-compat/wfs-layer.js +2 -1
  144. package/esri-compat/widget-host.d.ts +42 -6
  145. package/esri-compat/widget-host.d.ts.map +1 -1
  146. package/esri-compat/widget-host.js +99 -10
  147. package/generated-app/projection.d.ts.map +1 -1
  148. package/generated-app/runtime.d.ts.map +1 -1
  149. package/map/data-to-map-bridge.d.ts +1 -1
  150. package/map/data-to-map-bridge.d.ts.map +1 -1
  151. package/map/data-to-map-bridge.js +1 -2
  152. package/map/honua-map.d.ts.map +1 -1
  153. package/map/honua-map.js +1 -1
  154. package/map/index.d.ts +2 -0
  155. package/map/index.d.ts.map +1 -1
  156. package/map/index.js +2 -0
  157. package/map/realtime-reconciliation-adapter.d.ts +98 -0
  158. package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
  159. package/map/realtime-reconciliation-adapter.js +84 -0
  160. package/map/temporal-playback.d.ts +25 -0
  161. package/map/temporal-playback.d.ts.map +1 -1
  162. package/map/temporal-playback.js +29 -1
  163. package/operator/workspace/workspace.d.ts.map +1 -1
  164. package/operator/workspace/workspace.js +0 -2
  165. package/package.json +4 -3
  166. package/query-planner/canonical-filter.d.ts +30 -0
  167. package/query-planner/canonical-filter.d.ts.map +1 -0
  168. package/query-planner/canonical-filter.js +30 -0
  169. package/query-planner/columnar-plan.d.ts +63 -0
  170. package/query-planner/columnar-plan.d.ts.map +1 -0
  171. package/query-planner/columnar-plan.js +94 -0
  172. package/query-planner/diagnostics.d.ts +3 -3
  173. package/query-planner/diagnostics.d.ts.map +1 -1
  174. package/query-planner/diagnostics.js +17 -4
  175. package/query-planner/duckdb.d.ts.map +1 -1
  176. package/query-planner/duckdb.js +16 -2
  177. package/query-planner/execution-context.d.ts.map +1 -1
  178. package/query-planner/execution-context.js +3 -1
  179. package/query-planner/executor.d.ts.map +1 -1
  180. package/query-planner/executor.js +7 -1
  181. package/query-planner/geoservices.d.ts.map +1 -1
  182. package/query-planner/geoservices.js +24 -5
  183. package/query-planner/grpc.d.ts.map +1 -1
  184. package/query-planner/grpc.js +29 -5
  185. package/query-planner/index.d.ts +20 -5
  186. package/query-planner/index.d.ts.map +1 -1
  187. package/query-planner/index.js +16 -3
  188. package/query-planner/ir.d.ts +6 -1
  189. package/query-planner/ir.d.ts.map +1 -1
  190. package/query-planner/ir.js +63 -1
  191. package/query-planner/odata-protocol-module.d.ts +54 -0
  192. package/query-planner/odata-protocol-module.d.ts.map +1 -0
  193. package/query-planner/odata-protocol-module.js +199 -0
  194. package/query-planner/odata-v1.d.ts +23 -0
  195. package/query-planner/odata-v1.d.ts.map +1 -0
  196. package/query-planner/odata-v1.js +202 -0
  197. package/query-planner/odata.d.ts +0 -3
  198. package/query-planner/odata.d.ts.map +1 -1
  199. package/query-planner/odata.js +0 -143
  200. package/query-planner/ogc-features.d.ts.map +1 -1
  201. package/query-planner/ogc-features.js +23 -1
  202. package/query-planner/planner.d.ts +2 -2
  203. package/query-planner/planner.d.ts.map +1 -1
  204. package/query-planner/planner.js +146 -27
  205. package/query-planner/representation.d.ts +59 -0
  206. package/query-planner/representation.d.ts.map +1 -0
  207. package/query-planner/representation.js +140 -0
  208. package/query-planner/types.d.ts +153 -3
  209. package/query-planner/types.d.ts.map +1 -1
  210. package/query-planner/wfs-protocol-module.d.ts +57 -0
  211. package/query-planner/wfs-protocol-module.d.ts.map +1 -0
  212. package/query-planner/wfs-protocol-module.js +731 -0
  213. package/query-planner/wfs-v1.d.ts +41 -0
  214. package/query-planner/wfs-v1.d.ts.map +1 -0
  215. package/query-planner/wfs-v1.js +366 -0
  216. package/query-planner/wfs.d.ts +1 -3
  217. package/query-planner/wfs.d.ts.map +1 -1
  218. package/query-planner/wfs.js +1 -70
  219. package/realtime/checkpoint-store.d.ts +206 -0
  220. package/realtime/checkpoint-store.d.ts.map +1 -0
  221. package/realtime/checkpoint-store.js +504 -0
  222. package/realtime/honua-server.d.ts +8 -0
  223. package/realtime/honua-server.d.ts.map +1 -1
  224. package/realtime/honua-server.js +123 -14
  225. package/realtime/index.d.ts +4 -0
  226. package/realtime/index.d.ts.map +1 -1
  227. package/realtime/index.js +2 -0
  228. package/realtime/reconciliation.d.ts +245 -0
  229. package/realtime/reconciliation.d.ts.map +1 -0
  230. package/realtime/reconciliation.js +377 -0
  231. package/realtime/resumable-transport.d.ts.map +1 -1
  232. package/realtime/sse.d.ts.map +1 -1
  233. package/realtime/websocket.d.ts.map +1 -1
  234. package/realtime/wire.d.ts +2 -1
  235. package/realtime/wire.d.ts.map +1 -1
  236. package/realtime/wire.js +24 -8
  237. package/replica-sync/errors.d.ts +19 -1
  238. package/replica-sync/errors.d.ts.map +1 -1
  239. package/replica-sync/errors.js +16 -0
  240. package/replica-sync/geoservices.d.ts +260 -0
  241. package/replica-sync/geoservices.d.ts.map +1 -0
  242. package/replica-sync/geoservices.js +1093 -0
  243. package/replica-sync/index.d.ts +5 -1
  244. package/replica-sync/index.d.ts.map +1 -1
  245. package/replica-sync/index.js +3 -1
  246. package/replica-sync/transport-conformance.d.ts +58 -0
  247. package/replica-sync/transport-conformance.d.ts.map +1 -0
  248. package/replica-sync/transport-conformance.js +265 -0
  249. package/replica-sync/types.d.ts +22 -0
  250. package/replica-sync/types.d.ts.map +1 -1
  251. package/runtime/index.d.ts +6 -2
  252. package/runtime/index.d.ts.map +1 -1
  253. package/runtime/index.js +3 -1
  254. package/runtime/load-package.d.ts +24 -0
  255. package/runtime/load-package.d.ts.map +1 -1
  256. package/runtime/load-package.js +45 -1
  257. package/runtime/maplibre-renderer.d.ts +11 -0
  258. package/runtime/maplibre-renderer.d.ts.map +1 -1
  259. package/runtime/maplibre-renderer.js +32 -7
  260. package/runtime/offline-region-protocol.d.ts +73 -0
  261. package/runtime/offline-region-protocol.d.ts.map +1 -0
  262. package/runtime/offline-region-protocol.js +63 -0
  263. package/runtime/offline-region-style.d.ts +78 -0
  264. package/runtime/offline-region-style.d.ts.map +1 -0
  265. package/runtime/offline-region-style.js +147 -0
  266. package/runtime/pmtiles-protocol.d.ts +7 -8
  267. package/runtime/pmtiles-protocol.d.ts.map +1 -1
  268. package/runtime/pmtiles-protocol.js +19 -66
  269. package/runtime/protocol-registry.d.ts +55 -0
  270. package/runtime/protocol-registry.d.ts.map +1 -0
  271. package/runtime/protocol-registry.js +103 -0
  272. package/runtime/runtime.d.ts +2 -0
  273. package/runtime/runtime.d.ts.map +1 -1
  274. package/runtime/runtime.js +6 -1
  275. package/scene-workspace/analysis-widgets.d.ts +4 -2
  276. package/scene-workspace/analysis-widgets.d.ts.map +1 -1
  277. package/scene-workspace/analysis-widgets.js +4 -2
  278. package/scene-workspace/camera-correspondence.d.ts +151 -0
  279. package/scene-workspace/camera-correspondence.d.ts.map +1 -0
  280. package/scene-workspace/camera-correspondence.js +220 -0
  281. package/scene-workspace/cesium-adapter.d.ts +230 -29
  282. package/scene-workspace/cesium-adapter.d.ts.map +1 -1
  283. package/scene-workspace/cesium-adapter.js +741 -56
  284. package/scene-workspace/cesium-mount.d.ts +205 -0
  285. package/scene-workspace/cesium-mount.d.ts.map +1 -0
  286. package/scene-workspace/cesium-mount.js +392 -0
  287. package/scene-workspace/cesium-scene-owner.d.ts +160 -0
  288. package/scene-workspace/cesium-scene-owner.d.ts.map +1 -0
  289. package/scene-workspace/cesium-scene-owner.js +205 -0
  290. package/scene-workspace/cesium-state-sync.d.ts +101 -0
  291. package/scene-workspace/cesium-state-sync.d.ts.map +1 -0
  292. package/scene-workspace/cesium-state-sync.js +432 -0
  293. package/scene-workspace/cesium-time.d.ts +346 -0
  294. package/scene-workspace/cesium-time.d.ts.map +1 -0
  295. package/scene-workspace/cesium-time.js +403 -0
  296. package/scene-workspace/index.d.ts +42 -10
  297. package/scene-workspace/index.d.ts.map +1 -1
  298. package/scene-workspace/index.js +32 -7
  299. package/scene-workspace/maplibre-state-sync.d.ts +112 -0
  300. package/scene-workspace/maplibre-state-sync.d.ts.map +1 -0
  301. package/scene-workspace/maplibre-state-sync.js +400 -0
  302. package/scene-workspace/primitives.d.ts +237 -3
  303. package/scene-workspace/primitives.d.ts.map +1 -1
  304. package/scene-workspace/primitives.js +1087 -17
  305. package/scene-workspace/scene-discovery.d.ts +5 -2
  306. package/scene-workspace/scene-discovery.d.ts.map +1 -1
  307. package/scene-workspace/scene-discovery.js +5 -2
  308. package/scene-workspace/scene-view.d.ts +5 -2
  309. package/scene-workspace/scene-view.d.ts.map +1 -1
  310. package/scene-workspace/scene-view.js +5 -2
  311. package/scene-workspace/source-to-cesium.d.ts +141 -8
  312. package/scene-workspace/source-to-cesium.d.ts.map +1 -1
  313. package/scene-workspace/source-to-cesium.js +497 -72
  314. package/scene-workspace/state-sync-port-kit.d.ts +156 -0
  315. package/scene-workspace/state-sync-port-kit.d.ts.map +1 -0
  316. package/scene-workspace/state-sync-port-kit.js +319 -0
  317. package/scene-workspace/state-sync.d.ts +34 -1
  318. package/scene-workspace/state-sync.d.ts.map +1 -1
  319. package/scene-workspace/state-sync.js +48 -0
  320. package/scene-workspace/types.d.ts +8 -0
  321. package/scene-workspace/types.d.ts.map +1 -1
  322. package/scene-workspace/types.js +2 -0
  323. package/scene-workspace/workspace.d.ts +12 -1
  324. package/scene-workspace/workspace.d.ts.map +1 -1
  325. package/scene-workspace/workspace.js +13 -0
  326. package/studio/index.d.ts +24 -7
  327. package/studio/index.d.ts.map +1 -1
  328. package/studio/index.js +21 -7
  329. package/studio/lifecycle-client.d.ts +164 -0
  330. package/studio/lifecycle-client.d.ts.map +1 -0
  331. package/studio/lifecycle-client.js +299 -0
  332. package/studio/lifecycle-errors.d.ts +91 -0
  333. package/studio/lifecycle-errors.d.ts.map +1 -0
  334. package/studio/lifecycle-errors.js +77 -0
  335. package/studio/lifecycle-types.d.ts +321 -0
  336. package/studio/lifecycle-types.d.ts.map +1 -0
  337. package/studio/lifecycle-types.js +22 -0
  338. package/web-components/controller.d.ts.map +1 -1
  339. package/web-components/csp-styles.d.ts +6 -0
  340. package/web-components/csp-styles.d.ts.map +1 -0
  341. package/web-components/csp-styles.js +53 -0
  342. package/web-components/elements.d.ts +158 -2
  343. package/web-components/elements.d.ts.map +1 -1
  344. package/web-components/elements.js +1302 -215
  345. package/web-components/export-redaction.d.ts +252 -0
  346. package/web-components/export-redaction.d.ts.map +1 -0
  347. package/web-components/export-redaction.js +588 -0
  348. package/web-components/export.d.ts +417 -0
  349. package/web-components/export.d.ts.map +1 -0
  350. package/web-components/export.js +719 -0
  351. package/web-components/feature-editor-model.d.ts +265 -0
  352. package/web-components/feature-editor-model.d.ts.map +1 -0
  353. package/web-components/feature-editor-model.js +438 -0
  354. package/web-components/feature-editor-sketch.d.ts +125 -0
  355. package/web-components/feature-editor-sketch.d.ts.map +1 -0
  356. package/web-components/feature-editor-sketch.js +194 -0
  357. package/web-components/feature-editor-workflow.d.ts +286 -0
  358. package/web-components/feature-editor-workflow.d.ts.map +1 -0
  359. package/web-components/feature-editor-workflow.js +889 -0
  360. package/web-components/feature-editor.d.ts +83 -0
  361. package/web-components/feature-editor.d.ts.map +1 -0
  362. package/web-components/feature-editor.js +829 -0
  363. package/web-components/feature-table-engine.d.ts +465 -0
  364. package/web-components/feature-table-engine.d.ts.map +1 -0
  365. package/web-components/feature-table-engine.js +1360 -0
  366. package/web-components/feature-table-view.d.ts +95 -0
  367. package/web-components/feature-table-view.d.ts.map +1 -0
  368. package/web-components/feature-table-view.js +343 -0
  369. package/web-components/index.d.ts +34 -3
  370. package/web-components/index.d.ts.map +1 -1
  371. package/web-components/index.js +35 -2
  372. package/web-components/maplibre-renderer.d.ts.map +1 -1
  373. package/web-components/maplibre-renderer.js +13 -4
  374. package/web-components/measurement.d.ts +3 -1
  375. package/web-components/measurement.d.ts.map +1 -1
  376. package/web-components/measurement.js +62 -18
  377. package/web-components/time-slider.d.ts +122 -0
  378. package/web-components/time-slider.d.ts.map +1 -0
  379. package/web-components/time-slider.js +740 -0
  380. package/web-components/types.d.ts +310 -0
  381. package/web-components/types.d.ts.map +1 -1
  382. package/webmap/convert-label.js +0 -1
  383. package/webmap/convert-symbol.d.ts.map +1 -1
@@ -0,0 +1,740 @@
1
+ /**
2
+ * `<honua-time-slider>` — the time-slider UI over the existing temporal
3
+ * playback controller (issue #959, epic #486 "Esri Widget Cliff").
4
+ *
5
+ * Temporal playback has been headless since issue #497: `createTemporalPlayback`
6
+ * (`@honua/sdk-js/map`) owns the window, the frame timer, and the filter sinks
7
+ * that drive the map. This element is the missing *view* over that controller —
8
+ * it introduces no second temporal model, keeps no window state of its own, and
9
+ * never fetches or caches data. Every user gesture becomes one controller call,
10
+ * and every rendered value comes back from the controller's own `tick` /
11
+ * `play` / `pause` / `end` events. Layered over a realtime feed the controller
12
+ * keeps issue #393's snapshot-plus-delta semantics untouched, because the
13
+ * element adds nothing between the controller and its sinks.
14
+ *
15
+ * ## Wiring
16
+ * ```ts doc-test=skip reason="wiring snippet requires an application host"
17
+ * import { createTemporalPlayback } from "@honua/sdk-js/map";
18
+ * import "@honua/sdk-js/web-components";
19
+ *
20
+ * const playback = createTemporalPlayback({ handle: mounted, timeField: "event_time", ... });
21
+ * document.querySelector("honua-time-slider").playback = playback;
22
+ * ```
23
+ *
24
+ * The `playback` property (or the equivalent {@link HonuaTimeSliderElement.connect})
25
+ * accepts anything shaped like {@link HonuaTimeSliderPlayback}; the element
26
+ * never imports the controller module, so the component kit's bundle does not
27
+ * gain the `/map` entrypoint's closure.
28
+ *
29
+ * ## Attributes
30
+ * - `label` — accessible name and heading of the panel.
31
+ * - `unavailable-reason` — honest degraded state: set this when the mounted
32
+ * source has no usable temporal metadata. The panel still renders, names the
33
+ * reason, and disables every transport control rather than offering a
34
+ * scrubber that cannot move anything.
35
+ * - `speeds` — comma-separated playback rate multipliers for the speed control
36
+ * (default `0.5,1,2,4`). The control is hidden entirely when the bound
37
+ * controller exposes no `setSpeed`.
38
+ *
39
+ * ## Events
40
+ * - `honua-time-change` — `CustomEvent<HonuaTimeChangeDetail>` whenever the
41
+ * window moves (controller tick, scrub, step, or keyboard).
42
+ * - `honua-time-playback-change` — `CustomEvent<HonuaTimePlaybackChangeDetail>`
43
+ * whenever transport or rate changes (play, pause, end, speed, binding).
44
+ *
45
+ * ## Accessibility
46
+ * The scrubber follows the WAI-ARIA slider pattern: `role="slider"` with
47
+ * `aria-valuemin` / `aria-valuemax` / `aria-valuenow` in epoch milliseconds,
48
+ * a human-readable `aria-valuetext` naming the window, `aria-orientation`, and
49
+ * an accessible name derived from the panel label. It is a single tab stop
50
+ * (`tabindex="0"`, pinned to `-1` while degraded) and is operable from the
51
+ * keyboard alone: ArrowRight/ArrowUp step forward, ArrowLeft/ArrowDown step
52
+ * back (mirrored on the inline axis under `dir="rtl"`), PageUp/PageDown move
53
+ * ten steps, Home/End jump to the first/last window. Transport is a
54
+ * `role="group"` of buttons with `aria-pressed` on the play/pause toggle, and
55
+ * the window readout is a `role="status"` live region.
56
+ *
57
+ * Rendering is CSP-safe (`renderCspSafeShadowHtml`) and the shadow tree is
58
+ * built exactly once: state updates mutate the existing nodes in place, so
59
+ * handlers never accumulate, an active drag is never torn out from under the
60
+ * pointer, and the focused control is never replaced. Theme via the shared
61
+ * `--honua-ui-*` custom properties.
62
+ *
63
+ * @module
64
+ */
65
+ import { renderCspSafeShadowHtml } from "./csp-styles.js";
66
+ const globalDom = globalThis;
67
+ const HTMLElementBase = globalDom.HTMLElement ?? class {
68
+ };
69
+ const DEFAULT_SPEEDS = [0.5, 1, 2, 4];
70
+ /** Steps moved by PageUp / PageDown, per the WAI-ARIA slider pattern. */
71
+ const LARGE_STEP = 10;
72
+ export class HonuaTimeSliderElement extends HTMLElementBase {
73
+ static get observedAttributes() {
74
+ return ["label", "unavailable-reason", "speeds"];
75
+ }
76
+ #playback;
77
+ #subscriptions = [];
78
+ #messages = {};
79
+ #connected = false;
80
+ #shape = "";
81
+ #dragging = false;
82
+ #disposeDrag;
83
+ // ── binding ────────────────────────────────────────────────
84
+ /** The temporal playback controller this slider drives and reflects. */
85
+ get playback() {
86
+ return this.#playback;
87
+ }
88
+ set playback(playback) {
89
+ if (this.#playback === playback)
90
+ return;
91
+ this.#unsubscribe();
92
+ this.#playback = playback;
93
+ if (playback)
94
+ this.#subscribe(playback);
95
+ this.render();
96
+ this.#dispatchPlaybackChange({});
97
+ }
98
+ /** Binds a playback controller. Equivalent to assigning `.playback`. */
99
+ connect(playback) {
100
+ this.playback = playback;
101
+ }
102
+ /** Caller-supplied message source; assigning re-renders the panel text. */
103
+ get messages() {
104
+ return this.#messages;
105
+ }
106
+ set messages(messages) {
107
+ this.#messages = messages ?? {};
108
+ this.render();
109
+ }
110
+ /**
111
+ * Why playback is unavailable, e.g. the mounted source exposes no temporal
112
+ * field. Reflects the `unavailable-reason` attribute.
113
+ */
114
+ get unavailableReason() {
115
+ return this.#attr("unavailable-reason") ?? undefined;
116
+ }
117
+ set unavailableReason(reason) {
118
+ if (typeof this.setAttribute !== "function")
119
+ return;
120
+ if (reason === undefined || reason === "")
121
+ this.removeAttribute?.("unavailable-reason");
122
+ else
123
+ this.setAttribute("unavailable-reason", reason);
124
+ }
125
+ /** Playback rate multipliers offered by the speed control. */
126
+ get speeds() {
127
+ const declared = this.#attr("speeds");
128
+ if (!declared)
129
+ return DEFAULT_SPEEDS;
130
+ const parsed = declared
131
+ .split(",")
132
+ .map((entry) => Number(entry.trim()))
133
+ .filter((entry) => Number.isFinite(entry) && entry > 0);
134
+ return parsed.length > 0 ? parsed : DEFAULT_SPEEDS;
135
+ }
136
+ set speeds(values) {
137
+ if (typeof this.setAttribute !== "function")
138
+ return;
139
+ if (!values || values.length === 0)
140
+ this.removeAttribute?.("speeds");
141
+ else
142
+ this.setAttribute("speeds", values.join(","));
143
+ }
144
+ // ── derived state ──────────────────────────────────────────
145
+ /** `unsupported` when a reason is declared, `ready` when bound, else `idle`. */
146
+ get status() {
147
+ if (this.unavailableReason)
148
+ return "unsupported";
149
+ return this.#playback ? "ready" : "idle";
150
+ }
151
+ /** The controller's current window, when one is bound. */
152
+ get window() {
153
+ return this.#playback?.window;
154
+ }
155
+ /** The controller's full playable extent, when one is bound. */
156
+ get extent() {
157
+ return this.#playback?.extent;
158
+ }
159
+ get playing() {
160
+ return this.#playback?.playing ?? false;
161
+ }
162
+ get speed() {
163
+ return this.#playback?.speed ?? 1;
164
+ }
165
+ /** Window-start position within the scrubbable range, 0..1. */
166
+ get progress() {
167
+ const min = this.#min();
168
+ const max = this.#max();
169
+ const now = this.#playback?.window.start;
170
+ if (min === undefined || max === undefined || now === undefined || max <= min)
171
+ return 0;
172
+ return Math.min(1, Math.max(0, (now - min) / (max - min)));
173
+ }
174
+ // ── commands ───────────────────────────────────────────────
175
+ play() {
176
+ if (!this.#operable())
177
+ return;
178
+ this.#playback?.play();
179
+ }
180
+ pause() {
181
+ this.#playback?.pause();
182
+ }
183
+ /** Play when paused, pause when playing. */
184
+ toggle() {
185
+ if (this.playing)
186
+ this.pause();
187
+ else
188
+ this.play();
189
+ }
190
+ /** Moves the window `count` steps forward (positive) or back (negative). */
191
+ step(direction = 1, count = 1) {
192
+ const playback = this.#playback;
193
+ if (!playback || !this.#operable())
194
+ return;
195
+ const repeats = Math.max(1, Math.trunc(count));
196
+ if (typeof playback.step === "function") {
197
+ for (let index = 0; index < repeats; index += 1)
198
+ playback.step(direction);
199
+ }
200
+ else {
201
+ playback.scrub(playback.window.start + direction * this.#stepMs() * repeats);
202
+ }
203
+ this.#afterWindowMove("step");
204
+ }
205
+ /** Jumps the window start to `epochMs`, clamped by the controller. */
206
+ scrubTo(epochMs) {
207
+ const playback = this.#playback;
208
+ if (!playback || !this.#operable() || !Number.isFinite(epochMs))
209
+ return;
210
+ playback.scrub(epochMs);
211
+ this.#afterWindowMove("scrub");
212
+ }
213
+ /** Changes the playback rate multiplier when the controller supports it. */
214
+ setSpeed(multiplier) {
215
+ const playback = this.#playback;
216
+ if (!playback || typeof playback.setSpeed !== "function")
217
+ return;
218
+ if (!Number.isFinite(multiplier) || multiplier <= 0)
219
+ return;
220
+ playback.setSpeed(multiplier);
221
+ this.#sync();
222
+ this.#dispatchPlaybackChange({});
223
+ }
224
+ // ── lifecycle ──────────────────────────────────────────────
225
+ attributeChangedCallback() {
226
+ this.render();
227
+ }
228
+ connectedCallback() {
229
+ this.#connected = true;
230
+ this.#ensureShadowRoot();
231
+ // Reconnecting re-takes exactly the subscriptions disconnect released, so
232
+ // a detach/reattach cycle neither strands a listener nor double-binds.
233
+ if (this.#playback && this.#subscriptions.length === 0)
234
+ this.#subscribe(this.#playback);
235
+ this.render();
236
+ }
237
+ disconnectedCallback() {
238
+ this.#connected = false;
239
+ this.#disposeDrag?.();
240
+ this.#disposeDrag = undefined;
241
+ this.#dragging = false;
242
+ this.#unsubscribe();
243
+ }
244
+ #subscribe(playback) {
245
+ this.#subscriptions = [
246
+ playback.on("tick", () => {
247
+ this.#sync();
248
+ this.#dispatchChange("tick");
249
+ }),
250
+ playback.on("play", () => {
251
+ this.#sync();
252
+ this.#dispatchPlaybackChange({});
253
+ }),
254
+ playback.on("pause", () => {
255
+ this.#sync();
256
+ this.#dispatchPlaybackChange({});
257
+ }),
258
+ playback.on("end", () => {
259
+ this.#sync();
260
+ this.#dispatchPlaybackChange({ ended: true });
261
+ }),
262
+ ];
263
+ }
264
+ #unsubscribe() {
265
+ for (const subscription of this.#subscriptions.splice(0))
266
+ subscription.remove();
267
+ }
268
+ // ── rendering ──────────────────────────────────────────────
269
+ #ensureShadowRoot() {
270
+ if (!this.shadowRoot && typeof this.attachShadow === "function") {
271
+ this.attachShadow({ mode: "open" });
272
+ }
273
+ }
274
+ /**
275
+ * Builds the shadow tree once per structural shape, then updates it in place.
276
+ * The shape only changes when the speed control appears/disappears or its
277
+ * options change, so a playback tick never replaces a node — which is what
278
+ * keeps handlers from accumulating and the focused control (or an active
279
+ * pointer drag) intact.
280
+ */
281
+ render() {
282
+ const root = this.shadowRoot;
283
+ if (!root || !this.#connected)
284
+ return;
285
+ const shape = `${this.#showSpeed() ? this.speeds.join(",") : "none"}`;
286
+ if (shape !== this.#shape) {
287
+ this.#shape = shape;
288
+ renderCspSafeShadowHtml(root, this.#template());
289
+ this.#bind(root);
290
+ }
291
+ this.#sync();
292
+ }
293
+ #template() {
294
+ const speeds = this.#showSpeed()
295
+ ? `
296
+ <label class="speed" part="speed">
297
+ <span data-time-speed-label></span>
298
+ <select data-time-speed>
299
+ ${this.speeds.map((value) => `<option value="${value}"></option>`).join("")}
300
+ </select>
301
+ </label>`
302
+ : "";
303
+ return `
304
+ <style>${structuralStyles()}</style>
305
+ <section class="panel" part="panel">
306
+ <div class="bar">
307
+ <h2 data-time-heading></h2>
308
+ <span class="state" data-time-state></span>
309
+ </div>
310
+ <p class="window" part="window" data-time-window role="status" aria-live="polite"></p>
311
+ <div class="track" part="track">
312
+ <div
313
+ class="slider"
314
+ part="slider"
315
+ data-time-slider
316
+ id="honua-time-slider-scrubber"
317
+ role="slider"
318
+ aria-orientation="horizontal"
319
+ >
320
+ <span class="fill" data-time-fill aria-hidden="true"></span>
321
+ <span class="thumb" part="thumb" data-time-thumb aria-hidden="true"></span>
322
+ </div>
323
+ </div>
324
+ <div class="transport" role="group" data-time-transport>
325
+ <button type="button" part="step" data-time-step="-1"></button>
326
+ <button type="button" part="toggle" data-time-toggle></button>
327
+ <button type="button" part="step" data-time-step="1"></button>
328
+ </div>
329
+ ${speeds}
330
+ <p class="hint" data-time-hint></p>
331
+ </section>
332
+ `;
333
+ }
334
+ #bind(root) {
335
+ const slider = root.querySelector("[data-time-slider]");
336
+ slider?.addEventListener("keydown", (event) => this.#onKeyDown(event));
337
+ slider?.addEventListener("pointerdown", (event) => this.#onPointerDown(event));
338
+ root.querySelector("[data-time-toggle]")?.addEventListener("click", () => this.toggle());
339
+ for (const button of root.querySelectorAll("[data-time-step]")) {
340
+ button.addEventListener("click", () => {
341
+ this.step(button.getAttribute("data-time-step") === "-1" ? -1 : 1);
342
+ });
343
+ }
344
+ const speed = root.querySelector("[data-time-speed]");
345
+ speed?.addEventListener("change", () => this.setSpeed(Number(speed.value)));
346
+ }
347
+ /** Updates every rendered value in place. Never replaces a node. */
348
+ #sync() {
349
+ const root = this.shadowRoot;
350
+ if (!root || typeof root.querySelector !== "function")
351
+ return;
352
+ const messages = this.#messages;
353
+ const label = this.#label();
354
+ const status = this.status;
355
+ const operable = this.#operable();
356
+ const playing = this.playing;
357
+ root.querySelector("section")?.setAttribute("aria-label", label);
358
+ setText(root.querySelector("[data-time-heading]"), label);
359
+ setText(root.querySelector("[data-time-state]"), this.#statusText(status));
360
+ setText(root.querySelector("[data-time-window]"), this.#windowText());
361
+ const slider = root.querySelector("[data-time-slider]");
362
+ if (slider) {
363
+ const min = this.#min();
364
+ const max = this.#max();
365
+ const now = this.#playback?.window.start;
366
+ slider.setAttribute("aria-label", messages.sliderLabel?.(label) ?? `${label} window start`);
367
+ slider.setAttribute("aria-valuemin", String(min ?? 0));
368
+ slider.setAttribute("aria-valuemax", String(max ?? 0));
369
+ slider.setAttribute("aria-valuenow", String(now ?? min ?? 0));
370
+ slider.setAttribute("aria-valuetext", this.#windowText());
371
+ slider.setAttribute("aria-disabled", String(!operable));
372
+ slider.setAttribute("tabindex", operable ? "0" : "-1");
373
+ const fill = root.querySelector("[data-time-fill]");
374
+ const thumb = root.querySelector("[data-time-thumb]");
375
+ const percent = `${(this.progress * 100).toFixed(2)}%`;
376
+ if (fill?.style)
377
+ fill.style.inlineSize = percent;
378
+ if (thumb?.style)
379
+ thumb.style.insetInlineStart = percent;
380
+ }
381
+ const transport = root.querySelector("[data-time-transport]");
382
+ transport?.setAttribute("aria-label", messages.transportGroupLabel?.(label) ?? `${label} playback`);
383
+ const toggle = root.querySelector("[data-time-toggle]");
384
+ if (toggle) {
385
+ setText(toggle, playing ? (messages.pause ?? "Pause") : (messages.play ?? "Play"));
386
+ toggle.setAttribute("aria-pressed", String(playing));
387
+ toggle.disabled = !operable;
388
+ }
389
+ for (const button of root.querySelectorAll("[data-time-step]")) {
390
+ const backward = button.getAttribute("data-time-step") === "-1";
391
+ setText(button, backward ? (messages.stepBackward ?? "Step back") : (messages.stepForward ?? "Step forward"));
392
+ button.disabled = !operable;
393
+ }
394
+ setText(root.querySelector("[data-time-speed-label]"), messages.speedLabel ?? "Speed");
395
+ const speed = root.querySelector("[data-time-speed]");
396
+ if (speed) {
397
+ for (const option of speed.querySelectorAll("option")) {
398
+ const multiplier = Number(option.value);
399
+ setText(option, messages.speedOption?.(multiplier) ?? `${multiplier}x`);
400
+ }
401
+ const current = String(this.speed);
402
+ if (speed.value !== current && this.speeds.some((value) => String(value) === current))
403
+ speed.value = current;
404
+ speed.disabled = !operable;
405
+ }
406
+ setText(root.querySelector("[data-time-hint]"), messages.hint ??
407
+ "Arrow keys step the window, Page Up and Page Down move ten steps, Home and End jump to the ends.");
408
+ }
409
+ #statusText(status) {
410
+ const declared = this.#messages.status?.[status];
411
+ if (declared)
412
+ return declared;
413
+ if (status === "unsupported")
414
+ return "unavailable";
415
+ if (status === "idle")
416
+ return "no playback";
417
+ return "ready";
418
+ }
419
+ #windowText() {
420
+ const reason = this.unavailableReason;
421
+ if (reason)
422
+ return this.#messages.unavailable?.(reason) ?? reason;
423
+ const window = this.#playback?.window;
424
+ if (!window)
425
+ return this.#messages.noPlayback ?? "Bind a temporal playback controller to scrub time.";
426
+ const format = this.#messages.instant ?? formatInstant;
427
+ const start = format(window.start);
428
+ const end = format(window.end);
429
+ return this.#messages.window?.(start, end) ?? `${start} to ${end}`;
430
+ }
431
+ // ── interaction ────────────────────────────────────────────
432
+ #onKeyDown(event) {
433
+ if (!this.#operable())
434
+ return;
435
+ const mirrored = this.#isRtl();
436
+ let handled = true;
437
+ switch (event.key) {
438
+ case "ArrowRight":
439
+ this.#keyboardStep(mirrored ? -1 : 1, 1);
440
+ break;
441
+ case "ArrowUp":
442
+ this.#keyboardStep(1, 1);
443
+ break;
444
+ case "ArrowLeft":
445
+ this.#keyboardStep(mirrored ? 1 : -1, 1);
446
+ break;
447
+ case "ArrowDown":
448
+ this.#keyboardStep(-1, 1);
449
+ break;
450
+ case "PageUp":
451
+ this.#keyboardStep(1, LARGE_STEP);
452
+ break;
453
+ case "PageDown":
454
+ this.#keyboardStep(-1, LARGE_STEP);
455
+ break;
456
+ case "Home":
457
+ this.#keyboardScrub(this.#min());
458
+ break;
459
+ case "End":
460
+ this.#keyboardScrub(this.#max());
461
+ break;
462
+ default:
463
+ handled = false;
464
+ break;
465
+ }
466
+ if (handled)
467
+ event.preventDefault?.();
468
+ }
469
+ #keyboardStep(direction, count) {
470
+ const playback = this.#playback;
471
+ if (!playback)
472
+ return;
473
+ const repeats = Math.max(1, Math.trunc(count));
474
+ if (typeof playback.step === "function") {
475
+ for (let index = 0; index < repeats; index += 1)
476
+ playback.step(direction);
477
+ }
478
+ else {
479
+ playback.scrub(playback.window.start + direction * this.#stepMs() * repeats);
480
+ }
481
+ this.#afterWindowMove("keyboard");
482
+ }
483
+ #keyboardScrub(time) {
484
+ if (time === undefined || !this.#playback)
485
+ return;
486
+ this.#playback.scrub(time);
487
+ this.#afterWindowMove("keyboard");
488
+ }
489
+ #onPointerDown(event) {
490
+ if (!this.#operable() || typeof this.getBoundingClientRect !== "function")
491
+ return;
492
+ const slider = this.shadowRoot?.querySelector("[data-time-slider]");
493
+ if (!slider)
494
+ return;
495
+ this.#dragging = true;
496
+ slider.setPointerCapture?.(event.pointerId);
497
+ event.preventDefault?.();
498
+ const move = (moveEvent) => {
499
+ if (this.#dragging)
500
+ this.#scrubFromPointer(slider, moveEvent);
501
+ };
502
+ const up = (upEvent) => {
503
+ this.#dragging = false;
504
+ slider.releasePointerCapture?.(upEvent.pointerId);
505
+ this.#disposeDrag?.();
506
+ this.#disposeDrag = undefined;
507
+ };
508
+ slider.addEventListener("pointermove", move);
509
+ slider.addEventListener("pointerup", up);
510
+ slider.addEventListener("pointercancel", up);
511
+ this.#disposeDrag = () => {
512
+ slider.removeEventListener("pointermove", move);
513
+ slider.removeEventListener("pointerup", up);
514
+ slider.removeEventListener("pointercancel", up);
515
+ };
516
+ this.#scrubFromPointer(slider, event);
517
+ }
518
+ #scrubFromPointer(slider, event) {
519
+ const rect = slider.getBoundingClientRect?.();
520
+ const min = this.#min();
521
+ const max = this.#max();
522
+ if (!rect || !rect.width || min === undefined || max === undefined)
523
+ return;
524
+ const raw = (event.clientX - rect.left) / rect.width;
525
+ const fraction = Math.min(1, Math.max(0, this.#isRtl() ? 1 - raw : raw));
526
+ this.#playback?.scrub(min + fraction * (max - min));
527
+ this.#afterWindowMove("scrub");
528
+ }
529
+ // ── helpers ────────────────────────────────────────────────
530
+ /**
531
+ * Controller sinks are asynchronous, so `tick` lands a turn later. Sync now
532
+ * so the rendered value tracks the gesture, and report the gesture's own
533
+ * source rather than letting it surface as an anonymous `tick`.
534
+ */
535
+ #afterWindowMove(source) {
536
+ this.#sync();
537
+ this.#dispatchChange(source);
538
+ }
539
+ #operable() {
540
+ return this.#playback !== undefined && !this.unavailableReason;
541
+ }
542
+ #showSpeed() {
543
+ return typeof this.#playback?.setSpeed === "function";
544
+ }
545
+ #stepMs() {
546
+ const playback = this.#playback;
547
+ if (!playback)
548
+ return 0;
549
+ const declared = playback.stepMs;
550
+ if (typeof declared === "number" && Number.isFinite(declared) && declared > 0)
551
+ return declared;
552
+ return Math.max(1, playback.window.end - playback.window.start);
553
+ }
554
+ #min() {
555
+ return this.#playback?.extent.start;
556
+ }
557
+ /** The last window start that still fits inside the extent. */
558
+ #max() {
559
+ const playback = this.#playback;
560
+ if (!playback)
561
+ return undefined;
562
+ const windowMs = playback.window.end - playback.window.start;
563
+ return Math.max(playback.extent.start, playback.extent.end - windowMs);
564
+ }
565
+ #label() {
566
+ return this.#messages.label ?? this.#attr("label") ?? "Time";
567
+ }
568
+ #attr(name) {
569
+ return typeof this.getAttribute === "function" ? this.getAttribute(name) : null;
570
+ }
571
+ #isRtl() {
572
+ const explicit = this.#attr("dir")?.toLowerCase();
573
+ if (explicit === "rtl")
574
+ return true;
575
+ if (explicit === "ltr")
576
+ return false;
577
+ return globalDom.getComputedStyle?.(this)?.direction === "rtl";
578
+ }
579
+ #dispatchChange(source) {
580
+ const window = this.#playback?.window;
581
+ if (!window || !globalDom.CustomEvent || typeof this.dispatchEvent !== "function")
582
+ return;
583
+ const detail = {
584
+ window,
585
+ progress: this.progress,
586
+ playing: this.playing,
587
+ speed: this.speed,
588
+ status: this.status,
589
+ source,
590
+ };
591
+ this.dispatchEvent(new globalDom.CustomEvent("honua-time-change", { bubbles: true, composed: true, detail }));
592
+ }
593
+ #dispatchPlaybackChange(extra) {
594
+ if (!globalDom.CustomEvent || typeof this.dispatchEvent !== "function")
595
+ return;
596
+ const reason = this.unavailableReason;
597
+ const detail = {
598
+ playing: this.playing,
599
+ speed: this.speed,
600
+ status: this.status,
601
+ ...(extra.ended ? { ended: true } : {}),
602
+ ...(reason ? { reason } : {}),
603
+ };
604
+ this.dispatchEvent(new globalDom.CustomEvent("honua-time-playback-change", { bubbles: true, composed: true, detail }));
605
+ }
606
+ }
607
+ /** Registers `<honua-time-slider>`; skipped when the tag is already defined. */
608
+ export function defineHonuaTimeSlider(registry = globalDom.customElements) {
609
+ if (!registry)
610
+ return;
611
+ if (!registry.get("honua-time-slider")) {
612
+ registry.define("honua-time-slider", HonuaTimeSliderElement);
613
+ }
614
+ }
615
+ function setText(node, value) {
616
+ if (node && node.textContent !== value)
617
+ node.textContent = value;
618
+ }
619
+ /** Locale-neutral default instant format; override with `messages.instant`. */
620
+ function formatInstant(epochMs) {
621
+ if (!Number.isFinite(epochMs))
622
+ return "";
623
+ return new Date(epochMs).toISOString().slice(0, 16).replace("T", " ");
624
+ }
625
+ /**
626
+ * Structural styles themable via the shared `--honua-ui-*` custom properties.
627
+ * Deliberately declares no transition, animation, or keyframes: the element
628
+ * owns no motion, so there is nothing for `prefers-reduced-motion` to suppress.
629
+ */
630
+ function structuralStyles() {
631
+ return `
632
+ :host {
633
+ --honua-ui-bg: #ffffff;
634
+ --honua-ui-fg: #172033;
635
+ --honua-ui-muted: #667085;
636
+ --honua-ui-border: #d0d5dd;
637
+ --honua-ui-accent: #1d4ed8;
638
+ --honua-ui-accent-fg: #ffffff;
639
+ box-sizing: border-box;
640
+ color: var(--honua-ui-fg);
641
+ direction: inherit;
642
+ display: block;
643
+ font: 14px/1.4 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
644
+ }
645
+ :host([dir="rtl"]) { direction: rtl; }
646
+ *, *::before, *::after { box-sizing: inherit; }
647
+ button {
648
+ border: 1px solid var(--honua-ui-border);
649
+ background: var(--honua-ui-bg);
650
+ border-radius: 6px;
651
+ color: inherit;
652
+ cursor: pointer;
653
+ font: inherit;
654
+ min-height: 32px;
655
+ padding-block: 0;
656
+ padding-inline: 10px;
657
+ }
658
+ button:disabled { cursor: not-allowed; opacity: 0.55; }
659
+ button[aria-pressed="true"] {
660
+ background: var(--honua-ui-accent);
661
+ border-color: var(--honua-ui-accent);
662
+ color: var(--honua-ui-accent-fg);
663
+ }
664
+ h2 { font-size: 14px; margin: 0; }
665
+ .panel {
666
+ background: var(--honua-ui-bg);
667
+ border: 1px solid var(--honua-ui-border);
668
+ border-radius: 8px;
669
+ display: grid;
670
+ gap: 10px;
671
+ min-width: 200px;
672
+ padding-block: 10px;
673
+ padding-inline: 10px;
674
+ text-align: start;
675
+ }
676
+ .bar { align-items: center; display: flex; gap: 8px; justify-content: space-between; }
677
+ .state { color: var(--honua-ui-muted); font-size: 12px; }
678
+ .window { margin: 0; overflow-wrap: anywhere; }
679
+ .track { display: block; min-width: 0; padding-block: 4px; }
680
+ .slider {
681
+ background: var(--honua-ui-border);
682
+ block-size: 6px;
683
+ border-radius: 3px;
684
+ cursor: pointer;
685
+ inline-size: 100%;
686
+ max-inline-size: 100%;
687
+ position: relative;
688
+ touch-action: none;
689
+ }
690
+ .slider[aria-disabled="true"] { cursor: not-allowed; opacity: 0.55; }
691
+ .slider:focus-visible { outline: 2px solid var(--honua-ui-accent); outline-offset: 3px; }
692
+ .fill {
693
+ background: var(--honua-ui-accent);
694
+ block-size: 100%;
695
+ border-radius: 3px;
696
+ display: block;
697
+ inline-size: 0%;
698
+ max-inline-size: 100%;
699
+ }
700
+ .thumb {
701
+ background: var(--honua-ui-accent);
702
+ block-size: 16px;
703
+ border: 2px solid var(--honua-ui-bg);
704
+ border-radius: 50%;
705
+ display: block;
706
+ inline-size: 16px;
707
+ inset-block-start: -5px;
708
+ inset-inline-start: 0%;
709
+ position: absolute;
710
+ transform: translateX(-50%);
711
+ }
712
+ :host([dir="rtl"]) .thumb { transform: translateX(50%); }
713
+ .transport { display: grid; gap: 6px; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); }
714
+ .transport button { min-width: 0; overflow-wrap: anywhere; white-space: normal; }
715
+ .speed { align-items: center; display: flex; gap: 8px; }
716
+ .speed select { flex: 1 1 auto; font: inherit; min-width: 0; }
717
+ .hint { color: var(--honua-ui-muted); font-size: 12px; margin: 0; overflow-wrap: anywhere; }
718
+ @media (max-width: 320px) {
719
+ .panel { min-width: 0; }
720
+ .bar { align-items: flex-start; flex-direction: column; }
721
+ .transport { grid-template-columns: minmax(0, 1fr); }
722
+ .transport button { inline-size: 100%; }
723
+ .speed { align-items: flex-start; flex-direction: column; }
724
+ .speed select { inline-size: 100%; }
725
+ }
726
+ @media (forced-colors: active), (prefers-contrast: more) {
727
+ .panel { background: Canvas; border-color: CanvasText; color: CanvasText; }
728
+ .state, .hint { color: GrayText; }
729
+ button { background: ButtonFace; border-color: ButtonText; color: ButtonText; }
730
+ button[aria-pressed="true"] { background: Highlight; border-color: Highlight; color: HighlightText; }
731
+ button:disabled { color: GrayText; }
732
+ button:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
733
+ .slider { background: Canvas; border: 1px solid ButtonText; forced-color-adjust: none; }
734
+ .fill { background: Highlight; }
735
+ .thumb { background: ButtonText; border-color: Canvas; }
736
+ .slider:focus-visible { outline: 2px solid Highlight; outline-offset: 3px; }
737
+ .slider[aria-disabled="true"] { border-color: GrayText; }
738
+ }
739
+ `;
740
+ }