@eifi1/ui-kit 0.45.0 → 0.46.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 (228) hide show
  1. package/README.md +23 -12
  2. package/dist/admin/admin-area-labels.d.ts +9 -0
  3. package/dist/admin/admin-area-labels.js +5 -1
  4. package/dist/admin/admin-area-labels.js.map +1 -1
  5. package/dist/admin/admin-overview.d.ts +16 -1
  6. package/dist/admin/admin-overview.js +12 -1
  7. package/dist/admin/admin-overview.js.map +1 -1
  8. package/dist/admin/mail-test-card.d.ts +3 -2
  9. package/dist/admin/mail-test-card.js +3 -1
  10. package/dist/admin/mail-test-card.js.map +1 -1
  11. package/dist/components/appearance-labels.d.ts +10 -0
  12. package/dist/components/appearance-labels.js +6 -1
  13. package/dist/components/appearance-labels.js.map +1 -1
  14. package/dist/components/data-table-column-settings.js +2 -2
  15. package/dist/components/data-table-column-settings.js.map +1 -1
  16. package/dist/components/data-table-head.js +2 -2
  17. package/dist/components/data-table-head.js.map +1 -1
  18. package/dist/components/data-table-row-actions.d.ts +18 -2
  19. package/dist/components/data-table-row-actions.js +86 -2
  20. package/dist/components/data-table-row-actions.js.map +1 -1
  21. package/dist/components/data-table-shared.js +9 -2
  22. package/dist/components/data-table-shared.js.map +1 -1
  23. package/dist/components/data-table.d.ts +43 -2
  24. package/dist/components/data-table.js +9 -4
  25. package/dist/components/data-table.js.map +1 -1
  26. package/dist/components/date-format-setting.d.ts +29 -0
  27. package/dist/components/date-format-setting.js +36 -0
  28. package/dist/components/date-format-setting.js.map +1 -0
  29. package/dist/components/swipeable-row.d.ts +8 -0
  30. package/dist/components/swipeable-row.js.map +1 -1
  31. package/dist/components/tabs.d.ts +3 -2
  32. package/dist/components/tabs.js +1 -1
  33. package/dist/components/tabs.js.map +1 -1
  34. package/dist/components/translation-review-editor.js +1 -0
  35. package/dist/components/translation-review-editor.js.map +1 -1
  36. package/dist/components/translation-review-labels.d.ts +5 -0
  37. package/dist/components/translation-review-labels.js +2 -0
  38. package/dist/components/translation-review-labels.js.map +1 -1
  39. package/dist/components/translation-review.d.ts +18 -8
  40. package/dist/components/translation-review.js +64 -7
  41. package/dist/components/translation-review.js.map +1 -1
  42. package/dist/components/write-lock-labels.js +2 -1
  43. package/dist/components/write-lock-labels.js.map +1 -1
  44. package/dist/components/write-lock.d.ts +33 -3
  45. package/dist/components/write-lock.js +9 -0
  46. package/dist/components/write-lock.js.map +1 -1
  47. package/dist/data-table.d.ts +1 -1
  48. package/dist/data-table.js.map +1 -1
  49. package/dist/eslint/index.js +1 -1
  50. package/dist/feedback/feedback-row-detail.d.ts +4 -2
  51. package/dist/feedback/feedback-row-detail.js +17 -26
  52. package/dist/feedback/feedback-row-detail.js.map +1 -1
  53. package/dist/feedback/feedback-swipe.d.ts +18 -0
  54. package/dist/feedback/feedback-swipe.js +10 -1
  55. package/dist/feedback/feedback-swipe.js.map +1 -1
  56. package/dist/i18n/german.js +41 -5
  57. package/dist/i18n/german.js.map +1 -1
  58. package/dist/i18n/kit-labels.d.ts +12 -1
  59. package/dist/i18n/kit-labels.js +8 -2
  60. package/dist/i18n/kit-labels.js.map +1 -1
  61. package/dist/i18n/locales/en.js +40 -6
  62. package/dist/i18n/locales/en.js.map +2 -2
  63. package/dist/i18n/locales/es.js +41 -5
  64. package/dist/i18n/locales/es.js.map +1 -1
  65. package/dist/i18n/locales/fr.js +41 -5
  66. package/dist/i18n/locales/fr.js.map +1 -1
  67. package/dist/i18n/locales/hu.js +41 -5
  68. package/dist/i18n/locales/hu.js.map +1 -1
  69. package/dist/i18n/locales/it.js +41 -5
  70. package/dist/i18n/locales/it.js.map +1 -1
  71. package/dist/i18n/locales/zh.js +41 -5
  72. package/dist/i18n/locales/zh.js.map +1 -1
  73. package/dist/i18n/review.js +3 -1
  74. package/dist/i18n/review.js.map +1 -1
  75. package/dist/index.d.ts +15 -2
  76. package/dist/index.js +35 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/lib/date-format.d.ts +81 -0
  79. package/dist/lib/date-format.js +56 -0
  80. package/dist/lib/date-format.js.map +1 -0
  81. package/dist/lib/format.d.ts +3 -0
  82. package/dist/lib/format.js +5 -4
  83. package/dist/lib/format.js.map +1 -1
  84. package/dist/notifications/device-zone-report.d.ts +27 -0
  85. package/dist/notifications/device-zone-report.js +24 -0
  86. package/dist/notifications/device-zone-report.js.map +1 -0
  87. package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
  88. package/dist/notifications/feedback-notification-types-labels.js +4 -1
  89. package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
  90. package/dist/notifications/feedback-notification-types.d.ts +11 -5
  91. package/dist/notifications/feedback-notification-types.js +2 -1
  92. package/dist/notifications/feedback-notification-types.js.map +1 -1
  93. package/dist/notifications/notification-bell.d.ts +33 -0
  94. package/dist/notifications/notification-bell.js +74 -0
  95. package/dist/notifications/notification-bell.js.map +1 -0
  96. package/dist/notifications/notification-inbox.d.ts +77 -0
  97. package/dist/notifications/notification-inbox.js +206 -0
  98. package/dist/notifications/notification-inbox.js.map +1 -0
  99. package/dist/notifications/notification-types-setting.d.ts +6 -4
  100. package/dist/notifications/notification-types-setting.js +103 -45
  101. package/dist/notifications/notification-types-setting.js.map +1 -1
  102. package/dist/notifications/notification-types.d.ts +19 -7
  103. package/dist/notifications/notifications-labels.d.ts +41 -7
  104. package/dist/notifications/notifications-labels.js +18 -1
  105. package/dist/notifications/notifications-labels.js.map +1 -1
  106. package/dist/notifications/push-devices-setting.d.ts +20 -3
  107. package/dist/notifications/push-devices-setting.js +93 -33
  108. package/dist/notifications/push-devices-setting.js.map +1 -1
  109. package/dist/notifications/push-worker.d.ts +108 -0
  110. package/dist/notifications/push-worker.js +62 -0
  111. package/dist/notifications/push-worker.js.map +1 -0
  112. package/dist/notifications/quiet-hours-setting.d.ts +29 -17
  113. package/dist/notifications/quiet-hours-setting.js +9 -41
  114. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  115. package/dist/notifications/time-zone-select.d.ts +5 -2
  116. package/dist/notifications/time-zone-select.js.map +1 -1
  117. package/dist/notifications/use-push-device.d.ts +4 -17
  118. package/dist/notifications/use-push-device.js +7 -15
  119. package/dist/notifications/use-push-device.js.map +1 -1
  120. package/dist/offline/local-first.d.ts +33 -0
  121. package/dist/offline/local-first.js +39 -0
  122. package/dist/offline/local-first.js.map +1 -0
  123. package/dist/offline/query-persistence.d.ts +26 -1
  124. package/dist/offline/query-persistence.js +27 -23
  125. package/dist/offline/query-persistence.js.map +1 -1
  126. package/dist/offline/service-worker-updates.d.ts +46 -0
  127. package/dist/offline/service-worker-updates.js +39 -0
  128. package/dist/offline/service-worker-updates.js.map +1 -0
  129. package/dist/offline.d.ts +5 -2
  130. package/dist/offline.js +11 -2
  131. package/dist/offline.js.map +1 -1
  132. package/dist/push-worker.d.ts +2 -0
  133. package/dist/push-worker.js +14 -0
  134. package/dist/push-worker.js.map +1 -0
  135. package/dist/shell/account-menu-text-size.d.ts +33 -0
  136. package/dist/shell/account-menu-text-size.js +51 -0
  137. package/dist/shell/account-menu-text-size.js.map +1 -0
  138. package/dist/shell/top-bar-actions.d.ts +6 -2
  139. package/dist/shell/top-bar-actions.js +1 -0
  140. package/dist/shell/top-bar-actions.js.map +1 -1
  141. package/dist/shell.d.ts +2 -0
  142. package/dist/shell.js +2 -0
  143. package/dist/shell.js.map +1 -1
  144. package/dist/sync/db.d.ts +76 -0
  145. package/dist/sync/db.js +24 -0
  146. package/dist/sync/db.js.map +1 -0
  147. package/dist/sync/engine.d.ts +124 -0
  148. package/dist/sync/engine.js +474 -0
  149. package/dist/sync/engine.js.map +1 -0
  150. package/dist/sync/hooks.d.ts +30 -0
  151. package/dist/sync/hooks.js +67 -0
  152. package/dist/sync/hooks.js.map +1 -0
  153. package/dist/sync/testing.d.ts +28 -0
  154. package/dist/sync/testing.js +41 -0
  155. package/dist/sync/testing.js.map +1 -0
  156. package/dist/sync/wire.d.ts +50 -0
  157. package/dist/sync/wire.js +2 -0
  158. package/dist/sync/wire.js.map +1 -0
  159. package/dist/sync.d.ts +7 -0
  160. package/dist/sync.js +22 -0
  161. package/dist/sync.js.map +1 -0
  162. package/dist/tour/tour-labels.js +1 -0
  163. package/dist/tour/tour-labels.js.map +1 -1
  164. package/dist/tour/tour.d.ts +30 -0
  165. package/dist/tour/tour.js +50 -6
  166. package/dist/tour/tour.js.map +1 -1
  167. package/package.json +19 -1
  168. package/src/admin/admin-area-labels.ts +13 -0
  169. package/src/admin/admin-overview.tsx +37 -2
  170. package/src/admin/mail-test-card.tsx +7 -3
  171. package/src/components/appearance-labels.ts +16 -0
  172. package/src/components/data-table-column-settings.tsx +2 -2
  173. package/src/components/data-table-head.tsx +2 -2
  174. package/src/components/data-table-row-actions.tsx +118 -2
  175. package/src/components/data-table-shared.tsx +21 -2
  176. package/src/components/data-table.tsx +51 -6
  177. package/src/components/date-format-setting.tsx +64 -0
  178. package/src/components/swipeable-row.tsx +8 -0
  179. package/src/components/tabs.tsx +4 -3
  180. package/src/components/translation-review-editor.tsx +2 -0
  181. package/src/components/translation-review-labels.ts +7 -0
  182. package/src/components/translation-review.tsx +102 -14
  183. package/src/components/write-lock-labels.ts +1 -0
  184. package/src/components/write-lock.tsx +36 -3
  185. package/src/data-table.ts +1 -0
  186. package/src/feedback/feedback-row-detail.tsx +18 -23
  187. package/src/feedback/feedback-swipe.tsx +28 -1
  188. package/src/i18n/german.ts +44 -4
  189. package/src/i18n/kit-labels.tsx +18 -1
  190. package/src/i18n/locales/en.ts +1 -0
  191. package/src/i18n/locales/es.ts +44 -4
  192. package/src/i18n/locales/fr.ts +44 -4
  193. package/src/i18n/locales/hu.ts +44 -4
  194. package/src/i18n/locales/it.ts +44 -4
  195. package/src/i18n/locales/zh.ts +44 -2
  196. package/src/i18n/review.ts +3 -1
  197. package/src/index.ts +42 -0
  198. package/src/lib/date-format.ts +151 -0
  199. package/src/lib/format.ts +10 -4
  200. package/src/notifications/device-zone-report.ts +54 -0
  201. package/src/notifications/feedback-notification-types-labels.ts +10 -1
  202. package/src/notifications/feedback-notification-types.ts +12 -5
  203. package/src/notifications/notification-bell.tsx +116 -0
  204. package/src/notifications/notification-inbox.tsx +304 -0
  205. package/src/notifications/notification-types-setting.tsx +151 -55
  206. package/src/notifications/notification-types.ts +20 -8
  207. package/src/notifications/notifications-labels.ts +69 -11
  208. package/src/notifications/push-devices-setting.tsx +123 -34
  209. package/src/notifications/push-worker.ts +160 -0
  210. package/src/notifications/quiet-hours-setting.tsx +41 -58
  211. package/src/notifications/time-zone-select.tsx +5 -2
  212. package/src/notifications/use-push-device.ts +20 -33
  213. package/src/offline/local-first.ts +76 -0
  214. package/src/offline/query-persistence.ts +57 -24
  215. package/src/offline/service-worker-updates.ts +81 -0
  216. package/src/offline.ts +7 -1
  217. package/src/push-worker.ts +25 -0
  218. package/src/shell/account-menu-text-size.tsx +77 -0
  219. package/src/shell/top-bar-actions.tsx +6 -1
  220. package/src/shell.ts +3 -0
  221. package/src/sync/db.ts +103 -0
  222. package/src/sync/engine.ts +699 -0
  223. package/src/sync/hooks.ts +93 -0
  224. package/src/sync/testing.ts +73 -0
  225. package/src/sync/wire.ts +61 -0
  226. package/src/sync.ts +29 -0
  227. package/src/tour/tour-labels.ts +1 -0
  228. package/src/tour/tour.tsx +100 -7
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
3
+ import { liveQuery } from "dexie";
4
+ function useSyncWorker(engine) {
5
+ useEffect(() => {
6
+ if (!engine) return;
7
+ engine.start();
8
+ return () => engine.stop();
9
+ }, [engine]);
10
+ }
11
+ const IDLE = {
12
+ phase: "idle",
13
+ online: true,
14
+ counts: { pending: 0, waiting: 0, blocked: 0, rejected: 0 },
15
+ lastSyncedAt: null,
16
+ nextRetryAt: null
17
+ };
18
+ const noEngine = () => () => void 0;
19
+ const idle = () => IDLE;
20
+ function useSyncStatus(engine) {
21
+ const state = useSyncExternalStore(
22
+ engine ? engine.subscribe : noEngine,
23
+ engine ? engine.getState : idle,
24
+ engine ? engine.getState : idle
25
+ );
26
+ const browserOnline = useSyncExternalStore(subscribeOnline, readOnline, () => true);
27
+ const onSync = useCallback(async () => {
28
+ await engine?.syncNow();
29
+ }, [engine]);
30
+ const status = {
31
+ phase: state.phase,
32
+ online: engine ? state.online : browserOnline,
33
+ pending: state.counts.pending,
34
+ waitingForPlan: state.counts.waiting,
35
+ blocked: state.counts.blocked + state.counts.rejected,
36
+ lastSyncedAt: state.lastSyncedAt
37
+ };
38
+ return { status, onSync };
39
+ }
40
+ function subscribeOnline(listener) {
41
+ window.addEventListener("online", listener);
42
+ window.addEventListener("offline", listener);
43
+ return () => {
44
+ window.removeEventListener("online", listener);
45
+ window.removeEventListener("offline", listener);
46
+ };
47
+ }
48
+ const readOnline = () => navigator.onLine !== false;
49
+ function useSyncQuery(db, query, deps) {
50
+ const [state, setState] = useState({ data: void 0, isLoading: true });
51
+ useEffect(() => {
52
+ if (!db) return;
53
+ const subscription = liveQuery(() => query(db)).subscribe({
54
+ next: (data) => setState({ data, isLoading: false }),
55
+ error: () => setState({ data: void 0, isLoading: false })
56
+ });
57
+ return () => subscription.unsubscribe();
58
+ }, [db, ...deps]);
59
+ return db ? state : NO_COPY;
60
+ }
61
+ const NO_COPY = { data: void 0, isLoading: true };
62
+ export {
63
+ useSyncQuery,
64
+ useSyncStatus,
65
+ useSyncWorker
66
+ };
67
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sync/hooks.ts"],"sourcesContent":["import { useCallback, useEffect, useState, useSyncExternalStore } from \"react\";\nimport { liveQuery } from \"dexie\";\nimport type { SyncStatus } from \"../shell/sync-status-indicator\";\nimport type { SyncDb } from \"./db\";\nimport type { SyncEngine, SyncEngineState } from \"./engine\";\n\n/**\n * Keep `engine` running while it is mounted: started on mount, stopped on unmount or when\n * the engine changes (a company switch builds a new engine on its own database, D15).\n * `null` runs nothing — signed out, or a computer marked shared. Mount once, in the\n * signed-in layout (kastlan's `useHandoverSyncLoop`).\n */\nexport function useSyncWorker(engine: SyncEngine | null): void {\n useEffect(() => {\n if (!engine) return;\n engine.start();\n return () => engine.stop();\n }, [engine]);\n}\n\nconst IDLE: SyncEngineState = {\n phase: \"idle\",\n online: true,\n counts: { pending: 0, waiting: 0, blocked: 0, rejected: 0 },\n lastSyncedAt: null,\n nextRetryAt: null,\n};\nconst noEngine = () => () => undefined;\nconst idle = () => IDLE;\n\n/**\n * The engine's state as the kit's `SyncStatusIndicator` reads it (§2.3): `pending` = queued\n * and in flight (and the steps' pending), `waitingForPlan` = `waiting`, `blocked` = `blocked`\n * + `rejected`, `lastSyncedAt` from `meta`. `onSync` is \"Sync now\". Without an engine, an\n * idle status that follows the browser's connection.\n */\nexport function useSyncStatus(engine: SyncEngine | null): { status: SyncStatus; onSync: () => Promise<void> } {\n const state = useSyncExternalStore(\n engine ? engine.subscribe : noEngine,\n engine ? engine.getState : idle,\n engine ? engine.getState : idle,\n );\n const browserOnline = useSyncExternalStore(subscribeOnline, readOnline, () => true);\n const onSync = useCallback(async () => {\n await engine?.syncNow();\n }, [engine]);\n const status: SyncStatus = {\n phase: state.phase,\n online: engine ? state.online : browserOnline,\n pending: state.counts.pending,\n waitingForPlan: state.counts.waiting,\n blocked: state.counts.blocked + state.counts.rejected,\n lastSyncedAt: state.lastSyncedAt,\n };\n return { status, onSync };\n}\n\nfunction subscribeOnline(listener: () => void) {\n window.addEventListener(\"online\", listener);\n window.addEventListener(\"offline\", listener);\n return () => {\n window.removeEventListener(\"online\", listener);\n window.removeEventListener(\"offline\", listener);\n };\n}\nconst readOnline = () => navigator.onLine !== false;\n\n/**\n * A read of the copy that follows every change to it (Dexie's `liveQuery`; kastlan's\n * `useLive`). `deps` names what `query` reads; `query` itself is rebuilt every render.\n * `query` gets the app's own database type (`HandoverDb`). Without a database (not open\n * yet) it answers `isLoading: true`, so a page shows no \"empty\" before the copy opens.\n */\nexport function useSyncQuery<T, D extends SyncDb = SyncDb>(\n db: D | null,\n query: (db: D) => Promise<T>,\n deps: readonly unknown[],\n): { data: T | undefined; isLoading: boolean } {\n const [state, setState] = useState<{ data: T | undefined; isLoading: boolean }>({ data: undefined, isLoading: true });\n useEffect(() => {\n if (!db) return;\n const subscription = liveQuery(() => query(db)).subscribe({\n next: (data) => setState({ data, isLoading: false }),\n error: () => setState({ data: undefined, isLoading: false }),\n });\n return () => subscription.unsubscribe();\n // `query` is rebuilt every render; `deps` names what it reads.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [db, ...deps]);\n return db ? state : NO_COPY;\n}\n\nconst NO_COPY = { data: undefined, isLoading: true } as const;\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,UAAU,4BAA4B;AACvE,SAAS,iBAAiB;AAWnB,SAAS,cAAc,QAAiC;AAC7D,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,WAAO,MAAM;AACb,WAAO,MAAM,OAAO,KAAK;AAAA,EAC3B,GAAG,CAAC,MAAM,CAAC;AACb;AAEA,MAAM,OAAwB;AAAA,EAC5B,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,EAAE;AAAA,EAC1D,cAAc;AAAA,EACd,aAAa;AACf;AACA,MAAM,WAAW,MAAM,MAAM;AAC7B,MAAM,OAAO,MAAM;AAQZ,SAAS,cAAc,QAAgF;AAC5G,QAAM,QAAQ;AAAA,IACZ,SAAS,OAAO,YAAY;AAAA,IAC5B,SAAS,OAAO,WAAW;AAAA,IAC3B,SAAS,OAAO,WAAW;AAAA,EAC7B;AACA,QAAM,gBAAgB,qBAAqB,iBAAiB,YAAY,MAAM,IAAI;AAClF,QAAM,SAAS,YAAY,YAAY;AACrC,UAAM,QAAQ,QAAQ;AAAA,EACxB,GAAG,CAAC,MAAM,CAAC;AACX,QAAM,SAAqB;AAAA,IACzB,OAAO,MAAM;AAAA,IACb,QAAQ,SAAS,MAAM,SAAS;AAAA,IAChC,SAAS,MAAM,OAAO;AAAA,IACtB,gBAAgB,MAAM,OAAO;AAAA,IAC7B,SAAS,MAAM,OAAO,UAAU,MAAM,OAAO;AAAA,IAC7C,cAAc,MAAM;AAAA,EACtB;AACA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEA,SAAS,gBAAgB,UAAsB;AAC7C,SAAO,iBAAiB,UAAU,QAAQ;AAC1C,SAAO,iBAAiB,WAAW,QAAQ;AAC3C,SAAO,MAAM;AACX,WAAO,oBAAoB,UAAU,QAAQ;AAC7C,WAAO,oBAAoB,WAAW,QAAQ;AAAA,EAChD;AACF;AACA,MAAM,aAAa,MAAM,UAAU,WAAW;AAQvC,SAAS,aACd,IACA,OACA,MAC6C;AAC7C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAsD,EAAE,MAAM,QAAW,WAAW,KAAK,CAAC;AACpH,YAAU,MAAM;AACd,QAAI,CAAC,GAAI;AACT,UAAM,eAAe,UAAU,MAAM,MAAM,EAAE,CAAC,EAAE,UAAU;AAAA,MACxD,MAAM,CAAC,SAAS,SAAS,EAAE,MAAM,WAAW,MAAM,CAAC;AAAA,MACnD,OAAO,MAAM,SAAS,EAAE,MAAM,QAAW,WAAW,MAAM,CAAC;AAAA,IAC7D,CAAC;AACD,WAAO,MAAM,aAAa,YAAY;AAAA,EAGxC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;AAChB,SAAO,KAAK,QAAQ;AACtB;AAEA,MAAM,UAAU,EAAE,MAAM,QAAW,WAAW,KAAK;","names":[]}
@@ -0,0 +1,28 @@
1
+ import type { SyncRequestBody, SyncResponseBody } from "./wire.js";
2
+ /** One scripted answer: a body, a function of the request, or a failure. */
3
+ export type FakeSyncAnswer = SyncResponseBody | ((body: SyncRequestBody) => SyncResponseBody | Promise<SyncResponseBody>) | {
4
+ fail: "offline";
5
+ } | {
6
+ fail: number;
7
+ data?: unknown;
8
+ };
9
+ export interface FakeSyncTransport {
10
+ /** The engine's `send`. */
11
+ send(body: SyncRequestBody, signal?: AbortSignal): Promise<SyncResponseBody>;
12
+ /** Every request received, in order (the body as sent). */
13
+ readonly requests: SyncRequestBody[];
14
+ /** Queue answers for the next requests, in order; past them the default answers. */
15
+ answer(...answers: FakeSyncAnswer[]): void;
16
+ /** The next request meets no connection (an axios-like error with no response). */
17
+ failOffline(): void;
18
+ /** The next request answers `status` (a 5xx, a 402 `{code}`, a 403). */
19
+ failStatus(status: number, data?: unknown): void;
20
+ /** The default answer's server knowledge, raised by one for every request. */
21
+ knowledge: number;
22
+ }
23
+ /**
24
+ * The default answer echoes every pushed change as applied, with a server id (`pk`, 100 on),
25
+ * and an empty pull besides; `knowledge` goes up by one each request. Script anything else
26
+ * with `answer(...)`, `failOffline()` and `failStatus(...)`.
27
+ */
28
+ export declare function createFakeSyncTransport(): FakeSyncTransport;
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ function wireError(fail, data) {
3
+ const err = new Error(fail === "offline" ? "Network Error" : `Request failed with status code ${fail}`);
4
+ return Object.assign(err, {
5
+ isAxiosError: true,
6
+ response: fail === "offline" ? void 0 : { status: fail, data: data ?? {} }
7
+ });
8
+ }
9
+ function createFakeSyncTransport() {
10
+ const script = [];
11
+ let nextPk = 100;
12
+ const transport = {
13
+ requests: [],
14
+ knowledge: 0,
15
+ answer: (...answers) => void script.push(...answers),
16
+ failOffline: () => void script.push({ fail: "offline" }),
17
+ failStatus: (status, data) => void script.push({ fail: status, data }),
18
+ async send(body) {
19
+ transport.requests.push(structuredClone(body));
20
+ const next = script.shift();
21
+ if (next && typeof next === "object" && "fail" in next) {
22
+ throw wireError(next.fail, "data" in next ? next.data : void 0);
23
+ }
24
+ if (typeof next === "function") return next(body);
25
+ if (next) return next;
26
+ transport.knowledge += 1;
27
+ return {
28
+ server_knowledge: transport.knowledge,
29
+ changes: body.changes.map((change) => ({ ...change, pk: change.op === "delete" ? null : nextPk++ })),
30
+ rejections: [],
31
+ refused: null,
32
+ page_cursor: null
33
+ };
34
+ }
35
+ };
36
+ return transport;
37
+ }
38
+ export {
39
+ createFakeSyncTransport
40
+ };
41
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sync/testing.ts"],"sourcesContent":["import type { SyncRequestBody, SyncResponseBody } from \"./wire\";\n\n/*\n * `@eifi1/ui-kit/sync/testing` — a scripted `/sync` server for an app's tests of the engine\n * (docs/local-first-harmonization.md §2.3). Pass `transport.send` as the engine's `send`.\n * Imports nothing (no Dexie): it is the engine's `send`, not a store.\n */\n\n/** One scripted answer: a body, a function of the request, or a failure. */\nexport type FakeSyncAnswer =\n | SyncResponseBody\n | ((body: SyncRequestBody) => SyncResponseBody | Promise<SyncResponseBody>)\n | { fail: \"offline\" }\n | { fail: number; data?: unknown };\n\nexport interface FakeSyncTransport {\n /** The engine's `send`. */\n send(body: SyncRequestBody, signal?: AbortSignal): Promise<SyncResponseBody>;\n /** Every request received, in order (the body as sent). */\n readonly requests: SyncRequestBody[];\n /** Queue answers for the next requests, in order; past them the default answers. */\n answer(...answers: FakeSyncAnswer[]): void;\n /** The next request meets no connection (an axios-like error with no response). */\n failOffline(): void;\n /** The next request answers `status` (a 5xx, a 402 `{code}`, a 403). */\n failStatus(status: number, data?: unknown): void;\n /** The default answer's server knowledge, raised by one for every request. */\n knowledge: number;\n}\n\n/** The error an axios client throws: `isAxiosError`, and `response` only with an answer. */\nfunction wireError(fail: \"offline\" | number, data?: unknown): Error {\n const err = new Error(fail === \"offline\" ? \"Network Error\" : `Request failed with status code ${fail}`);\n return Object.assign(err, {\n isAxiosError: true,\n response: fail === \"offline\" ? undefined : { status: fail, data: data ?? {} },\n });\n}\n\n/**\n * The default answer echoes every pushed change as applied, with a server id (`pk`, 100 on),\n * and an empty pull besides; `knowledge` goes up by one each request. Script anything else\n * with `answer(...)`, `failOffline()` and `failStatus(...)`.\n */\nexport function createFakeSyncTransport(): FakeSyncTransport {\n const script: FakeSyncAnswer[] = [];\n let nextPk = 100;\n const transport: FakeSyncTransport = {\n requests: [],\n knowledge: 0,\n answer: (...answers) => void script.push(...answers),\n failOffline: () => void script.push({ fail: \"offline\" }),\n failStatus: (status, data) => void script.push({ fail: status, data }),\n async send(body) {\n transport.requests.push(structuredClone(body));\n const next = script.shift();\n if (next && typeof next === \"object\" && \"fail\" in next) {\n throw wireError(next.fail, \"data\" in next ? next.data : undefined);\n }\n if (typeof next === \"function\") return next(body);\n if (next) return next;\n transport.knowledge += 1;\n return {\n server_knowledge: transport.knowledge,\n changes: body.changes.map((change) => ({ ...change, pk: change.op === \"delete\" ? null : nextPk++ })),\n rejections: [],\n refused: null,\n page_cursor: null,\n };\n },\n };\n return transport;\n}\n"],"mappings":";AA+BA,SAAS,UAAU,MAA0B,MAAuB;AAClE,QAAM,MAAM,IAAI,MAAM,SAAS,YAAY,kBAAkB,mCAAmC,IAAI,EAAE;AACtG,SAAO,OAAO,OAAO,KAAK;AAAA,IACxB,cAAc;AAAA,IACd,UAAU,SAAS,YAAY,SAAY,EAAE,QAAQ,MAAM,MAAM,QAAQ,CAAC,EAAE;AAAA,EAC9E,CAAC;AACH;AAOO,SAAS,0BAA6C;AAC3D,QAAM,SAA2B,CAAC;AAClC,MAAI,SAAS;AACb,QAAM,YAA+B;AAAA,IACnC,UAAU,CAAC;AAAA,IACX,WAAW;AAAA,IACX,QAAQ,IAAI,YAAY,KAAK,OAAO,KAAK,GAAG,OAAO;AAAA,IACnD,aAAa,MAAM,KAAK,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAAA,IACvD,YAAY,CAAC,QAAQ,SAAS,KAAK,OAAO,KAAK,EAAE,MAAM,QAAQ,KAAK,CAAC;AAAA,IACrE,MAAM,KAAK,MAAM;AACf,gBAAU,SAAS,KAAK,gBAAgB,IAAI,CAAC;AAC7C,YAAM,OAAO,OAAO,MAAM;AAC1B,UAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,MAAM;AACtD,cAAM,UAAU,KAAK,MAAM,UAAU,OAAO,KAAK,OAAO,MAAS;AAAA,MACnE;AACA,UAAI,OAAO,SAAS,WAAY,QAAO,KAAK,IAAI;AAChD,UAAI,KAAM,QAAO;AACjB,gBAAU,aAAa;AACvB,aAAO;AAAA,QACL,kBAAkB,UAAU;AAAA,QAC5B,SAAS,KAAK,QAAQ,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,IAAI,OAAO,OAAO,WAAW,OAAO,SAAS,EAAE;AAAA,QACnG,YAAY,CAAC;AAAA,QACb,SAAS;AAAA,QACT,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,50 @@
1
+ /** One row's delta on the wire. */
2
+ export interface SyncWireChange {
3
+ entity: string;
4
+ id: string;
5
+ op: "upsert" | "delete";
6
+ data?: Record<string, unknown> | null;
7
+ /** The device's edit time on a push (D2). */
8
+ updated_at?: string | null;
9
+ /** The server's id of the row, on a pull (kastlan). */
10
+ pk?: number | null;
11
+ knowledge?: number | null;
12
+ }
13
+ /** Where a paged pull stopped (server-kit `SyncPageCursor`); handed back as it came. */
14
+ export interface SyncPageCursor {
15
+ entity: string;
16
+ knowledge?: number | null;
17
+ id?: string | null;
18
+ window_end: number;
19
+ }
20
+ export interface SyncRequestBody {
21
+ scope: string;
22
+ last_knowledge: number;
23
+ changes: SyncWireChange[];
24
+ /** This client keeps refused changes and reads `refused` and `rejections` (D1). */
25
+ refusals: true;
26
+ /** The device's clock as the body is built, fresh for every request (D2 amendment). */
27
+ client_now: string;
28
+ paged?: boolean;
29
+ page_cursor?: SyncPageCursor | null;
30
+ }
31
+ /** The WHOLE push refused (a lapsed plan): the pull is served all the same. */
32
+ export interface SyncRefusalBody {
33
+ code: string;
34
+ detail: string;
35
+ change_ids: string[];
36
+ }
37
+ /** One pushed change the server did not apply, with its reason. */
38
+ export interface SyncRejectionBody {
39
+ change_id: string;
40
+ code: string;
41
+ detail: string;
42
+ }
43
+ export interface SyncResponseBody {
44
+ server_knowledge: number;
45
+ changes: SyncWireChange[];
46
+ refused?: SyncRefusalBody | null;
47
+ rejections?: SyncRejectionBody[];
48
+ /** Set while the pull did not fit one page; `null` on the last page (D12). */
49
+ page_cursor?: SyncPageCursor | null;
50
+ }
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/sync.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { createSyncDb, SYNC_DB_STORES } from "./sync/db.js";
2
+ export type { CreateSyncDbOptions, OutboxChange, OutboxStatus, SyncDb, SyncDbUpgrade, SyncedRow, SyncMetaEntry } from "./sync/db.js";
3
+ export { createSyncEngine, DEFAULT_HOLD_CODES, DEFAULT_SYNC_BACKOFF_MS, releaseSyncEngine, syncEngineFor } from "./sync/engine.js";
4
+ export type { SyncCounts, SyncEngine, SyncEngineOptions, SyncEngineState, SyncRoundResult, SyncStep, SyncStepContext, SyncStepResult, } from "./sync/engine.js";
5
+ export { useSyncQuery, useSyncStatus, useSyncWorker } from "./sync/hooks.js";
6
+ export type { SyncPageCursor, SyncRefusalBody, SyncRejectionBody, SyncRequestBody, SyncResponseBody, SyncWireChange, } from "./sync/wire.js";
7
+ export { isOfflineError, readLocalFirst, stampAfter, writeLocalFirst } from "./offline/local-first.js";
package/dist/sync.js ADDED
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { createSyncDb, SYNC_DB_STORES } from "./sync/db.js";
3
+ import { createSyncEngine, DEFAULT_HOLD_CODES, DEFAULT_SYNC_BACKOFF_MS, releaseSyncEngine, syncEngineFor } from "./sync/engine.js";
4
+ import { useSyncQuery, useSyncStatus, useSyncWorker } from "./sync/hooks.js";
5
+ import { isOfflineError, readLocalFirst, stampAfter, writeLocalFirst } from "./offline/local-first.js";
6
+ export {
7
+ DEFAULT_HOLD_CODES,
8
+ DEFAULT_SYNC_BACKOFF_MS,
9
+ SYNC_DB_STORES,
10
+ createSyncDb,
11
+ createSyncEngine,
12
+ isOfflineError,
13
+ readLocalFirst,
14
+ releaseSyncEngine,
15
+ stampAfter,
16
+ syncEngineFor,
17
+ useSyncQuery,
18
+ useSyncStatus,
19
+ useSyncWorker,
20
+ writeLocalFirst
21
+ };
22
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sync.ts"],"sourcesContent":["// `@eifi1/ui-kit/sync` — local-first phase 3 (docs/local-first-harmonization.md §2.3, 0.46):\n// the sync engine on a Dexie copy, its worker and status hooks, and the live read of the copy.\n//\n// The only entry point that imports `dexie` — an optional peer for that reason. It never loads\n// `/offline`'s TanStack and idb-keyval peers: the leaf utilities both entries export live in\n// one module of their own (src/offline/local-first.ts). NOT re-exported from the main barrel.\nexport { createSyncDb, SYNC_DB_STORES } from \"./sync/db\";\nexport type { CreateSyncDbOptions, OutboxChange, OutboxStatus, SyncDb, SyncDbUpgrade, SyncedRow, SyncMetaEntry } from \"./sync/db\";\nexport { createSyncEngine, DEFAULT_HOLD_CODES, DEFAULT_SYNC_BACKOFF_MS, releaseSyncEngine, syncEngineFor } from \"./sync/engine\";\nexport type {\n SyncCounts,\n SyncEngine,\n SyncEngineOptions,\n SyncEngineState,\n SyncRoundResult,\n SyncStep,\n SyncStepContext,\n SyncStepResult,\n} from \"./sync/engine\";\nexport { useSyncQuery, useSyncStatus, useSyncWorker } from \"./sync/hooks\";\nexport type {\n SyncPageCursor,\n SyncRefusalBody,\n SyncRejectionBody,\n SyncRequestBody,\n SyncResponseBody,\n SyncWireChange,\n} from \"./sync/wire\";\nexport { isOfflineError, readLocalFirst, stampAfter, writeLocalFirst } from \"./offline/local-first\";\n"],"mappings":";AAMA,SAAS,cAAc,sBAAsB;AAE7C,SAAS,kBAAkB,oBAAoB,yBAAyB,mBAAmB,qBAAqB;AAWhH,SAAS,cAAc,eAAe,qBAAqB;AAS3D,SAAS,gBAAgB,gBAAgB,YAAY,uBAAuB;","names":[]}
@@ -5,6 +5,7 @@ const DEFAULT_TOUR_LABELS = {
5
5
  skip: "Skip",
6
6
  done: "Done",
7
7
  awaitClickHint: "Click the highlighted element to continue",
8
+ awaitInputHint: "Enter a value in the highlighted field to continue",
8
9
  step: (c, t) => `${c} / ${t}`
9
10
  };
