@k2b/ui 0.13.0 → 0.15.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 (74) hide show
  1. package/dist/browser/content/FileGrid.js +28 -8
  2. package/dist/browser/content/FileGrid.js.map +1 -1
  3. package/dist/browser/content/MarkdownView.js +21 -7
  4. package/dist/browser/content/MarkdownView.js.map +1 -1
  5. package/dist/browser/content/MessageRow.js +1250 -0
  6. package/dist/browser/content/MessageRow.js.map +1 -0
  7. package/dist/browser/content/VirtualFeed.js +1127 -0
  8. package/dist/browser/content/VirtualFeed.js.map +1 -0
  9. package/dist/browser/content/collection-selection.js +27 -11
  10. package/dist/browser/content/collection-selection.js.map +1 -1
  11. package/dist/browser/content/index.js +3 -0
  12. package/dist/browser/content/index.js.map +1 -1
  13. package/dist/browser/content/markdown-info-blocks.js +363 -0
  14. package/dist/browser/content/markdown-info-blocks.js.map +1 -0
  15. package/dist/browser/feedback/InstallGuide.js +199 -145
  16. package/dist/browser/feedback/InstallGuide.js.map +1 -1
  17. package/dist/browser/feedback/dialog-core.js +6 -1
  18. package/dist/browser/feedback/dialog-core.js.map +1 -1
  19. package/dist/browser/feedback/install.js +8 -4
  20. package/dist/browser/feedback/install.js.map +1 -1
  21. package/dist/browser/inputs/QrScanner.js +101 -21
  22. package/dist/browser/inputs/QrScanner.js.map +1 -1
  23. package/dist/browser/intl/messages.js +92 -0
  24. package/dist/browser/intl/messages.js.map +1 -1
  25. package/dist/browser/layout/MobileShell.js +48 -6
  26. package/dist/browser/layout/MobileShell.js.map +1 -1
  27. package/dist/browser/layout/TabBar.js +8 -4
  28. package/dist/browser/layout/TabBar.js.map +1 -1
  29. package/dist/browser/surfaces/Avatar.js +33 -17
  30. package/dist/browser/surfaces/Avatar.js.map +1 -1
  31. package/dist/ssr/content/FileGrid.js +17 -3
  32. package/dist/ssr/content/FileGrid.js.map +1 -1
  33. package/dist/ssr/content/MarkdownView.js +21 -7
  34. package/dist/ssr/content/MarkdownView.js.map +1 -1
  35. package/dist/ssr/content/MessageRow.js +796 -0
  36. package/dist/ssr/content/MessageRow.js.map +1 -0
  37. package/dist/ssr/content/VirtualFeed.js +989 -0
  38. package/dist/ssr/content/VirtualFeed.js.map +1 -0
  39. package/dist/ssr/content/collection-selection.js +27 -11
  40. package/dist/ssr/content/collection-selection.js.map +1 -1
  41. package/dist/ssr/content/index.js +3 -0
  42. package/dist/ssr/content/index.js.map +1 -1
  43. package/dist/ssr/content/markdown-info-blocks.js +363 -0
  44. package/dist/ssr/content/markdown-info-blocks.js.map +1 -0
  45. package/dist/ssr/feedback/InstallGuide.js +150 -98
  46. package/dist/ssr/feedback/InstallGuide.js.map +1 -1
  47. package/dist/ssr/feedback/dialog-core.js +6 -1
  48. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  49. package/dist/ssr/feedback/install.js +8 -4
  50. package/dist/ssr/feedback/install.js.map +1 -1
  51. package/dist/ssr/inputs/QrScanner.js +101 -21
  52. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  53. package/dist/ssr/intl/messages.js +92 -0
  54. package/dist/ssr/intl/messages.js.map +1 -1
  55. package/dist/ssr/layout/MobileShell.js +48 -6
  56. package/dist/ssr/layout/MobileShell.js.map +1 -1
  57. package/dist/ssr/layout/TabBar.js +3 -2
  58. package/dist/ssr/layout/TabBar.js.map +1 -1
  59. package/dist/ssr/surfaces/Avatar.js +14 -1
  60. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  61. package/dist/styles.css +1 -1
  62. package/dist/types/content/FileGrid.d.ts +6 -1
  63. package/dist/types/content/MarkdownView.d.ts +14 -2
  64. package/dist/types/content/MessageRow.d.ts +168 -0
  65. package/dist/types/content/VirtualFeed.d.ts +81 -0
  66. package/dist/types/content/collection-selection.d.ts +15 -1
  67. package/dist/types/content/index.d.ts +5 -0
  68. package/dist/types/content/markdown-info-blocks.d.ts +67 -0
  69. package/dist/types/feedback/InstallGuide.d.ts +5 -3
  70. package/dist/types/feedback/install.d.ts +4 -3
  71. package/dist/types/inputs/QrScanner.d.ts +10 -2
  72. package/dist/types/intl/messages.d.ts +46 -0
  73. package/dist/types/layout/TabBar.d.ts +4 -1
  74. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["feedback/dialog-core.ts"],"sourcesContent":["import type { JSX } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { getK2bPortalRoot } from \"../internal/portal\";\nimport { resolveUiMessages } from \"../intl/messages\";\nimport { isPointInsideToast } from \"./toast\";\n\nexport type DialogClose<T> = (result?: T) => void;\n\nexport type OpenDialogOptions = {\n /** Close only this dialog when its owning operation is cancelled. */\n signal?: AbortSignal;\n panelClassName?: string;\n contentClassName?: string;\n initialFocus?: \"first-input\" | \"none\" | ((dialog: HTMLDialogElement) => HTMLElement | null);\n cancelBehavior?: \"resolve-undefined\" | \"ignore\";\n ariaLabel?: string;\n /**\n * Add a same-URL history entry while the dialog is open, so the browser's or the phone's Back closes the dialog\n * instead of leaving the page. Closing the dialog removes the entry again; the returned promise settles after that.\n */\n history?: boolean;\n};\n\nexport type DialogRender<T> = (\n close: DialogClose<T>,\n context: {\n dialog: HTMLDialogElement;\n /** Switch this entry between a modal and a modeless window without remounting. */\n setModal: (modal: boolean) => void;\n /** Set viewport coordinates, or restore the panel stylesheet position with null. */\n setPosition: (position: { x: number; y: number } | null) => void;\n /** Route Escape/backdrop through the same guarded handler as Cancel and X. */\n setDismissHandler: (handler: () => void | Promise<void>) => void;\n /** Request dismissal through this entry's cancellation policy and guard. */\n requestDismiss: () => Promise<void>;\n },\n) => JSX.Element;\n\nexport type DialogCore = {\n open: <T>(view: DialogRender<T>, options?: OpenDialogOptions) => Promise<T | undefined>;\n close: (result?: unknown) => void;\n isOpen: () => boolean;\n};\n\ntype DialogStackEntry = {\n container: HTMLDivElement;\n dispose?: () => void;\n resolve?: (value: unknown) => void;\n panelClassName: string;\n cancelBehavior: NonNullable<OpenDialogOptions[\"cancelBehavior\"]>;\n initialFocus: NonNullable<OpenDialogOptions[\"initialFocus\"]>;\n opener?: HTMLElement;\n openerRing: boolean;\n ariaLabel?: string;\n dismissHandler?: () => void | Promise<void>;\n dismissPending?: boolean;\n modal: boolean;\n position?: { x: number; y: number } | null;\n /** The marker of this dialog's history entry, for dialogs opened with `history`. */\n historyId?: number;\n};\n\ntype DialogState = {\n element?: HTMLDialogElement;\n modal?: boolean;\n stack: DialogStackEntry[];\n scrollLocked?: boolean;\n previousBodyOverflow?: string;\n previousHtmlOverflow?: string;\n mouseDownOnDialog?: boolean;\n connectionObserver?: MutationObserver;\n};\n\nconst DEFAULT_PANEL_CLASS = \"k2b-dialog\";\nconst DEFAULT_CONTENT_CLASS = \"k2b-dialog__viewport\";\nlet nextDialogTitleId = 0;\n/** The history state key that marks a dialog's same-URL entry. */\nconst HISTORY_MARKER = \"k2bDialog\";\n\nconst historyRecord = (): Record<string, unknown> =>\n typeof history.state === \"object\" && history.state !== null ? { ...history.state } : {};\nconst historyMarker = (): unknown => historyRecord()[HISTORY_MARKER];\n\nconst resolveInitialFocusTarget = (entry: DialogStackEntry, dialog: HTMLDialogElement): HTMLElement | null => {\n const { initialFocus } = entry;\n if (initialFocus === \"none\") return null;\n if (typeof initialFocus === \"function\") return initialFocus(dialog);\n const input = entry.container.querySelector<HTMLElement>(\n \"input:not([type='hidden']):not([disabled]), textarea:not([disabled]), select:not([disabled]), [role='combobox']:not([disabled]):not([aria-disabled='true'])\",\n );\n return (\n input ??\n entry.container.querySelector<HTMLElement>(\n \"input:not([type='hidden']):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), a[href], [tabindex]:not([tabindex='-1'])\",\n )\n );\n};\n\nconst applyAccessibleName = (dialog: HTMLDialogElement, entry: DialogStackEntry): void => {\n dialog.removeAttribute(\"aria-label\");\n dialog.removeAttribute(\"aria-labelledby\");\n if (entry.ariaLabel) {\n dialog.setAttribute(\"aria-label\", entry.ariaLabel);\n return;\n }\n const heading = entry.container.querySelector<HTMLElement>(\"h1, h2, h3\");\n if (!heading) {\n dialog.setAttribute(\"aria-label\", resolveUiMessages().dialog);\n return;\n }\n heading.id ||= `k2b-dialog-title-${++nextDialogTitleId}`;\n dialog.setAttribute(\"aria-labelledby\", heading.id);\n};\n\nconst schedule = (callback: () => void): void => {\n if (typeof requestAnimationFrame === \"function\") requestAnimationFrame(callback);\n else queueMicrotask(callback);\n};\n\n/**\n * Whether focus is still where closing a level left it, on the page or the\n * dialog: nothing took it before the next frame, neither a close handler nor a\n * Tab pressed right after Escape.\n */\nconst focusUnclaimed = (dialog: HTMLDialogElement): boolean => {\n const active = document.activeElement;\n return !active || active === document.body || active === dialog;\n};\n\nexport const createDialogCore = (): DialogCore => {\n const state: DialogState = { stack: [] };\n /** Markers of this core's entries in the browser history, oldest first. */\n let historyIds: number[] = [];\n let nextHistoryId = 0;\n let historySyncScheduled = false;\n let listeningToHistory = false;\n /** Settles when the synthetic Back that removes closed dialogs' entries has arrived. */\n let returning: Promise<void> | undefined;\n\n /** Closed dialogs leave the history with one Back to the newest dialog that is still open, or to the page. */\n const syncHistory = () => {\n historySyncScheduled = false;\n const openIds = new Set(state.stack.map((entry) => entry.historyId));\n const current = historyIds.at(-1);\n let steps = 0;\n while (historyIds.length > 0 && !openIds.has(historyIds.at(-1))) {\n historyIds.pop();\n steps++;\n }\n // Someone else moved the history meanwhile; leave it alone.\n if (steps === 0 || historyMarker() !== current) return;\n const back = new Promise<void>((resolve) => window.addEventListener(\"popstate\", () => resolve(), { once: true }));\n returning = back;\n void back.then(() => {\n if (returning === back) returning = undefined;\n });\n history.go(-steps);\n };\n\n const releaseHistory = (entry: DialogStackEntry) => {\n if (entry.historyId === undefined || historySyncScheduled) return;\n historySyncScheduled = true;\n queueMicrotask(syncHistory);\n };\n\n /** Back or Forward moved to another entry: close every dialog opened after the one it shows. */\n const onPopState = () => {\n const marker = historyMarker();\n const index = typeof marker === \"number\" ? historyIds.indexOf(marker) : -1;\n historyIds = historyIds.slice(0, index + 1);\n const gone = (entry: DialogStackEntry) => entry.historyId !== undefined && !historyIds.includes(entry.historyId);\n while (state.stack.some(gone)) popTop(undefined);\n };\n\n const pushHistory = (entry: DialogStackEntry) => {\n if (!listeningToHistory) {\n window.addEventListener(\"popstate\", onPopState);\n listeningToHistory = true;\n }\n const previous = historyRecord();\n // A reload or Forward never brings back a dialog or its sensitive transient state.\n if (historyIds.length === 0 && previous[HISTORY_MARKER] !== undefined) {\n delete previous[HISTORY_MARKER];\n history.replaceState(previous, \"\");\n }\n entry.historyId = ++nextHistoryId;\n historyIds.push(entry.historyId);\n history.pushState({ ...previous, [HISTORY_MARKER]: entry.historyId }, \"\");\n };\n\n const ensureDialogElement = () => {\n if (typeof document === \"undefined\") throw new Error(\"@k2b/ui dialogs can only be opened in the browser\");\n // Reuse the shared element whenever it is still in the document. Resolving\n // the portal root first would create a second <dialog> when focus has moved\n // into another `.k2b-ui` scope, orphaning the levels already on the stack.\n if (state.element?.isConnected) return state.element;\n\n const element = document.createElement(\"dialog\");\n element.onclose = () => {\n // Repeated Escape can force a non-cancelable native close. A retained\n // entry (ignore, an async guard, or a parent) still owns the modal.\n const top = state.stack[state.stack.length - 1];\n if (state.element !== element || element.open || !top) return;\n if (top.modal) element.showModal();\n else element.show();\n resolveInitialFocusTarget(top, element)?.focus();\n };\n getK2bPortalRoot().appendChild(element);\n state.element = element;\n return element;\n };\n\n const lockPageScroll = () => {\n if (typeof document === \"undefined\" || state.scrollLocked) return;\n state.previousBodyOverflow = document.body.style.overflow;\n state.previousHtmlOverflow = document.documentElement.style.overflow;\n document.body.style.overflow = \"hidden\";\n document.documentElement.style.overflow = \"hidden\";\n state.scrollLocked = true;\n };\n\n const unlockPageScroll = () => {\n if (typeof document === \"undefined\" || !state.scrollLocked) return;\n document.body.style.overflow = state.previousBodyOverflow ?? \"\";\n document.documentElement.style.overflow = state.previousHtmlOverflow ?? \"\";\n state.scrollLocked = false;\n state.previousBodyOverflow = undefined;\n state.previousHtmlOverflow = undefined;\n };\n\n const applyPresentation = (entry: DialogStackEntry) => {\n const dialog = state.element;\n if (!dialog || state.stack[state.stack.length - 1] !== entry) return;\n const position = entry.position;\n dialog.style.position = position ? \"fixed\" : \"\";\n dialog.style.inset = position ? `${position.y}px auto auto ${position.x}px` : \"\";\n dialog.style.margin = position ? \"0\" : \"\";\n dialog.style.transform = position ? \"none\" : \"\";\n dialog.dataset.modeless = String(!entry.modal);\n dialog.setAttribute(\"aria-modal\", String(entry.modal));\n if (dialog.open && state.modal !== entry.modal) {\n const active = document.activeElement;\n dialog.close();\n if (entry.modal) dialog.showModal();\n else dialog.show();\n if (active instanceof HTMLElement && active.isConnected && (!entry.modal || dialog.contains(active))) {\n active.focus({ preventScroll: true });\n } else resolveInitialFocusTarget(entry, dialog)?.focus({ preventScroll: true });\n }\n state.modal = entry.modal;\n if (entry.modal) lockPageScroll();\n else unlockPageScroll();\n };\n\n const modelessEscape = (event: KeyboardEvent) => {\n const top = state.stack[state.stack.length - 1];\n if (!top || top.modal || event.key !== \"Escape\" || event.defaultPrevented || event.isComposing) return;\n event.preventDefault();\n void requestDismiss(top);\n };\n\n const stopConnectionObserver = () => {\n document.removeEventListener(\"keydown\", modelessEscape);\n state.connectionObserver?.disconnect();\n state.connectionObserver = undefined;\n };\n\n const applyCancelBehavior = (\n dialog: HTMLDialogElement,\n close: () => void,\n behavior: NonNullable<OpenDialogOptions[\"cancelBehavior\"]>,\n ) => {\n dialog.oncancel = (event) => {\n if (behavior === \"ignore\") {\n event.preventDefault();\n return;\n }\n event.preventDefault();\n close();\n };\n dialog.onmousedown = (event) => {\n state.mouseDownOnDialog = event.target === dialog;\n };\n dialog.onclick = (event) => {\n const realBackdropClick = state.mouseDownOnDialog === true;\n state.mouseDownOnDialog = false;\n if (event.target !== dialog || !state.modal) return;\n if (!realBackdropClick) return;\n if (behavior === \"ignore\") return;\n if (isPointInsideToast(event.clientX, event.clientY)) return;\n close();\n };\n };\n\n const requestDismiss = async (entry: DialogStackEntry) => {\n if (state.stack[state.stack.length - 1] !== entry || entry.dismissPending || entry.cancelBehavior === \"ignore\") return;\n if (!entry.dismissHandler) {\n popTop(undefined);\n return;\n }\n entry.dismissPending = true;\n try {\n await entry.dismissHandler();\n } catch (error) {\n // A failed guard must never discard the dialog's state.\n console.error(\"Dialog dismissal failed\", error);\n } finally {\n entry.dismissPending = false;\n }\n };\n\n const popTop = (result?: unknown) => {\n const top = state.stack.pop();\n if (!top) return;\n releaseHistory(top);\n top.dispose?.();\n top.container.remove();\n\n const dialog = state.element;\n const previous = state.stack[state.stack.length - 1];\n if (previous && dialog) {\n previous.container.style.display = \"\";\n dialog.className = previous.panelClassName;\n applyPresentation(previous);\n applyAccessibleName(dialog, previous);\n applyCancelBehavior(dialog, () => void requestDismiss(previous), previous.cancelBehavior);\n schedule(() => {\n if (!focusUnclaimed(dialog)) return;\n if (top.opener?.isConnected) returnFocus(top.opener, top.openerRing);\n else resolveInitialFocusTarget(previous, dialog)?.focus();\n });\n } else if (dialog) {\n dialog.oncancel = null;\n dialog.onmousedown = null;\n dialog.onclick = null;\n dialog.removeAttribute(\"aria-label\");\n dialog.removeAttribute(\"aria-labelledby\");\n if (dialog.open && typeof dialog.close === \"function\") dialog.close();\n else dialog.removeAttribute(\"open\");\n dialog.remove();\n state.element = undefined;\n stopConnectionObserver();\n unlockPageScroll();\n // After the Escape that closed it: WebKit lights the ring of whatever\n // that key press leaves focused.\n schedule(() => {\n if (top.opener?.isConnected && focusUnclaimed(dialog)) returnFocus(top.opener, top.openerRing);\n });\n }\n\n top.resolve?.(result);\n };\n\n const resetDisconnectedDialog = () => {\n if (state.stack.length === 0 || state.element?.isConnected) return;\n const entries = state.stack.splice(0);\n const dialog = state.element;\n state.element = undefined;\n stopConnectionObserver();\n dialog?.remove();\n unlockPageScroll();\n for (const entry of entries.reverse()) {\n releaseHistory(entry);\n entry.dispose?.();\n entry.container.remove();\n entry.resolve?.(undefined);\n }\n };\n\n const observeConnection = () => {\n if (state.connectionObserver || typeof MutationObserver === \"undefined\") return;\n document.addEventListener(\"keydown\", modelessEscape);\n state.connectionObserver = new MutationObserver(resetDisconnectedDialog);\n state.connectionObserver.observe(document.documentElement, { childList: true, subtree: true });\n };\n\n const open = <T>(view: DialogRender<T>, options: OpenDialogOptions = {}): Promise<T | undefined> => {\n if (options.signal?.aborted) return Promise.resolve(undefined);\n if (options.history) {\n // Dialogs closed just now leave the history first, or this dialog's entry would be the one removed.\n if (historySyncScheduled) syncHistory();\n if (returning) return returning.then(() => open(view, options));\n }\n const dialog = ensureDialogElement();\n const previousTop = state.stack[state.stack.length - 1];\n if (previousTop) previousTop.container.style.display = \"none\";\n const activeElement = document.activeElement;\n\n const panelClassName = options.panelClassName ?? DEFAULT_PANEL_CLASS;\n const cancelBehavior = options.cancelBehavior ?? \"resolve-undefined\";\n const initialFocus = options.initialFocus ?? \"first-input\";\n dialog.className = panelClassName;\n\n const container = document.createElement(\"div\");\n container.className = options.contentClassName ?? DEFAULT_CONTENT_CLASS;\n dialog.appendChild(container);\n\n const entry: DialogStackEntry = {\n container,\n panelClassName,\n cancelBehavior,\n initialFocus,\n opener: activeElement instanceof HTMLElement ? activeElement : undefined,\n openerRing: ringOnReturn(activeElement),\n ariaLabel: options.ariaLabel,\n modal: true,\n };\n\n return new Promise((resolve, reject) => {\n const abort = () => {\n const index = state.stack.indexOf(entry);\n if (index < 0) return;\n if (index === state.stack.length - 1) popTop(undefined);\n else {\n state.stack.splice(index, 1);\n releaseHistory(entry);\n entry.dispose?.();\n entry.container.remove();\n entry.resolve?.(undefined);\n }\n };\n entry.resolve = (value) => {\n options.signal?.removeEventListener(\"abort\", abort);\n if (entry.historyId === undefined) {\n resolve(value as T | undefined);\n return;\n }\n // After the history sync, so a caller that navigates or opens the next dialog finds the history settled.\n queueMicrotask(() => void (returning ?? Promise.resolve()).then(() => resolve(value as T | undefined)));\n };\n const closeTyped: DialogClose<T> = (result) => {\n if (state.stack[state.stack.length - 1] !== entry) return;\n popTop(result);\n };\n\n if (options.history) pushHistory(entry);\n state.stack.push(entry);\n try {\n entry.dispose = render(\n () =>\n view(closeTyped, {\n dialog,\n setModal: (modal) => {\n entry.modal = modal;\n applyPresentation(entry);\n },\n setPosition: (position) => {\n if (position && (!Number.isFinite(position.x) || !Number.isFinite(position.y))) return;\n entry.position = position;\n applyPresentation(entry);\n },\n requestDismiss: () => requestDismiss(entry),\n setDismissHandler: (handler) => {\n entry.dismissHandler = handler;\n },\n }),\n container,\n );\n applyPresentation(entry);\n applyAccessibleName(dialog, entry);\n applyCancelBehavior(dialog, () => void requestDismiss(entry), cancelBehavior);\n\n if (state.stack.length === 1) {\n // The native dialog returns focus to the element focused when it\n // opened, with a ring of the browser's choosing. Opening from a\n // blurred document leaves the return to popTop and its opener ring.\n if (activeElement instanceof HTMLElement) activeElement.blur();\n if (typeof dialog.showModal === \"function\") {\n if (entry.modal) dialog.showModal();\n else dialog.show();\n } else dialog.setAttribute(\"open\", \"\");\n observeConnection();\n }\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n if (options.signal?.aborted) abort();\n schedule(() => {\n if (state.stack[state.stack.length - 1] === entry) resolveInitialFocusTarget(entry, dialog)?.focus();\n });\n } catch (error) {\n entry.resolve = undefined;\n if (state.stack[state.stack.length - 1] === entry) popTop(undefined);\n reject(error);\n }\n });\n };\n\n const close: DialogCore[\"close\"] = (result) => {\n let first = true;\n while (state.stack.length > 0) {\n popTop(first ? result : undefined);\n first = false;\n }\n };\n\n return {\n open,\n close,\n isOpen: () => state.stack.length > 0,\n };\n};\n\nexport const dialogCore = createDialogCore();\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["feedback/dialog-core.ts"],"sourcesContent":["import type { JSX } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { getK2bPortalRoot } from \"../internal/portal\";\nimport { resolveUiMessages } from \"../intl/messages\";\nimport { isPointInsideToast } from \"./toast\";\n\nexport type DialogClose<T> = (result?: T) => void;\n\nexport type OpenDialogOptions = {\n /** Close only this dialog when its owning operation is cancelled. */\n signal?: AbortSignal;\n panelClassName?: string;\n contentClassName?: string;\n initialFocus?: \"first-input\" | \"none\" | ((dialog: HTMLDialogElement) => HTMLElement | null);\n cancelBehavior?: \"resolve-undefined\" | \"ignore\";\n ariaLabel?: string;\n /**\n * Add a same-URL history entry while the dialog is open, so the browser's or the phone's Back closes the dialog\n * instead of leaving the page. Closing the dialog removes the entry again; the returned promise settles after that.\n */\n history?: boolean;\n};\n\nexport type DialogRender<T> = (\n close: DialogClose<T>,\n context: {\n dialog: HTMLDialogElement;\n /** Switch this entry between a modal and a modeless window without remounting. */\n setModal: (modal: boolean) => void;\n /** Set viewport coordinates, or restore the panel stylesheet position with null. */\n setPosition: (position: { x: number; y: number } | null) => void;\n /** Route Escape/backdrop through the same guarded handler as Cancel and X. */\n setDismissHandler: (handler: () => void | Promise<void>) => void;\n /** Request dismissal through this entry's cancellation policy and guard. */\n requestDismiss: () => Promise<void>;\n },\n) => JSX.Element;\n\nexport type DialogCore = {\n open: <T>(view: DialogRender<T>, options?: OpenDialogOptions) => Promise<T | undefined>;\n close: (result?: unknown) => void;\n isOpen: () => boolean;\n};\n\ntype DialogStackEntry = {\n container: HTMLDivElement;\n dispose?: () => void;\n resolve?: (value: unknown) => void;\n panelClassName: string;\n cancelBehavior: NonNullable<OpenDialogOptions[\"cancelBehavior\"]>;\n initialFocus: NonNullable<OpenDialogOptions[\"initialFocus\"]>;\n opener?: HTMLElement;\n openerRing: boolean;\n ariaLabel?: string;\n dismissHandler?: () => void | Promise<void>;\n dismissPending?: boolean;\n modal: boolean;\n position?: { x: number; y: number } | null;\n /** The marker of this dialog's history entry, for dialogs opened with `history`. */\n historyId?: number;\n};\n\ntype DialogState = {\n element?: HTMLDialogElement;\n modal?: boolean;\n stack: DialogStackEntry[];\n scrollLocked?: boolean;\n previousBodyOverflow?: string;\n previousHtmlOverflow?: string;\n mouseDownOnDialog?: boolean;\n connectionObserver?: MutationObserver;\n};\n\nconst DEFAULT_PANEL_CLASS = \"k2b-dialog\";\nconst DEFAULT_CONTENT_CLASS = \"k2b-dialog__viewport\";\nlet nextDialogTitleId = 0;\n/** The history state key that marks a dialog's same-URL entry. */\nconst HISTORY_MARKER = \"k2bDialog\";\n\nconst historyRecord = (): Record<string, unknown> =>\n typeof history.state === \"object\" && history.state !== null ? { ...history.state } : {};\nconst historyMarker = (): unknown => historyRecord()[HISTORY_MARKER];\n\nconst resolveInitialFocusTarget = (entry: DialogStackEntry, dialog: HTMLDialogElement): HTMLElement | null => {\n const { initialFocus } = entry;\n if (initialFocus === \"none\") return null;\n if (typeof initialFocus === \"function\") return initialFocus(dialog);\n const input = entry.container.querySelector<HTMLElement>(\n \"input:not([type='hidden']):not([disabled]), textarea:not([disabled]), select:not([disabled]), [role='combobox']:not([disabled]):not([aria-disabled='true'])\",\n );\n return (\n input ??\n entry.container.querySelector<HTMLElement>(\n \"input:not([type='hidden']):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), a[href], [tabindex]:not([tabindex='-1'])\",\n )\n );\n};\n\nconst applyAccessibleName = (dialog: HTMLDialogElement, entry: DialogStackEntry): void => {\n dialog.removeAttribute(\"aria-label\");\n dialog.removeAttribute(\"aria-labelledby\");\n if (entry.ariaLabel) {\n dialog.setAttribute(\"aria-label\", entry.ariaLabel);\n return;\n }\n const heading = entry.container.querySelector<HTMLElement>(\"h1, h2, h3\");\n if (!heading) {\n dialog.setAttribute(\"aria-label\", resolveUiMessages().dialog);\n return;\n }\n heading.id ||= `k2b-dialog-title-${++nextDialogTitleId}`;\n dialog.setAttribute(\"aria-labelledby\", heading.id);\n};\n\nconst schedule = (callback: () => void): void => {\n if (typeof requestAnimationFrame === \"function\") requestAnimationFrame(callback);\n else queueMicrotask(callback);\n};\n\n/**\n * Whether focus is still where closing a level left it, on the page or the\n * dialog: nothing took it before the next frame, neither a close handler nor a\n * Tab pressed right after Escape.\n */\nconst focusUnclaimed = (dialog: HTMLDialogElement): boolean => {\n const active = document.activeElement;\n return !active || active === document.body || active === dialog;\n};\n\nexport const createDialogCore = (): DialogCore => {\n const state: DialogState = { stack: [] };\n /** Markers of this core's entries in the browser history, oldest first. */\n let historyIds: number[] = [];\n let nextHistoryId = 0;\n let historySyncScheduled = false;\n let listeningToHistory = false;\n /** Settles when the synthetic Back that removes closed dialogs' entries has arrived. */\n let returning: Promise<void> | undefined;\n\n /** Closed dialogs leave the history with one Back to the newest dialog that is still open, or to the page. */\n const syncHistory = () => {\n historySyncScheduled = false;\n const openIds = new Set(state.stack.map((entry) => entry.historyId));\n const current = historyIds.at(-1);\n let steps = 0;\n while (historyIds.length > 0 && !openIds.has(historyIds.at(-1))) {\n historyIds.pop();\n steps++;\n }\n // Someone else moved the history meanwhile; leave it alone.\n if (steps === 0 || historyMarker() !== current) return;\n const back = new Promise<void>((resolve) => window.addEventListener(\"popstate\", () => resolve(), { once: true }));\n returning = back;\n void back.then(() => {\n if (returning === back) returning = undefined;\n });\n history.go(-steps);\n };\n\n const releaseHistory = (entry: DialogStackEntry) => {\n if (entry.historyId === undefined || historySyncScheduled) return;\n historySyncScheduled = true;\n queueMicrotask(syncHistory);\n };\n\n /** Back or Forward moved to another entry: close every dialog opened after the one it shows. */\n const onPopState = () => {\n const marker = historyMarker();\n const index = typeof marker === \"number\" ? historyIds.indexOf(marker) : -1;\n historyIds = historyIds.slice(0, index + 1);\n const gone = (entry: DialogStackEntry) => entry.historyId !== undefined && !historyIds.includes(entry.historyId);\n while (state.stack.some(gone)) popTop(undefined);\n };\n\n const pushHistory = (entry: DialogStackEntry) => {\n if (!listeningToHistory) {\n window.addEventListener(\"popstate\", onPopState);\n listeningToHistory = true;\n }\n const previous = historyRecord();\n // A reload or Forward never brings back a dialog or its sensitive transient state.\n if (historyIds.length === 0 && previous[HISTORY_MARKER] !== undefined) {\n delete previous[HISTORY_MARKER];\n history.replaceState(previous, \"\");\n }\n entry.historyId = ++nextHistoryId;\n historyIds.push(entry.historyId);\n history.pushState({ ...previous, [HISTORY_MARKER]: entry.historyId }, \"\");\n };\n\n const ensureDialogElement = () => {\n if (typeof document === \"undefined\") throw new Error(\"@k2b/ui dialogs can only be opened in the browser\");\n // Reuse the shared element whenever it is still in the document. Resolving\n // the portal root first would create a second <dialog> when focus has moved\n // into another `.k2b-ui` scope, orphaning the levels already on the stack.\n if (state.element?.isConnected) return state.element;\n\n const element = document.createElement(\"dialog\");\n element.onclose = () => {\n // Repeated Escape can force a non-cancelable native close. A retained\n // entry (ignore, an async guard, or a parent) still owns the modal.\n const top = state.stack[state.stack.length - 1];\n if (state.element !== element || element.open || !top) return;\n if (top.modal) element.showModal();\n else element.show();\n resolveInitialFocusTarget(top, element)?.focus();\n };\n getK2bPortalRoot().appendChild(element);\n state.element = element;\n return element;\n };\n\n const lockPageScroll = () => {\n if (typeof document === \"undefined\" || state.scrollLocked) return;\n state.previousBodyOverflow = document.body.style.overflow;\n state.previousHtmlOverflow = document.documentElement.style.overflow;\n document.body.style.overflow = \"hidden\";\n document.documentElement.style.overflow = \"hidden\";\n state.scrollLocked = true;\n };\n\n const unlockPageScroll = () => {\n if (typeof document === \"undefined\" || !state.scrollLocked) return;\n document.body.style.overflow = state.previousBodyOverflow ?? \"\";\n document.documentElement.style.overflow = state.previousHtmlOverflow ?? \"\";\n state.scrollLocked = false;\n state.previousBodyOverflow = undefined;\n state.previousHtmlOverflow = undefined;\n };\n\n const applyPresentation = (entry: DialogStackEntry) => {\n const dialog = state.element;\n if (!dialog || state.stack[state.stack.length - 1] !== entry) return;\n const position = entry.position;\n dialog.style.position = position ? \"fixed\" : \"\";\n dialog.style.inset = position ? `${position.y}px auto auto ${position.x}px` : \"\";\n dialog.style.margin = position ? \"0\" : \"\";\n dialog.style.transform = position ? \"none\" : \"\";\n dialog.dataset.modeless = String(!entry.modal);\n dialog.setAttribute(\"aria-modal\", String(entry.modal));\n if (dialog.open && state.modal !== entry.modal) {\n const active = document.activeElement;\n dialog.close();\n if (entry.modal) dialog.showModal();\n else dialog.show();\n if (active instanceof HTMLElement && active.isConnected && (!entry.modal || dialog.contains(active))) {\n active.focus({ preventScroll: true });\n } else resolveInitialFocusTarget(entry, dialog)?.focus({ preventScroll: true });\n }\n state.modal = entry.modal;\n if (entry.modal) lockPageScroll();\n else unlockPageScroll();\n };\n\n const modelessEscape = (event: KeyboardEvent) => {\n const top = state.stack[state.stack.length - 1];\n if (!top || top.modal || event.key !== \"Escape\" || event.defaultPrevented || event.isComposing) return;\n event.preventDefault();\n void requestDismiss(top);\n };\n\n const stopConnectionObserver = () => {\n document.removeEventListener(\"keydown\", modelessEscape);\n state.connectionObserver?.disconnect();\n state.connectionObserver = undefined;\n };\n\n const applyCancelBehavior = (\n dialog: HTMLDialogElement,\n close: () => void,\n behavior: NonNullable<OpenDialogOptions[\"cancelBehavior\"]>,\n ) => {\n dialog.oncancel = (event) => {\n if (behavior === \"ignore\") {\n event.preventDefault();\n return;\n }\n event.preventDefault();\n close();\n };\n dialog.onmousedown = (event) => {\n state.mouseDownOnDialog = event.target === dialog;\n };\n dialog.onclick = (event) => {\n const realBackdropClick = state.mouseDownOnDialog === true;\n state.mouseDownOnDialog = false;\n if (event.target !== dialog || !state.modal) return;\n if (!realBackdropClick) return;\n if (behavior === \"ignore\") return;\n if (isPointInsideToast(event.clientX, event.clientY)) return;\n close();\n };\n };\n\n const requestDismiss = async (entry: DialogStackEntry) => {\n if (state.stack[state.stack.length - 1] !== entry || entry.dismissPending || entry.cancelBehavior === \"ignore\") return;\n if (!entry.dismissHandler) {\n popTop(undefined);\n return;\n }\n entry.dismissPending = true;\n try {\n await entry.dismissHandler();\n } catch (error) {\n // A failed guard must never discard the dialog's state.\n console.error(\"Dialog dismissal failed\", error);\n } finally {\n entry.dismissPending = false;\n }\n };\n\n const popTop = (result?: unknown) => {\n const top = state.stack.pop();\n if (!top) return;\n releaseHistory(top);\n top.dispose?.();\n top.container.remove();\n\n const dialog = state.element;\n const previous = state.stack[state.stack.length - 1];\n if (previous && dialog) {\n previous.container.style.display = \"\";\n dialog.className = previous.panelClassName;\n applyPresentation(previous);\n applyAccessibleName(dialog, previous);\n applyCancelBehavior(dialog, () => void requestDismiss(previous), previous.cancelBehavior);\n schedule(() => {\n if (!focusUnclaimed(dialog)) return;\n if (top.opener?.isConnected) returnFocus(top.opener, top.openerRing);\n else resolveInitialFocusTarget(previous, dialog)?.focus();\n });\n } else if (dialog) {\n dialog.oncancel = null;\n dialog.onmousedown = null;\n dialog.onclick = null;\n dialog.removeAttribute(\"aria-label\");\n dialog.removeAttribute(\"aria-labelledby\");\n if (dialog.open && typeof dialog.close === \"function\") dialog.close();\n else dialog.removeAttribute(\"open\");\n dialog.remove();\n state.element = undefined;\n stopConnectionObserver();\n unlockPageScroll();\n // After the Escape that closed it: WebKit lights the ring of whatever\n // that key press leaves focused.\n schedule(() => {\n if (top.opener?.isConnected && focusUnclaimed(dialog)) returnFocus(top.opener, top.openerRing);\n });\n }\n\n top.resolve?.(result);\n };\n\n const resetDisconnectedDialog = () => {\n if (state.stack.length === 0 || state.element?.isConnected) return;\n const entries = state.stack.splice(0);\n const dialog = state.element;\n state.element = undefined;\n stopConnectionObserver();\n dialog?.remove();\n unlockPageScroll();\n for (const entry of entries.reverse()) {\n releaseHistory(entry);\n entry.dispose?.();\n entry.container.remove();\n entry.resolve?.(undefined);\n }\n };\n\n const observeConnection = () => {\n if (state.connectionObserver || typeof MutationObserver === \"undefined\") return;\n document.addEventListener(\"keydown\", modelessEscape);\n state.connectionObserver = new MutationObserver(resetDisconnectedDialog);\n state.connectionObserver.observe(document.documentElement, { childList: true, subtree: true });\n };\n\n const open = <T>(view: DialogRender<T>, options: OpenDialogOptions = {}): Promise<T | undefined> => {\n if (options.signal?.aborted) return Promise.resolve(undefined);\n if (options.history) {\n // Dialogs closed just now leave the history first, or this dialog's entry would be the one removed.\n if (historySyncScheduled) syncHistory();\n if (returning) return returning.then(() => open(view, options));\n }\n const dialog = ensureDialogElement();\n const previousTop = state.stack[state.stack.length - 1];\n if (previousTop) previousTop.container.style.display = \"none\";\n const activeElement = document.activeElement;\n\n const panelClassName = options.panelClassName ?? DEFAULT_PANEL_CLASS;\n const cancelBehavior = options.cancelBehavior ?? \"resolve-undefined\";\n const initialFocus = options.initialFocus ?? \"first-input\";\n dialog.className = panelClassName;\n\n const container = document.createElement(\"div\");\n container.className = options.contentClassName ?? DEFAULT_CONTENT_CLASS;\n dialog.appendChild(container);\n\n const entry: DialogStackEntry = {\n container,\n panelClassName,\n cancelBehavior,\n initialFocus,\n opener: activeElement instanceof HTMLElement ? activeElement : undefined,\n openerRing: ringOnReturn(activeElement),\n ariaLabel: options.ariaLabel,\n modal: true,\n };\n\n return new Promise((resolve, reject) => {\n const abort = () => {\n const index = state.stack.indexOf(entry);\n if (index < 0) return;\n if (index === state.stack.length - 1) popTop(undefined);\n else {\n state.stack.splice(index, 1);\n releaseHistory(entry);\n entry.dispose?.();\n entry.container.remove();\n entry.resolve?.(undefined);\n }\n };\n entry.resolve = (value) => {\n options.signal?.removeEventListener(\"abort\", abort);\n if (entry.historyId === undefined) {\n resolve(value as T | undefined);\n return;\n }\n // After the history sync, so a caller that navigates or opens the next dialog finds the history settled.\n queueMicrotask(() => void (returning ?? Promise.resolve()).then(() => resolve(value as T | undefined)));\n };\n const closeTyped: DialogClose<T> = (result) => {\n if (state.stack[state.stack.length - 1] !== entry) return;\n popTop(result);\n };\n\n if (options.history) pushHistory(entry);\n state.stack.push(entry);\n try {\n entry.dispose = render(\n () =>\n view(closeTyped, {\n dialog,\n setModal: (modal) => {\n entry.modal = modal;\n applyPresentation(entry);\n },\n setPosition: (position) => {\n if (position && (!Number.isFinite(position.x) || !Number.isFinite(position.y))) return;\n entry.position = position;\n applyPresentation(entry);\n },\n requestDismiss: () => requestDismiss(entry),\n setDismissHandler: (handler) => {\n entry.dismissHandler = handler;\n },\n }),\n container,\n );\n applyPresentation(entry);\n applyAccessibleName(dialog, entry);\n applyCancelBehavior(dialog, () => void requestDismiss(entry), cancelBehavior);\n\n if (state.stack.length === 1) {\n // The native dialog returns focus to the element focused when it\n // opened, with a ring of the browser's choosing. Opening from a\n // blurred document leaves the return to popTop and its opener ring.\n if (activeElement instanceof HTMLElement) activeElement.blur();\n if (typeof dialog.showModal === \"function\") {\n if (entry.modal) dialog.showModal();\n else dialog.show();\n } else dialog.setAttribute(\"open\", \"\");\n observeConnection();\n }\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n if (options.signal?.aborted) abort();\n // Where opening left focus, the native dialog's own pick included. Focus that the content or a person moved\n // before the next frame stays: a browser can hold that frame back while keys already arrive.\n const opened = document.activeElement;\n schedule(() => {\n if (state.stack[state.stack.length - 1] !== entry) return;\n if (document.activeElement !== opened && !focusUnclaimed(dialog)) return;\n resolveInitialFocusTarget(entry, dialog)?.focus();\n });\n } catch (error) {\n entry.resolve = undefined;\n if (state.stack[state.stack.length - 1] === entry) popTop(undefined);\n reject(error);\n }\n });\n };\n\n const close: DialogCore[\"close\"] = (result) => {\n let first = true;\n while (state.stack.length > 0) {\n popTop(first ? result : undefined);\n first = false;\n }\n };\n\n return {\n open,\n close,\n isOpen: () => state.stack.length > 0,\n };\n};\n\nexport const dialogCore = createDialogCore();\n"],"mappings":"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","ignoreList":[]}