10
11
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tour/tour-labels.ts"],"sourcesContent":["// The English words of ./tour, in a module with no component code: the English catalogue\n// (src/i18n/defaults.ts) imports them without the component, so `en` loads as one chunk\n// like the other languages (0.34, KS U8). ./tour re-exports them.\n\nimport type { TourLabels } from \"./tour\";\n\nexport const DEFAULT_TOUR_LABELS: TourLabels = {\n next: \"Next\",\n back: \"Back\",\n skip: \"Skip\",\n done: \"Done\",\n awaitClickHint: \"Click the highlighted element to continue\",\n step: (c, t) => `${c} / ${t}`,\n};\n"],"mappings":";AAMO,MAAM,sBAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;AAC7B;","names":[]}
1
+ {"version":3,"sources":["../../src/tour/tour-labels.ts"],"sourcesContent":["// The English words of ./tour, in a module with no component code: the English catalogue\n// (src/i18n/defaults.ts) imports them without the component, so `en` loads as one chunk\n// like the other languages (0.34, KS U8). ./tour re-exports them.\n\nimport type { TourLabels } from \"./tour\";\n\nexport const DEFAULT_TOUR_LABELS: TourLabels = {\n next: \"Next\",\n back: \"Back\",\n skip: \"Skip\",\n done: \"Done\",\n awaitClickHint: \"Click the highlighted element to continue\",\n awaitInputHint: \"Enter a value in the highlighted field to continue\",\n step: (c, t) => `${c} / ${t}`,\n};\n"],"mappings":";AAMO,MAAM,sBAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;AAC7B;","names":[]}
@@ -30,6 +30,34 @@ export interface TourStep {
30
30
  * first, then the tour steps forward on a microtask. The spotlight hole stays
31
31
  * click-through to the target; the rest of the page does not. */
32
32
  awaitClick?: boolean;
33
+ /**
34
+ * The spotlighted control is for USING, not only for looking at (0.46, keksdose live
35
+ * #436): the layers it opens sit above the scrim and work. A plain step's overlay is
36
+ * `z-[60]`, over every kit layer, so a field whose number pad (`NumberPadSheet`), a
37
+ * select's menu or a popover opens at `z-50` opened UNDER the shields, where nothing
38
+ * could be tapped. Here the overlay drops to `z-[45]`: still over the top bar and the
39
+ * page (`z-40` and below, still dimmed and shielded), under every `z-50` layer the
40
+ * target opens — so an app's own layer wants `z-50` or more to clear it. Keys pressed
41
+ * in the target or in such a layer are theirs (Escape closes the pad, arrows walk a
42
+ * list); from the card the tour's keys work as on any step. The overlay is not
43
+ * `aria-modal` then, and carries `data-tour-interactive`.
44
+ *
45
+ * The card is in the same layer as the scrim, so an open pad or sheet may cover it
46
+ * until it closes. Requires `target`. A `HoverMenu` (`z-40`, the top bar's menus) stays
47
+ * under the scrim: point an `awaitClick` step at its trigger instead.
48
+ */
49
+ interactive?: boolean;
50
+ /**
51
+ * Advance once a value was ENTERED in the spotlighted control (0.46, keksdose live
52
+ * #436) — `awaitClick` for a field. Implies `interactive`. Counted when focus leaves
53
+ * the target (the number pad's Done, a Tab, a tap elsewhere), or Enter is pressed in
54
+ * it, and one of its fields (`input`, `textarea`, `select`: the target itself or
55
+ * inside it) then holds another value than when the step began; a native `change`
56
+ * (a select, a checkbox) counts at once. The card swaps Next for `awaitInputHint`,
57
+ * as for `awaitClick`. A picker whose value is not a field's (the kit's `Select`
58
+ * button) has nothing to compare: call `useTour().next()` from its `onChange`.
59
+ */
60
+ awaitInput?: boolean;
33
61
  /** Runs before the step is shown — e.g. navigate to a route, open a panel.
34
62
  * May be async; the step waits for it, then locates the target. */
35
63
  beforeStep?: () => void | Promise<void>;
@@ -41,6 +69,8 @@ export interface TourLabels {
41
69
  done: string;
42
70
  /** Shown in place of the Next button on an `awaitClick` step. */
43
71
  awaitClickHint: string;
72
+ /** Shown in place of the Next button on an `awaitInput` step (0.46). */
73
+ awaitInputHint: string;
44
74
  /** Step counter, e.g. (2, 7) => "2 / 7". */
45
75
  step: (current: number, total: number) => string;
46
76
  }
package/dist/tour/tour.js CHANGED
@@ -225,14 +225,51 @@ function TourOverlay({
225
225
  el.addEventListener("click", onClick, { once: true });
226
226
  return () => el.removeEventListener("click", onClick);
227
227
  }, [ready, spotted, index, step, onNext]);
228
+ const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput);
229
+ useEffect(() => {
230
+ if (!ready || !spotted || !step.awaitInput || !step.target) return;
231
+ const el = queryVisibleTarget(step.target);
232
+ if (!el) return;
233
+ const read = () => fieldValues(el);
234
+ const before = read();
235
+ let timer = 0;
236
+ let done = false;
237
+ const check = () => {
238
+ window.clearTimeout(timer);
239
+ timer = window.setTimeout(() => {
240
+ if (done || read() === before) return;
241
+ done = true;
242
+ onNext();
243
+ }, 0);
244
+ };
245
+ const onFocusOut = (e) => {
246
+ if (e.relatedTarget instanceof Node && el.contains(e.relatedTarget)) return;
247
+ check();
248
+ };
249
+ const onKeyDown = (e) => {
250
+ if (e.key === "Enter") check();
251
+ };
252
+ el.addEventListener("focusout", onFocusOut);
253
+ el.addEventListener("keydown", onKeyDown);
254
+ el.addEventListener("change", check);
255
+ return () => {
256
+ window.clearTimeout(timer);
257
+ el.removeEventListener("focusout", onFocusOut);
258
+ el.removeEventListener("keydown", onKeyDown);
259
+ el.removeEventListener("change", check);
260
+ };
261
+ }, [ready, spotted, index, step, onNext]);
228
262
  useEffect(() => {
229
263
  const onKey = (e) => {
264
+ if (interactive && e.target instanceof Node && e.target !== document.body && !rootRef.current?.contains(e.target)) {
265
+ return;
266
+ }
230
267
  if (e.key === "Escape") {
231
268
  onSkip();
232
269
  return;
233
270
  }
234
271
  if (e.key === "Enter") {
235
- if (step.awaitClick || ownsKey(e.target, "Enter")) return;
272
+ if (step.awaitClick || step.awaitInput || ownsKey(e.target, "Enter")) return;
236
273
  e.preventDefault();
237
274
  onNext();
238
275
  return;
@@ -248,7 +285,7 @@ function TourOverlay({
248
285
  };
249
286
  document.addEventListener("keydown", onKey);
250
287
  return () => document.removeEventListener("keydown", onKey);
251
- }, [isFirst, step, onNext, onPrev, onSkip]);
288
+ }, [isFirst, step, interactive, onNext, onPrev, onSkip]);
252
289
  if (!ready) return null;
253
290
  const pad = step.padding ?? 8;
254
291
  const spot = rect ? { top: rect.top - pad, left: rect.left - pad, width: rect.width + pad * 2, height: rect.height + pad * 2 } : null;
@@ -257,9 +294,10 @@ function TourOverlay({
257
294
  "div",
258
295
  {
259
296
  ref: rootRef,
260
- className: "pointer-events-none fixed inset-0 z-[60]",
297
+ className: cn("pointer-events-none fixed inset-0", interactive ? "z-[45]" : "z-[60]"),
261
298
  role: "dialog",
262
- "aria-modal": "true",
299
+ "aria-modal": interactive ? void 0 : "true",
300
+ "data-tour-interactive": interactive ? "" : void 0,
263
301
  "aria-label": step.title,
264
302
  dir,
265
303
  children: [
@@ -318,6 +356,12 @@ function TourOverlay({
318
356
  document.body
319
357
  );
320
358
  }
359
+ function fieldValues(el) {
360
+ const fields = el.matches("input,textarea,select") ? [el] : Array.from(el.querySelectorAll("input,textarea,select"));
361
+ return fields.map(
362
+ (f) => f instanceof HTMLInputElement && (f.type === "checkbox" || f.type === "radio") ? String(f.checked) : f.value
363
+ ).join("\0");
364
+ }
321
365
  function ownsKey(target, key) {
322
366
  if (!(target instanceof HTMLElement)) return false;
323
367
  if (target.isContentEditable) return true;
@@ -435,10 +479,10 @@ function TourCard({
435
479
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
436
480
  /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(index + 1, total) }),
437
481
  !isFirst && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onBack, className: "px-2.5 py-1 text-xs", children: labels.back }),
438
- !(step.awaitClick && step.target && rect) && /* @__PURE__ */ jsx(Button, { variant: "brand", onClick: onNext, className: cn("px-2.5 py-1 text-xs"), children: isLast ? labels.done : labels.next })
482
+ !((step.awaitClick || step.awaitInput) && step.target && rect) && /* @__PURE__ */ jsx(Button, { variant: "brand", onClick: onNext, className: cn("px-2.5 py-1 text-xs"), children: isLast ? labels.done : labels.next })
439
483
  ] })
440
484
  ] }),
441
- step.awaitClick && step.target && rect && /* @__PURE__ */ jsx("div", { className: "text-center text-xs italic text-placeholder", children: labels.awaitClickHint })
485
+ (step.awaitClick || step.awaitInput) && step.target && rect && /* @__PURE__ */ jsx("div", { className: "text-center text-xs italic text-placeholder", children: step.awaitInput && !step.awaitClick ? labels.awaitInputHint : labels.awaitClickHint })
442
486
  ] })
443
487
  ]
444
488
  }