@@ -3,8 +3,9 @@ import { createSignal, onCleanup } from "solid-js";
3
3
  /**
4
4
  * Which installation steps a browser needs. `apple-in-app` and `in-app` are browsers embedded in another app, on
5
5
  * iPhone or iPad and elsewhere, which cannot install at all. `apple-browser` is Chrome, Firefox, Edge, or another
6
- * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-browser`
7
- * is an Android browser other than Chrome.
6
+ * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-samsung` is
7
+ * Samsung Internet on Android, whose installed apps Android may block as built for an older Android version.
8
+ * `android-browser` is another Android browser, such as Firefox or Edge.
8
9
  */
9
10
 
10
11
  /** Tokens of apps that open links in their own browser view. Case-sensitive, so `Line/` matches only LINE. */
@@ -12,7 +13,7 @@ const EMBEDDED = /FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|musica
12
13
  /** Browsers on iPhone and iPad other than Safari. */
13
14
  const APPLE_BROWSER = /CriOS\/|FxiOS\/|EdgiOS\/|OPiOS\/|OPT\/|Ddg\/|DuckDuckGo\/|YaBrowser\//;
14
15
  /** Android browsers whose user agent also names Chrome. */
15
- const ANDROID_OTHER = /SamsungBrowser|EdgA\/|OPR\/|YaBrowser|Firefox\//;
16
+ const ANDROID_OTHER = /EdgA\/|OPR\/|YaBrowser|Firefox\//;
16
17
 
17
18
  /**
18
19
  * Classifies the browser from its user agent; `platform` and `touchPoints` only tell an iPad that reports a Mac apart.
@@ -29,7 +30,10 @@ export function installationPlatform(userAgent, platform, touchPoints) {
29
30
  }
30
31
  if (EMBEDDED.test(userAgent)) return "in-app";
31
32
  if (/Macintosh/.test(userAgent) && /Safari/.test(userAgent) && !/Chrome|Chromium|Edg\//.test(userAgent)) return "apple-desktop";
32
- if (/Android/.test(userAgent)) return /Chrome\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? "android" : "android-browser";
33
+ if (/Android/.test(userAgent)) {
34
+ if (/SamsungBrowser\//.test(userAgent)) return "android-samsung";
35
+ return /Chrome\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? "android" : "android-browser";
36
+ }
33
37
  return "generic";
34
38
  }
35
39
  function isInstallPrompt(event) {
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["feedback/install.ts"],"sourcesContent":["import { createSignal, onCleanup } from \"solid-js\";\n\n/**\n * Which installation steps a browser needs. `apple-in-app` and `in-app` are browsers embedded in another app, on\n * iPhone or iPad and elsewhere, which cannot install at all. `apple-browser` is Chrome, Firefox, Edge, or another\n * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-browser`\n * is an Android browser other than Chrome.\n */\nexport type InstallationPlatform =\n | \"in-app\"\n | \"apple-in-app\"\n | \"apple-browser\"\n | \"apple-mobile\"\n | \"apple-desktop\"\n | \"android-browser\"\n | \"android\"\n | \"generic\";\n\n/** Tokens of apps that open links in their own browser view. Case-sensitive, so `Line/` matches only LINE. */\nconst EMBEDDED =\n /FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|musical_ly|BytedanceWebview|Pinterest|MicroMessenger|\\bLine\\/|GSA\\/|; wv\\)/;\n/** Browsers on iPhone and iPad other than Safari. */\nconst APPLE_BROWSER = /CriOS\\/|FxiOS\\/|EdgiOS\\/|OPiOS\\/|OPT\\/|Ddg\\/|DuckDuckGo\\/|YaBrowser\\//;\n/** Android browsers whose user agent also names Chrome. */\nconst ANDROID_OTHER = /SamsungBrowser|EdgA\\/|OPR\\/|YaBrowser|Firefox\\//;\n\n/**\n * Classifies the browser from its user agent; `platform` and `touchPoints` only tell an iPad that reports a Mac apart.\n * A pure function, so the server and the browser compute the same value from the same user agent. Not a device\n * check: an installed app on iPhone may itself look like an embedded browser.\n */\nexport function installationPlatform(userAgent: string, platform: string, touchPoints: number): InstallationPlatform {\n if (/iPad|iPhone|iPod/.test(userAgent) || (platform === \"MacIntel\" && touchPoints > 1)) {\n if (EMBEDDED.test(userAgent)) return \"apple-in-app\";\n if (APPLE_BROWSER.test(userAgent)) return \"apple-browser\";\n // Safari and every real browser send `Safari/`; a bare web view inside an app, such as a QR scanner, does not.\n if (!/Safari\\//.test(userAgent)) return \"apple-in-app\";\n return \"apple-mobile\";\n }\n if (EMBEDDED.test(userAgent)) return \"in-app\";\n if (/Macintosh/.test(userAgent) && /Safari/.test(userAgent) && !/Chrome|Chromium|Edg\\//.test(userAgent)) return \"apple-desktop\";\n if (/Android/.test(userAgent)) return /Chrome\\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? \"android\" : \"android-browser\";\n return \"generic\";\n}\n\ninterface BeforeInstallPromptEvent extends Event {\n prompt(): Promise<{ outcome: \"accepted\" | \"dismissed\" }>;\n}\n\nfunction isInstallPrompt(event: Event): event is BeforeInstallPromptEvent {\n return \"prompt\" in event && typeof event.prompt === \"function\";\n}\n\nexport type InstallPrompt = {\n platform: InstallationPlatform;\n /** Running from the Home Screen or as an installed app, or installed during this visit. */\n installed: () => boolean;\n /** The browser offered its own installation dialog, which `install()` opens. */\n canPrompt: () => boolean;\n busy: () => boolean;\n /** The person accepted the browser's dialog; the browser finishes the installation. */\n requested: () => boolean;\n /** The browser's dialog could not be opened. */\n failed: () => boolean;\n /** Opens the browser's dialog once. Call it directly from a click. */\n install: () => Promise<void>;\n};\n\n/**\n * Tracks whether the page runs installed and captures the browser's installation prompt. Browser-only; call it once,\n * when the page loads, under an owner that lives as long as the page: Chrome offers its prompt only once, early.\n */\nexport function createInstallPrompt(): InstallPrompt {\n const displayMode = matchMedia(\"(display-mode: standalone)\");\n const isStandalone = () => displayMode.matches || (\"standalone\" in navigator && navigator.standalone === true);\n const [installed, setInstalled] = createSignal(isStandalone());\n const [prompt, setPrompt] = createSignal<BeforeInstallPromptEvent>();\n const [busy, setBusy] = createSignal(false);\n const [requested, setRequested] = createSignal(false);\n const [failed, setFailed] = createSignal(false);\n const capture = (event: Event) => {\n if (!isInstallPrompt(event)) return;\n event.preventDefault();\n if (installed()) return;\n setPrompt(event);\n setRequested(false);\n };\n const complete = () => {\n setInstalled(true);\n setPrompt(undefined);\n };\n const displayChanged = () => {\n if (isStandalone()) complete();\n };\n window.addEventListener(\"beforeinstallprompt\", capture);\n window.addEventListener(\"appinstalled\", complete);\n displayMode.addEventListener(\"change\", displayChanged);\n onCleanup(() => {\n window.removeEventListener(\"beforeinstallprompt\", capture);\n window.removeEventListener(\"appinstalled\", complete);\n displayMode.removeEventListener(\"change\", displayChanged);\n });\n const install = async () => {\n if (busy() || installed()) return;\n const event = prompt();\n setFailed(false);\n if (!event) return;\n // Consume once, and invoke synchronously within the user's click.\n setPrompt(undefined);\n setBusy(true);\n try {\n const result = await event.prompt();\n if (!installed()) setRequested(result.outcome === \"accepted\");\n } catch {\n setFailed(true);\n } finally {\n setBusy(false);\n }\n };\n return {\n platform: installationPlatform(navigator.userAgent, navigator.platform, navigator.maxTouchPoints),\n installed,\n canPrompt: () => Boolean(prompt()),\n busy,\n requested,\n failed,\n install,\n };\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["feedback/install.ts"],"sourcesContent":["import { createSignal, onCleanup } from \"solid-js\";\n\n/**\n * Which installation steps a browser needs. `apple-in-app` and `in-app` are browsers embedded in another app, on\n * iPhone or iPad and elsewhere, which cannot install at all. `apple-browser` is Chrome, Firefox, Edge, or another\n * browser on iPhone or iPad, which installs through its Share menu but less reliably than Safari. `android-samsung` is\n * Samsung Internet on Android, whose installed apps Android may block as built for an older Android version.\n * `android-browser` is another Android browser, such as Firefox or Edge.\n */\nexport type InstallationPlatform =\n | \"in-app\"\n | \"apple-in-app\"\n | \"apple-browser\"\n | \"apple-mobile\"\n | \"apple-desktop\"\n | \"android-samsung\"\n | \"android-browser\"\n | \"android\"\n | \"generic\";\n\n/** Tokens of apps that open links in their own browser view. Case-sensitive, so `Line/` matches only LINE. */\nconst EMBEDDED =\n /FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|musical_ly|BytedanceWebview|Pinterest|MicroMessenger|\\bLine\\/|GSA\\/|; wv\\)/;\n/** Browsers on iPhone and iPad other than Safari. */\nconst APPLE_BROWSER = /CriOS\\/|FxiOS\\/|EdgiOS\\/|OPiOS\\/|OPT\\/|Ddg\\/|DuckDuckGo\\/|YaBrowser\\//;\n/** Android browsers whose user agent also names Chrome. */\nconst ANDROID_OTHER = /EdgA\\/|OPR\\/|YaBrowser|Firefox\\//;\n\n/**\n * Classifies the browser from its user agent; `platform` and `touchPoints` only tell an iPad that reports a Mac apart.\n * A pure function, so the server and the browser compute the same value from the same user agent. Not a device\n * check: an installed app on iPhone may itself look like an embedded browser.\n */\nexport function installationPlatform(userAgent: string, platform: string, touchPoints: number): InstallationPlatform {\n if (/iPad|iPhone|iPod/.test(userAgent) || (platform === \"MacIntel\" && touchPoints > 1)) {\n if (EMBEDDED.test(userAgent)) return \"apple-in-app\";\n if (APPLE_BROWSER.test(userAgent)) return \"apple-browser\";\n // Safari and every real browser send `Safari/`; a bare web view inside an app, such as a QR scanner, does not.\n if (!/Safari\\//.test(userAgent)) return \"apple-in-app\";\n return \"apple-mobile\";\n }\n if (EMBEDDED.test(userAgent)) return \"in-app\";\n if (/Macintosh/.test(userAgent) && /Safari/.test(userAgent) && !/Chrome|Chromium|Edg\\//.test(userAgent)) return \"apple-desktop\";\n if (/Android/.test(userAgent)) {\n if (/SamsungBrowser\\//.test(userAgent)) return \"android-samsung\";\n return /Chrome\\//.test(userAgent) && !ANDROID_OTHER.test(userAgent) ? \"android\" : \"android-browser\";\n }\n return \"generic\";\n}\n\ninterface BeforeInstallPromptEvent extends Event {\n prompt(): Promise<{ outcome: \"accepted\" | \"dismissed\" }>;\n}\n\nfunction isInstallPrompt(event: Event): event is BeforeInstallPromptEvent {\n return \"prompt\" in event && typeof event.prompt === \"function\";\n}\n\nexport type InstallPrompt = {\n platform: InstallationPlatform;\n /** Running from the Home Screen or as an installed app, or installed during this visit. */\n installed: () => boolean;\n /** The browser offered its own installation dialog, which `install()` opens. */\n canPrompt: () => boolean;\n busy: () => boolean;\n /** The person accepted the browser's dialog; the browser finishes the installation. */\n requested: () => boolean;\n /** The browser's dialog could not be opened. */\n failed: () => boolean;\n /** Opens the browser's dialog once. Call it directly from a click. */\n install: () => Promise<void>;\n};\n\n/**\n * Tracks whether the page runs installed and captures the browser's installation prompt. Browser-only; call it once,\n * when the page loads, under an owner that lives as long as the page: Chrome offers its prompt only once, early.\n */\nexport function createInstallPrompt(): InstallPrompt {\n const displayMode = matchMedia(\"(display-mode: standalone)\");\n const isStandalone = () => displayMode.matches || (\"standalone\" in navigator && navigator.standalone === true);\n const [installed, setInstalled] = createSignal(isStandalone());\n const [prompt, setPrompt] = createSignal<BeforeInstallPromptEvent>();\n const [busy, setBusy] = createSignal(false);\n const [requested, setRequested] = createSignal(false);\n const [failed, setFailed] = createSignal(false);\n const capture = (event: Event) => {\n if (!isInstallPrompt(event)) return;\n event.preventDefault();\n if (installed()) return;\n setPrompt(event);\n setRequested(false);\n };\n const complete = () => {\n setInstalled(true);\n setPrompt(undefined);\n };\n const displayChanged = () => {\n if (isStandalone()) complete();\n };\n window.addEventListener(\"beforeinstallprompt\", capture);\n window.addEventListener(\"appinstalled\", complete);\n displayMode.addEventListener(\"change\", displayChanged);\n onCleanup(() => {\n window.removeEventListener(\"beforeinstallprompt\", capture);\n window.removeEventListener(\"appinstalled\", complete);\n displayMode.removeEventListener(\"change\", displayChanged);\n });\n const install = async () => {\n if (busy() || installed()) return;\n const event = prompt();\n setFailed(false);\n if (!event) return;\n // Consume once, and invoke synchronously within the user's click.\n setPrompt(undefined);\n setBusy(true);\n try {\n const result = await event.prompt();\n if (!installed()) setRequested(result.outcome === \"accepted\");\n } catch {\n setFailed(true);\n } finally {\n setBusy(false);\n }\n };\n return {\n platform: installationPlatform(navigator.userAgent, navigator.platform, navigator.maxTouchPoints),\n installed,\n canPrompt: () => Boolean(prompt()),\n busy,\n requested,\n failed,\n install,\n };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -13,18 +13,87 @@ var _tmpl$ = /*#__PURE__*/_$template(`<p class=k2b-qr-scanner__note>`),
13
13
  import { timed } from "@k2b/stdlib/solid";
14
14
  import { createSignal, onCleanup, onMount, Show } from "solid-js";
15
15
  import { useUiMessages } from "../intl/messages";
16
+
17
+ /**
18
+ * Why the scanner could not start, as far as the browser tells: `"denied"` when camera access is off, `"no-camera"`
19
+ * when the device has no camera, `"in-use"` when the camera is busy or could not start, most often because another app
20
+ * holds it, and `"unavailable"` for anything else, including a scanning engine that cannot load.
21
+ */
22
+
23
+ const sizes = [{
24
+ width: {
25
+ min: 1024
26
+ }
27
+ }, {
28
+ width: {
29
+ min: 768
30
+ }
31
+ }, {}];
32
+ /** The rear camera at a resolution that keeps small codes readable, then any camera: the order qr-scanner tries. */
33
+ const cameraRequests = [...sizes.map(size => ({
34
+ ...size,
35
+ facingMode: {
36
+ exact: "environment"
37
+ }
38
+ })), ...sizes];
39
+ const errorName = error => typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
40
+ const cameraFailure = name => {
41
+ if (name === "NotAllowedError" || name === "SecurityError") return "denied";
42
+ if (name === "NotFoundError" || name === "OverconstrainedError") return "no-camera";
43
+ if (name === "NotReadableError") return "in-use";
44
+ return "unavailable";
45
+ };
46
+
47
+ /** The facing a track reports, or else the one its label names, as qr-scanner reads it. */
48
+ const facingOf = track => {
49
+ if (!track) return undefined;
50
+ const {
51
+ facingMode
52
+ } = track.getSettings();
53
+ if (facingMode) return facingMode;
54
+ if (/rear|back|environment/i.test(track.label)) return "environment";
55
+ if (/front|user|face/i.test(track.label)) return "user";
56
+ return undefined;
57
+ };
58
+
16
59
  /**
17
- * qr-scanner turns every getUserMedia failure into the string "Camera not found.", so only the Permissions API can
18
- * tell a refusal apart. Safari and Chrome expose the camera permission; elsewhere a refusal counts as unavailable.
60
+ * Opens the camera here rather than in qr-scanner, which turns every failure into "Camera not found.". A refusal ends
61
+ * the search, because asking again could show the permission prompt again. Any other failure may belong to the request,
62
+ * such as a camera that cannot start at the larger size, so the next, looser request follows, as in qr-scanner. When
63
+ * none succeeds, a camera that exists but fails outweighs a request no camera could meet. A closed scanner asks no more.
19
64
  */
20
- const cameraDenied = async () => {
21
- try {
22
- return (await navigator.permissions.query({
23
- name: "camera"
24
- })).state === "denied";
25
- } catch {
26
- return false;
65
+ const openCamera = async closed => {
66
+ if (!navigator.mediaDevices) return {
67
+ error: "unavailable"
68
+ };
69
+ let error = "no-camera";
70
+ for (const video of cameraRequests) {
71
+ if (closed()) break;
72
+ try {
73
+ const stream = await navigator.mediaDevices.getUserMedia({
74
+ video,
75
+ audio: false
76
+ });
77
+ const facing = facingOf(stream.getVideoTracks()[0]);
78
+ // Like qr-scanner, mirror a front camera, and a camera of unknown facing that the rear request did not find.
79
+ return {
80
+ stream,
81
+ mirrored: facing ? facing === "user" : !video.facingMode
82
+ };
83
+ } catch (failure) {
84
+ const reason = cameraFailure(errorName(failure));
85
+ if (reason === "denied") return {
86
+ error: reason
87
+ };
88
+ if (reason !== "no-camera") error = reason;
89
+ }
27
90
  }
91
+ return {
92
+ error
93
+ };
94
+ };
95
+ const stopStream = stream => {
96
+ for (const track of stream.getTracks()) track.stop();
28
97
  };
29
98
 
30
99
  /**
@@ -44,24 +113,22 @@ export function QrScanner(props) {
44
113
  let video;
45
114
  let scanner;
46
115
  let closed = false;
47
- /** The host has unmounted the scanner; it no longer hears about failures that finish later. */
48
- let unmounted = false;
49
116
  let cleanupMotion;
50
117
  const destroy = () => {
51
118
  closed = true;
52
119
  cleanupMotion?.();
53
120
  resetRejected.cancel();
54
121
  resetFeedback.cancel();
55
- // pause(true) stops existing tracks immediately; destroy also handles a still-pending permission request.
122
+ // pause(true) stops the camera stream immediately.
56
123
  void scanner?.pause(true);
57
124
  scanner?.destroy();
58
125
  scanner = undefined;
59
126
  };
60
- const fail = async () => {
127
+ // Once closed, the host has unmounted the scanner or heard why it stopped; later failures stay silent.
128
+ const fail = reason => {
61
129
  if (closed) return;
62
130
  destroy();
63
- const denied = await cameraDenied();
64
- if (!unmounted) props.onError(denied ? "denied" : "unavailable");
131
+ props.onError(reason);
65
132
  };
66
133
  onMount(() => {
67
134
  const hidden = () => {
@@ -71,7 +138,6 @@ export function QrScanner(props) {
71
138
  document.addEventListener("visibilitychange", hidden);
72
139
  window.addEventListener("pagehide", leaving);
73
140
  onCleanup(() => {
74
- unmounted = true;
75
141
  destroy();
76
142
  document.removeEventListener("visibilitychange", hidden);
77
143
  window.removeEventListener("pagehide", leaving);
@@ -95,12 +161,20 @@ export function QrScanner(props) {
95
161
  setInvalid(true);
96
162
  resetFeedback.debouncedFn();
97
163
  }, {
98
- preferredCamera: "environment",
99
164
  highlightScanRegion: true,
100
- // Empty frames are expected. Never log decoded content or frame data.
165
+ // A frame that cannot be decoded never ends the session: the camera runs, and the next frame may read.
166
+ // An engine error other than an empty frame, which the native BarcodeDetector reports as "Scanner error:
167
+ // No QR code found", means the detector cannot read on this device. Fall back to the bundled worker for
168
+ // the page through the flag qr-scanner 1.4.2 sets itself for "not implemented" and "service unavailable";
169
+ // it swaps engines after this frame. Worker errors carry the same prefix; the flag changes nothing there.
170
+ // qr-scanner reports frames only as strings: an Error means the engine itself did not load, such as a
171
+ // decoder chunk gone after a deploy or a worker that cannot start, and no later frame can read. Never log
172
+ // decoded content or frame data.
101
173
  onDecodeError: error => {
102
- if (closed || error === Engine.NO_QR_CODE_FOUND) return;
103
- void fail();
174
+ if (typeof error !== "string") return fail("unavailable");
175
+ if (error.startsWith("Scanner error: ") && !error.endsWith(Engine.NO_QR_CODE_FOUND)) {
176
+ Reflect.set(Engine, "_disableBarcodeDetector", true);
177
+ }
104
178
  }
105
179
  });
106
180
  const motion = matchMedia("(prefers-reduced-motion: reduce)");
@@ -114,10 +188,16 @@ export function QrScanner(props) {
114
188
  applyMotion();
115
189
  motion.addEventListener("change", applyMotion);
116
190
  cleanupMotion = () => motion.removeEventListener("change", applyMotion);
191
+ const camera = await openCamera(() => closed);
192
+ if ("error" in camera) return fail(camera.error);
193
+ if (closed) return stopStream(camera.stream);
194
+ // qr-scanner plays a stream that is already set instead of opening its own.
195
+ video.srcObject = camera.stream;
196
+ video.style.transform = camera.mirrored ? "scaleX(-1)" : "";
117
197
  await scanner.start();
118
198
  if (!closed) setStarting(false);
119
199
  } catch {
120
- await fail();
200
+ fail("unavailable");
121
201
  }
122
202
  })();
123
203
  });
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["inputs/QrScanner.tsx"],"sourcesContent":["import { timed } from \"@k2b/stdlib/solid\";\nimport type QrScannerEngine from \"qr-scanner\";\nimport { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\n\nexport type QrScannerError = \"denied\" | \"unavailable\";\n\nexport type QrScannerProps = {\n /** Decoded text is untrusted. Return `true` to accept it and stop the camera, `false` to reject it and keep scanning. */\n onResult: (text: string) => boolean;\n /** The page was hidden or left; unmount the scanner. */\n onStop: () => void;\n /** The camera could not start: access was denied, or there is no usable camera. Offer another way in. */\n onError: (reason: QrScannerError) => void;\n /** What to point the camera at. Defaults to a generic QR code hint. */\n instructions?: string;\n class?: string;\n};\n\n/**\n * qr-scanner turns every getUserMedia failure into the string \"Camera not found.\", so only the Permissions API can\n * tell a refusal apart. Safari and Chrome expose the camera permission; elsewhere a refusal counts as unavailable.\n */\nconst cameraDenied = async (): Promise<boolean> => {\n try {\n return (await navigator.permissions.query({ name: \"camera\" as PermissionName })).state === \"denied\";\n } catch {\n return false;\n }\n};\n\n/**\n * Scans a QR code with the rear camera. Each mount owns exactly one camera session, which stops when the scanner\n * unmounts, the page is hidden, or a result is accepted. The scanning engine loads only when the scanner mounts.\n * Decoded content is never logged.\n */\nexport function QrScanner(props: QrScannerProps): JSX.Element {\n const messages = useUiMessages();\n const [starting, setStarting] = createSignal(true);\n const [invalid, setInvalid] = createSignal(false);\n let lastRejected: string | undefined;\n const resetRejected = timed.debounce(() => {\n lastRejected = undefined;\n }, 1000);\n const resetFeedback = timed.debounce(() => setInvalid(false), 1500);\n let video!: HTMLVideoElement;\n let scanner: QrScannerEngine | undefined;\n let closed = false;\n /** The host has unmounted the scanner; it no longer hears about failures that finish later. */\n let unmounted = false;\n let cleanupMotion: (() => void) | undefined;\n const destroy = () => {\n closed = true;\n cleanupMotion?.();\n resetRejected.cancel();\n resetFeedback.cancel();\n // pause(true) stops existing tracks immediately; destroy also handles a still-pending permission request.\n void scanner?.pause(true);\n scanner?.destroy();\n scanner = undefined;\n };\n const fail = async () => {\n if (closed) return;\n destroy();\n const denied = await cameraDenied();\n if (!unmounted) props.onError(denied ? \"denied\" : \"unavailable\");\n };\n onMount(() => {\n const hidden = () => {\n if (document.visibilityState !== \"visible\") props.onStop();\n };\n const leaving = () => props.onStop();\n document.addEventListener(\"visibilitychange\", hidden);\n window.addEventListener(\"pagehide\", leaving);\n onCleanup(() => {\n unmounted = true;\n destroy();\n document.removeEventListener(\"visibilitychange\", hidden);\n window.removeEventListener(\"pagehide\", leaving);\n });\n void (async () => {\n try {\n const { default: Engine } = await import(\"qr-scanner\");\n if (closed || document.visibilityState !== \"visible\") return;\n scanner = new Engine(\n video,\n (result) => {\n if (closed) return;\n // Rearm only after a full second without a decoded QR, not between individual frames.\n resetRejected.debouncedFn();\n if (result.data === lastRejected) return;\n if (props.onResult(result.data)) {\n destroy();\n return;\n }\n lastRejected = result.data;\n setInvalid(true);\n resetFeedback.debouncedFn();\n },\n {\n preferredCamera: \"environment\",\n highlightScanRegion: true,\n // Empty frames are expected. Never log decoded content or frame data.\n onDecodeError: (error) => {\n if (closed || error === Engine.NO_QR_CODE_FOUND) return;\n void fail();\n },\n },\n );\n const motion = matchMedia(\"(prefers-reduced-motion: reduce)\");\n const applyMotion = () => {\n for (const animation of scanner?.$overlay?.getAnimations({ subtree: true }) ?? []) {\n if (motion.matches) animation.pause();\n else animation.play();\n }\n };\n applyMotion();\n motion.addEventListener(\"change\", applyMotion);\n cleanupMotion = () => motion.removeEventListener(\"change\", applyMotion);\n await scanner.start();\n if (!closed) setStarting(false);\n } catch {\n await fail();\n }\n })();\n });\n return (\n <div class={props.class ? `k2b-qr-scanner ${props.class}` : \"k2b-qr-scanner\"}>\n <div class=\"k2b-qr-scanner__preview\" data-invalid={invalid() || undefined}>\n {/* Live camera preview contains no prerecorded speech or audio. */}\n <video ref={video} muted playsinline aria-label={messages().qrCameraPreview} />\n </div>\n <p role=\"status\">{starting() ? messages().qrCameraStarting : (props.instructions ?? messages().qrCameraInstructions)}</p>\n <Show when={!starting()}>\n <p class=\"k2b-qr-scanner__note\">{messages().qrCameraPrivacy}</p>\n </Show>\n </div>\n );\n}\n\nexport default QrScanner;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["inputs/QrScanner.tsx"],"sourcesContent":["import { timed } from \"@k2b/stdlib/solid\";\nimport type QrScannerEngine from \"qr-scanner\";\nimport { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\n\n/**\n * Why the scanner could not start, as far as the browser tells: `\"denied\"` when camera access is off, `\"no-camera\"`\n * when the device has no camera, `\"in-use\"` when the camera is busy or could not start, most often because another app\n * holds it, and `\"unavailable\"` for anything else, including a scanning engine that cannot load.\n */\nexport type QrScannerError = \"denied\" | \"no-camera\" | \"in-use\" | \"unavailable\";\n\nexport type QrScannerProps = {\n /** Decoded text is untrusted. Return `true` to accept it and stop the camera, `false` to reject it and keep scanning. */\n onResult: (text: string) => boolean;\n /** The page was hidden or left; unmount the scanner. */\n onStop: () => void;\n /**\n * The camera or the scanning engine could not start; the scanner has stopped. Offer another way in. A frame that\n * cannot be decoded never ends here.\n */\n onError: (reason: QrScannerError) => void;\n /** What to point the camera at. Defaults to a generic QR code hint. */\n instructions?: string;\n class?: string;\n};\n\nconst sizes: MediaTrackConstraints[] = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}];\n/** The rear camera at a resolution that keeps small codes readable, then any camera: the order qr-scanner tries. */\nconst cameraRequests: MediaTrackConstraints[] = [...sizes.map((size) => ({ ...size, facingMode: { exact: \"environment\" } })), ...sizes];\n\nconst errorName = (error: unknown): string => (typeof error === \"object\" && error !== null && \"name\" in error ? String(error.name) : \"\");\n\nconst cameraFailure = (name: string): QrScannerError => {\n if (name === \"NotAllowedError\" || name === \"SecurityError\") return \"denied\";\n if (name === \"NotFoundError\" || name === \"OverconstrainedError\") return \"no-camera\";\n if (name === \"NotReadableError\") return \"in-use\";\n return \"unavailable\";\n};\n\n/** The facing a track reports, or else the one its label names, as qr-scanner reads it. */\nconst facingOf = (track: MediaStreamTrack | undefined): string | undefined => {\n if (!track) return undefined;\n const { facingMode } = track.getSettings();\n if (facingMode) return facingMode;\n if (/rear|back|environment/i.test(track.label)) return \"environment\";\n if (/front|user|face/i.test(track.label)) return \"user\";\n return undefined;\n};\n\n/**\n * Opens the camera here rather than in qr-scanner, which turns every failure into \"Camera not found.\". A refusal ends\n * the search, because asking again could show the permission prompt again. Any other failure may belong to the request,\n * such as a camera that cannot start at the larger size, so the next, looser request follows, as in qr-scanner. When\n * none succeeds, a camera that exists but fails outweighs a request no camera could meet. A closed scanner asks no more.\n */\nconst openCamera = async (closed: () => boolean): Promise<{ stream: MediaStream; mirrored: boolean } | { error: QrScannerError }> => {\n if (!navigator.mediaDevices) return { error: \"unavailable\" };\n let error: QrScannerError = \"no-camera\";\n for (const video of cameraRequests) {\n if (closed()) break;\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ video, audio: false });\n const facing = facingOf(stream.getVideoTracks()[0]);\n // Like qr-scanner, mirror a front camera, and a camera of unknown facing that the rear request did not find.\n return { stream, mirrored: facing ? facing === \"user\" : !video.facingMode };\n } catch (failure) {\n const reason = cameraFailure(errorName(failure));\n if (reason === \"denied\") return { error: reason };\n if (reason !== \"no-camera\") error = reason;\n }\n }\n return { error };\n};\n\nconst stopStream = (stream: MediaStream) => {\n for (const track of stream.getTracks()) track.stop();\n};\n\n/**\n * Scans a QR code with the rear camera. Each mount owns exactly one camera session, which stops when the scanner\n * unmounts, the page is hidden, or a result is accepted. The scanning engine loads only when the scanner mounts.\n * Decoded content is never logged.\n */\nexport function QrScanner(props: QrScannerProps): JSX.Element {\n const messages = useUiMessages();\n const [starting, setStarting] = createSignal(true);\n const [invalid, setInvalid] = createSignal(false);\n let lastRejected: string | undefined;\n const resetRejected = timed.debounce(() => {\n lastRejected = undefined;\n }, 1000);\n const resetFeedback = timed.debounce(() => setInvalid(false), 1500);\n let video!: HTMLVideoElement;\n let scanner: QrScannerEngine | undefined;\n let closed = false;\n let cleanupMotion: (() => void) | undefined;\n const destroy = () => {\n closed = true;\n cleanupMotion?.();\n resetRejected.cancel();\n resetFeedback.cancel();\n // pause(true) stops the camera stream immediately.\n void scanner?.pause(true);\n scanner?.destroy();\n scanner = undefined;\n };\n // Once closed, the host has unmounted the scanner or heard why it stopped; later failures stay silent.\n const fail = (reason: QrScannerError) => {\n if (closed) return;\n destroy();\n props.onError(reason);\n };\n onMount(() => {\n const hidden = () => {\n if (document.visibilityState !== \"visible\") props.onStop();\n };\n const leaving = () => props.onStop();\n document.addEventListener(\"visibilitychange\", hidden);\n window.addEventListener(\"pagehide\", leaving);\n onCleanup(() => {\n destroy();\n document.removeEventListener(\"visibilitychange\", hidden);\n window.removeEventListener(\"pagehide\", leaving);\n });\n void (async () => {\n try {\n const { default: Engine } = await import(\"qr-scanner\");\n if (closed || document.visibilityState !== \"visible\") return;\n scanner = new Engine(\n video,\n (result) => {\n if (closed) return;\n // Rearm only after a full second without a decoded QR, not between individual frames.\n resetRejected.debouncedFn();\n if (result.data === lastRejected) return;\n if (props.onResult(result.data)) {\n destroy();\n return;\n }\n lastRejected = result.data;\n setInvalid(true);\n resetFeedback.debouncedFn();\n },\n {\n highlightScanRegion: true,\n // A frame that cannot be decoded never ends the session: the camera runs, and the next frame may read.\n // An engine error other than an empty frame, which the native BarcodeDetector reports as \"Scanner error:\n // No QR code found\", means the detector cannot read on this device. Fall back to the bundled worker for\n // the page through the flag qr-scanner 1.4.2 sets itself for \"not implemented\" and \"service unavailable\";\n // it swaps engines after this frame. Worker errors carry the same prefix; the flag changes nothing there.\n // qr-scanner reports frames only as strings: an Error means the engine itself did not load, such as a\n // decoder chunk gone after a deploy or a worker that cannot start, and no later frame can read. Never log\n // decoded content or frame data.\n onDecodeError: (error) => {\n if (typeof error !== \"string\") return fail(\"unavailable\");\n if (error.startsWith(\"Scanner error: \") && !error.endsWith(Engine.NO_QR_CODE_FOUND)) {\n Reflect.set(Engine, \"_disableBarcodeDetector\", true);\n }\n },\n },\n );\n const motion = matchMedia(\"(prefers-reduced-motion: reduce)\");\n const applyMotion = () => {\n for (const animation of scanner?.$overlay?.getAnimations({ subtree: true }) ?? []) {\n if (motion.matches) animation.pause();\n else animation.play();\n }\n };\n applyMotion();\n motion.addEventListener(\"change\", applyMotion);\n cleanupMotion = () => motion.removeEventListener(\"change\", applyMotion);\n const camera = await openCamera(() => closed);\n if (\"error\" in camera) return fail(camera.error);\n if (closed) return stopStream(camera.stream);\n // qr-scanner plays a stream that is already set instead of opening its own.\n video.srcObject = camera.stream;\n video.style.transform = camera.mirrored ? \"scaleX(-1)\" : \"\";\n await scanner.start();\n if (!closed) setStarting(false);\n } catch {\n fail(\"unavailable\");\n }\n })();\n });\n return (\n <div class={props.class ? `k2b-qr-scanner ${props.class}` : \"k2b-qr-scanner\"}>\n <div class=\"k2b-qr-scanner__preview\" data-invalid={invalid() || undefined}>\n {/* Live camera preview contains no prerecorded speech or audio. */}\n <video ref={video} muted playsinline aria-label={messages().qrCameraPreview} />\n </div>\n <p role=\"status\">{starting() ? messages().qrCameraStarting : (props.instructions ?? messages().qrCameraInstructions)}</p>\n <Show when={!starting()}>\n <p class=\"k2b-qr-scanner__note\">{messages().qrCameraPrivacy}</p>\n </Show>\n </div>\n );\n}\n\nexport default QrScanner;\n"],"mappings":"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","ignoreList":[]}
@@ -38,6 +38,11 @@ const uiMessages = i18n.define({
38
38
  close: "Close",
39
39
  closeDialog: "close dialog",
40
40
  copied: "Copied",
41
+ infoBlockNote: "Note",
42
+ infoBlockInfo: "Info",
43
+ infoBlockSuccess: "Success",
44
+ infoBlockWarning: "Warning",
45
+ infoBlockDanger: "Danger",
41
46
  copy: "Copy",
42
47
  closeLightbox: "Close lightbox",
43
48
  couldNotLoadData: "Could not load this data",
@@ -192,6 +197,42 @@ const uiMessages = i18n.define({
192
197
  dropFilesToAttach: "Drop files to attach",
193
198
  conversation: "Conversation",
194
199
  jumpToLatest: "Jump to latest",
200
+ feedJumpToLatest: "Jump to latest",
201
+ feedJumpToLatestCount: ({
202
+ count
203
+ }) => `Jump to latest, ${count} new`,
204
+ feedNewItems: ({
205
+ count
206
+ }) => count === 1 ? "1 new item" : `${count} new items`,
207
+ feedNewMarker: "New",
208
+ messageSent: "Sent",
209
+ messageNotSent: "Not sent",
210
+ messageRetry: "Retry",
211
+ messageShowMore: "Show more",
212
+ messageShowLess: "Show less",
213
+ messageReplyTo: "In reply to",
214
+ messageForwarded: "Forwarded",
215
+ messageEdited: "edited",
216
+ messageDeleted: "This message was deleted",
217
+ messageVideo: "Video",
218
+ messageMoreAttachments: ({
219
+ count
220
+ }) => `${count} more`,
221
+ messageReactions: "Reactions",
222
+ messageReaction: ({
223
+ emoji,
224
+ count,
225
+ names,
226
+ own
227
+ }) => `${emoji} ${own && count === 1 ? "your reaction" : `${count} ${count === 1 ? "reaction" : "reactions"}${own ? ", including yours" : ""}`}${names ? `: ${names}` : ""}`,
228
+ messageAddReaction: "Add reaction",
229
+ messageReplies: ({
230
+ count
231
+ }) => count === 1 ? "1 reply" : `${count} replies`,
232
+ messageLastReply: ({
233
+ time
234
+ }) => `Last reply ${time}`,
235
+ messageStop: "Stop",
195
236
  conversationMessages: ({
196
237
  label
197
238
  }) => `${label} messages`,
@@ -501,6 +542,11 @@ const uiMessages = i18n.define({
501
542
  installAppleBrowserDetail: "This browser installs through Share → Add to Home Screen. If that isn’t there, open this page in Safari.",
502
543
  installAndroidBrowserTitle: "Chrome works best",
503
544
  installAndroidBrowserDetail: "In this browser, look for “Install” or “Add to Home screen” in the menu. If that isn’t there, open this page in Chrome.",
545
+ installSamsungTitle: "Install with Chrome",
546
+ installSamsungDetail: ({
547
+ appName
548
+ }) => `On Samsung phones, Samsung Internet builds its own app package, which Android 14 and later may block as “built for an older version of Android”. That warning is about the package, not about ${appName}. Chrome installs ${appName} without it. Without Chrome, use this browser’s menu and continue through that warning.`,
549
+ installOpenChrome: "Open in Chrome",
504
550
  installCopyLink: "Copy link",
505
551
  installLinkCopied: "Link copied",
506
552
  installCopyFailed: "The link could not be copied. Copy this address manually:",
@@ -544,6 +590,11 @@ const uiMessages = i18n.define({
544
590
  close: "Schließen",
545
591
  closeDialog: "Dialog schließen",
546
592
  copied: "Kopiert",
593
+ infoBlockNote: "Notiz",
594
+ infoBlockInfo: "Info",
595
+ infoBlockSuccess: "Erfolg",
596
+ infoBlockWarning: "Warnung",
597
+ infoBlockDanger: "Gefahr",
547
598
  copy: "Kopieren",
548
599
  closeLightbox: "Bildansicht schließen",
549
600
  couldNotLoadData: "Diese Daten konnten nicht geladen werden",
@@ -698,6 +749,42 @@ const uiMessages = i18n.define({
698
749
  dropFilesToAttach: "Dateien zum Anhängen ablegen",
699
750
  conversation: "Unterhaltung",
700
751
  jumpToLatest: "Zur neuesten Nachricht",
752
+ feedJumpToLatest: "Zum Neuesten",
753
+ feedJumpToLatestCount: ({
754
+ count
755
+ }) => `Zum Neuesten, ${count} neu`,
756
+ feedNewItems: ({
757
+ count
758
+ }) => count === 1 ? "1 neuer Eintrag" : `${count} neue Einträge`,
759
+ feedNewMarker: "Neu",
760
+ messageSent: "Gesendet",
761
+ messageNotSent: "Nicht gesendet",
762
+ messageRetry: "Erneut senden",
763
+ messageShowMore: "Mehr anzeigen",
764
+ messageShowLess: "Weniger anzeigen",
765
+ messageReplyTo: "Antwort auf",
766
+ messageForwarded: "Weitergeleitet",
767
+ messageEdited: "bearbeitet",
768
+ messageDeleted: "Diese Nachricht wurde gelöscht",
769
+ messageVideo: "Video",
770
+ messageMoreAttachments: ({
771
+ count
772
+ }) => `${count} weitere`,
773
+ messageReactions: "Reaktionen",
774
+ messageReaction: ({
775
+ emoji,
776
+ count,
777
+ names,
778
+ own
779
+ }) => `${emoji} ${own && count === 1 ? "deine Reaktion" : `${count} ${count === 1 ? "Reaktion" : "Reaktionen"}${own ? ", einschließlich deiner" : ""}`}${names ? `: ${names}` : ""}`,
780
+ messageAddReaction: "Reaktion hinzufügen",
781
+ messageReplies: ({
782
+ count
783
+ }) => count === 1 ? "1 Antwort" : `${count} Antworten`,
784
+ messageLastReply: ({
785
+ time
786
+ }) => `Letzte Antwort ${time}`,
787
+ messageStop: "Stoppen",
701
788
  conversationMessages: ({
702
789
  label
703
790
  }) => `Nachrichten in ${label}`,
@@ -1007,6 +1094,11 @@ const uiMessages = i18n.define({
1007
1094
  installAppleBrowserDetail: "In diesem Browser installierst du über Teilen → Zum Home-Bildschirm. Fehlt der Eintrag, öffne diese Seite in Safari.",
1008
1095
  installAndroidBrowserTitle: "Am besten mit Chrome",
1009
1096
  installAndroidBrowserDetail: "Suche in diesem Browser im Menü nach „Installieren“ oder „Zum Startbildschirm hinzufügen“. Fehlt der Eintrag, öffne diese Seite in Chrome.",
1097
+ installSamsungTitle: "Mit Chrome installieren",
1098
+ installSamsungDetail: ({
1099
+ appName
1100
+ }) => `Auf Samsung-Handys erstellt Samsung Internet ein eigenes App-Paket, das Android ab Version 14 als „für eine ältere Android-Version entwickelt“ blockieren kann. Diese Warnung betrifft das Paket, nicht ${appName}. Mit Chrome installierst du ${appName} ohne sie. Ohne Chrome nutze das Menü dieses Browsers und fahre trotz dieser Warnung fort.`,
1101
+ installOpenChrome: "In Chrome öffnen",
1010
1102
  installCopyLink: "Link kopieren",
1011
1103
  installLinkCopied: "Link kopiert",
1012
1104
  installCopyFailed: "Der Link konnte nicht kopiert werden. Kopiere diese Adresse manuell:",