@k2b/ui 0.12.0 → 0.14.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.
- package/README.md +1 -1
- package/dist/browser/content/MarkdownView.js +13 -5
- package/dist/browser/content/MarkdownView.js.map +1 -1
- package/dist/browser/content/PdfPreview.js +5 -2
- package/dist/browser/content/PdfPreview.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/markdown-info-blocks.js +363 -0
- package/dist/browser/content/markdown-info-blocks.js.map +1 -0
- package/dist/browser/feedback/InstallGuide.js +267 -0
- package/dist/browser/feedback/InstallGuide.js.map +1 -0
- package/dist/browser/feedback/announce.js +50 -0
- package/dist/browser/feedback/announce.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +88 -2
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/feedback/index.js +4 -0
- package/dist/browser/feedback/index.js.map +1 -1
- package/dist/browser/feedback/install.js +99 -0
- package/dist/browser/feedback/install.js.map +1 -0
- package/dist/browser/feedback/prompts.js +11 -1
- package/dist/browser/feedback/prompts.js.map +1 -1
- package/dist/browser/feedback/theme-color.js +15 -0
- package/dist/browser/feedback/theme-color.js.map +1 -0
- package/dist/browser/feedback/toast.js +10 -44
- package/dist/browser/feedback/toast.js.map +1 -1
- package/dist/browser/inputs/QrScanner.js +164 -0
- package/dist/browser/inputs/QrScanner.js.map +1 -0
- package/dist/browser/inputs/index.js +1 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/intl/messages.js +96 -2
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/layout/Discussion.js +2 -1
- package/dist/browser/layout/Discussion.js.map +1 -1
- package/dist/browser/layout/MobileShell.js +284 -0
- package/dist/browser/layout/MobileShell.js.map +1 -0
- package/dist/browser/layout/TabBar.js +69 -0
- package/dist/browser/layout/TabBar.js.map +1 -0
- package/dist/browser/layout/index.js +2 -0
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/NoticeCard.js +55 -60
- package/dist/browser/surfaces/NoticeCard.js.map +1 -1
- package/dist/browser/surfaces/index.js +1 -1
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/ssr/content/MarkdownView.js +13 -5
- package/dist/ssr/content/MarkdownView.js.map +1 -1
- package/dist/ssr/content/PdfPreview.js +4 -2
- package/dist/ssr/content/PdfPreview.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/markdown-info-blocks.js +363 -0
- package/dist/ssr/content/markdown-info-blocks.js.map +1 -0
- package/dist/ssr/feedback/InstallGuide.js +201 -0
- package/dist/ssr/feedback/InstallGuide.js.map +1 -0
- package/dist/ssr/feedback/announce.js +50 -0
- package/dist/ssr/feedback/announce.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +88 -2
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/feedback/index.js +4 -0
- package/dist/ssr/feedback/index.js.map +1 -1
- package/dist/ssr/feedback/install.js +99 -0
- package/dist/ssr/feedback/install.js.map +1 -0
- package/dist/ssr/feedback/prompts.js +11 -1
- package/dist/ssr/feedback/prompts.js.map +1 -1
- package/dist/ssr/feedback/theme-color.js +15 -0
- package/dist/ssr/feedback/theme-color.js.map +1 -0
- package/dist/ssr/feedback/toast.js +10 -44
- package/dist/ssr/feedback/toast.js.map +1 -1
- package/dist/ssr/inputs/QrScanner.js +128 -0
- package/dist/ssr/inputs/QrScanner.js.map +1 -0
- package/dist/ssr/inputs/index.js +1 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/intl/messages.js +96 -2
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/layout/Discussion.js +2 -1
- package/dist/ssr/layout/Discussion.js.map +1 -1
- package/dist/ssr/layout/MobileShell.js +253 -0
- package/dist/ssr/layout/MobileShell.js.map +1 -0
- package/dist/ssr/layout/TabBar.js +25 -0
- package/dist/ssr/layout/TabBar.js.map +1 -0
- package/dist/ssr/layout/index.js +2 -0
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/NoticeCard.js +21 -30
- package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
- package/dist/ssr/surfaces/index.js +1 -1
- package/dist/ssr/surfaces/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tabler.css +1 -1
- package/dist/types/content/MarkdownView.d.ts +6 -2
- package/dist/types/content/index.d.ts +1 -0
- package/dist/types/content/markdown-info-blocks.d.ts +67 -0
- package/dist/types/feedback/InstallGuide.d.ts +26 -0
- package/dist/types/feedback/announce.d.ts +19 -0
- package/dist/types/feedback/dialog-core.d.ts +5 -0
- package/dist/types/feedback/index.d.ts +7 -0
- package/dist/types/feedback/install.d.ts +32 -0
- package/dist/types/feedback/prompts.d.ts +2 -0
- package/dist/types/feedback/theme-color.d.ts +6 -0
- package/dist/types/inputs/QrScanner.d.ts +20 -0
- package/dist/types/inputs/index.d.ts +2 -0
- package/dist/types/intl/messages.d.ts +47 -0
- package/dist/types/layout/MobileShell.d.ts +40 -0
- package/dist/types/layout/TabBar.d.ts +26 -0
- package/dist/types/layout/index.d.ts +4 -0
- package/dist/types/surfaces/NoticeCard.d.ts +4 -4
- package/dist/types/surfaces/index.d.ts +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/markdown-info-blocks.ts"],"sourcesContent":["import { Lexer, type MarkedExtension, Tokenizer, type TokenizerAndRendererExtension } from \"marked\";\nimport { resolveUiMessages, type UiMessages } from \"../intl/messages\";\nimport { NOTICE_CARD_CLASSES, type NoticeTone } from \"../surfaces/NoticeCard\";\n\n/**\n * Markdown info blocks: one grammar and one look for every Markdown surface.\n *\n * ```markdown\n * :::warning Before deleting\n * Deleting an item cannot be undone.\n * :::\n * ```\n *\n * The opening line holds `:::`, a type in any letter case, and an optional\n * plain-text title. The block ends at a line holding only `:::`, indented no\n * deeper than the opener; code, lists, quotes, and HTML inside the body cannot\n * end it. The body is ordinary Markdown. A block that is never closed stays\n * ordinary text.\n */\n\nconst TONES = {\n note: \"neutral\",\n info: \"info\",\n success: \"success\",\n warning: \"warning\",\n danger: \"danger\",\n} as const satisfies Record<string, NoticeTone>;\n\nexport type MarkdownInfoBlockType = keyof typeof TONES;\n\nexport type MarkdownInfoBlock = {\n type: MarkdownInfoBlockType;\n /** Plain-text title from the opening line. */\n title?: string;\n /** Raw Markdown between the opening and closing lines, with `\\n` line breaks. */\n body: string;\n /** Source length through the closing line, or the whole source when the block is not closed. */\n length: number;\n closed: boolean;\n};\n\nexport type MarkdownInfoBlockRenderInput = {\n type: MarkdownInfoBlockType;\n title?: string;\n /** Already rendered body HTML. The caller owns its sanitization. */\n bodyHtml: string;\n /** Locale for the screen-reader name of an untitled block. */\n locale?: string;\n};\n\nconst OPENER = /^( {0,3}):::(note|info|success|warning|danger)(?:[ \\t]+(.*?))?[ \\t]*$/i;\nconst START = /^ {0,3}:::(?:note|info|success|warning|danger)(?=[ \\t\\n]|$)/i;\nconst OPENERS = /(?<=^|\\n) {0,3}:::(?:note|info|success|warning|danger)(?=[ \\t\\n]|$)/gi;\nconst CLOSING = /^( {0,3}):::[ \\t]*$/;\nconst FENCE = /^( *)(`{3,}|~{3,})(.*)$/;\n\ntype ContainerKind = \"code\" | \"fences\" | \"list\" | \"blockquote\" | \"html\";\ntype ContainerToken = NonNullable<ReturnType<Tokenizer[ContainerKind]>>;\n\nconst LABELS: Record<MarkdownInfoBlockType, (t: UiMessages) => string> = {\n note: (t) => t.infoBlockNote,\n info: (t) => t.infoBlockInfo,\n success: (t) => t.infoBlockSuccess,\n warning: (t) => t.infoBlockWarning,\n danger: (t) => t.infoBlockDanger,\n};\n\nconst escapeHtml = (value: string): string =>\n value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\").replaceAll('\"', \""\").replaceAll(\"'\", \"'\");\n\nconst lineEnd = (source: string, from: number): number => {\n const end = source.indexOf(\"\\n\", from);\n return end < 0 ? source.length : end;\n};\n\nconst closes = (line: string, indent: number): boolean => {\n const closing = CLOSING.exec(line);\n return !!closing && closing[1]!.length <= indent;\n};\n\n/** Column where a list item's text starts, as CommonMark counts it. */\nconst itemTextIndent = (raw: string): number => {\n const [, bullet = \"\", gap = \"\"] = /^( {0,3}(?:[-+*]|\\d{1,9}[.)]))( *)/.exec(raw) ?? [];\n return bullet.length + (gap.length >= 1 && gap.length <= 4 && raw[bullet.length + gap.length] !== \"\\n\" ? gap.length : 1);\n};\n\n/**\n * Offset of the block's closing line inside a list or quote, or -1. Lazy\n * paragraph lines may hold it; a list item's own fenced code cannot.\n */\nconst closingLineIn = (token: ContainerToken, indent: number): number => {\n if (token.type !== \"list\" && token.type !== \"blockquote\") return -1;\n const items = token.type === \"list\" ? token.items : [];\n let item = -1;\n let itemStart = 0;\n let itemEnd = 0;\n let text = 0;\n let fence = \"\";\n for (let from = 0; from < token.raw.length; ) {\n while (item + 1 < items.length && from >= itemEnd) {\n item++;\n itemStart = itemEnd;\n itemEnd += items[item]!.raw.length;\n text = itemTextIndent(items[item]!.raw);\n fence = \"\";\n }\n const end = lineEnd(token.raw, from);\n const line = token.raw.slice(from, end);\n // A line indented less than the item's text has left the item and its fence.\n if (fence && line.trim() && line.length - line.trimStart().length < text) fence = \"\";\n // The first line of an item carries its bullet where the text would be indented.\n const marker = items.length ? FENCE.exec(from === itemStart ? \" \".repeat(text) + line.slice(text) : line) : null;\n if (fence) {\n if (marker && marker[2]![0] === fence[0] && marker[2]!.length >= fence.length && !marker[3]!.trim()) fence = \"\";\n } else if (\n marker &&\n marker[1]!.length >= text &&\n marker[1]!.length <= text + 3 &&\n !(marker[2]![0] === \"`\" && marker[3]!.includes(\"`\"))\n ) {\n fence = marker[2]!;\n } else if (closes(line, indent)) {\n return from;\n }\n from = end + 1;\n }\n return -1;\n};\n\n/** Whether `line` starts a block of its own after a paragraph line instead of continuing the paragraph, as marked decides. */\nconst interruptsParagraph = (line: string): boolean => Lexer.rules.block.gfm.paragraph.exec(`x\\n${line}\\n`)?.[0] === \"x\";\n\n/**\n * Reads info blocks in one `\\n`-separated source. The opening line reads like\n * a paragraph line, so the body continues it: only a block that may interrupt\n * a paragraph, such as a fence, a list, a quote, or block HTML, hides its\n * lines from the closing line. A scan's only state is its line and whether a\n * paragraph is open there, so a later scan that reaches a line an earlier one\n * passed ends the same way; `outcomes` remembers that, which keeps reading\n * many openers of one document linear.\n */\nconst infoBlockReader = (source: string) => {\n const outcomes = new Map<number, number>();\n let tokenizer: Tokenizer | undefined;\n\n /** Code, lists, quotes, and HTML whose lines belong to the body even when one of them reads `:::`. */\n const container = (offset: number, line: string, paragraph: boolean): ContainerToken | undefined => {\n const kind: ContainerKind | undefined = /^(?: {4}| {0,3}\\t)/.test(line)\n ? \"code\"\n : /^ {0,3}(?:`{3,}|~{3,})/.test(line)\n ? \"fences\"\n : /^ {0,3}(?:[-+*][ \\t]|\\d{1,9}[.)][ \\t])/.test(line)\n ? \"list\"\n : /^ {0,3}>/.test(line)\n ? \"blockquote\"\n : /^ {0,3}</.test(line)\n ? \"html\"\n : undefined;\n if (!kind || (paragraph && !interruptsParagraph(line))) return undefined;\n if (!tokenizer) {\n // One tokenizer per document: its lexer queues inline work that nothing drains.\n tokenizer = new Tokenizer({ gfm: true });\n new Lexer({ gfm: true, tokenizer });\n }\n return tokenizer[kind](source.slice(offset));\n };\n\n /** Offset of the closing line for a body that starts at `start`, or -1 when the block never closes. */\n const closingLine = (start: number, indent: number): number => {\n const passed: number[] = [];\n let offset = start;\n let paragraph = true;\n let result = -1;\n while (offset < source.length) {\n const key = offset * 8 + indent * 2 + (paragraph ? 1 : 0);\n const known = outcomes.get(key);\n if (known !== undefined) {\n result = known;\n break;\n }\n passed.push(key);\n const end = lineEnd(source, offset);\n const line = source.slice(offset, end);\n if (closes(line, indent)) {\n result = offset;\n break;\n }\n const token = container(offset, line, paragraph);\n if (token) {\n const closing = closingLineIn(token, indent);\n const length = closing < 0 ? token.raw.length : closing;\n if (length > 0) {\n offset += length;\n paragraph = false;\n continue;\n }\n }\n // A blank line ends a paragraph; any other line continues or starts one.\n paragraph = line.trim() !== \"\";\n offset = Math.min(end + 1, source.length);\n }\n for (const key of passed) outcomes.set(key, result);\n return result;\n };\n\n return (offset: number): MarkdownInfoBlock | null => {\n const firstEnd = lineEnd(source, offset);\n const opener = OPENER.exec(source.slice(offset, firstEnd));\n if (!opener) return null;\n const indent = opener[1]!.length;\n const type = opener[2]!.toLowerCase() as MarkdownInfoBlockType;\n const title = opener[3]?.trim() || undefined;\n const bodyStart = Math.min(firstEnd + 1, source.length);\n const closing = closingLine(bodyStart, indent);\n if (closing < 0) return { type, title, body: source.slice(bodyStart), length: source.length - offset, closed: false };\n const body = source.slice(bodyStart, closing).replace(/\\n$/, \"\");\n return { type, title, body, length: Math.min(lineEnd(source, closing) + 1, source.length) - offset, closed: true };\n };\n};\n\n/**\n * Reads the info block that starts `source`. Returns `null` when the first\n * line is not an info-block opener. Applications with their own Markdown\n * pipeline use this to recognize exactly the blocks `MarkdownView` renders.\n * Any line endings are accepted; `length` counts `source` as given.\n */\nexport const scanMarkdownInfoBlock = (source: string): MarkdownInfoBlock | null => {\n const normalized = source.replace(/\\r\\n?/g, \"\\n\");\n const block = infoBlockReader(normalized)(0);\n if (!block || normalized.length === source.length) return block;\n // Only `\\r\\n` shortens the source; count each one twice up to the block's end.\n let length = 0;\n for (let index = 0; index < block.length; index++) length += source[length] === \"\\r\" && source[length + 1] === \"\\n\" ? 2 : 1;\n return { ...block, length };\n};\n\nconst renderBlock = (input: Omit<MarkdownInfoBlockRenderInput, \"locale\">, messages: UiMessages): string => {\n const name = input.title\n ? `<p class=\"${NOTICE_CARD_CLASSES.title}\">${escapeHtml(input.title)}</p>`\n : `<span class=\"k2b-sr-only\">${escapeHtml(LABELS[input.type](messages))}: </span>`;\n return `<aside class=\"${NOTICE_CARD_CLASSES.root}\" data-tone=\"${TONES[input.type]}\" role=\"note\">${name}<div class=\"${NOTICE_CARD_CLASSES.body}\">${input.bodyHtml}</div></aside>`;\n};\n\n/**\n * Renders an info block as the calm `NoticeCard`: the tone tints the surface,\n * the text stays neutral, and there is no icon. A title is the visible heading\n * and names the block; without one, the localized type name is announced to\n * screen readers only.\n */\nexport const renderMarkdownInfoBlock = (input: MarkdownInfoBlockRenderInput): string => renderBlock(input, resolveUiMessages(input.locale));\n\n/**\n * What one `marked` lexer run knows about its document: the opening lines, the\n * blocks read so far, the length of the last top-level source, and the extent\n * of the last top-level paragraph `start` measured.\n */\ntype DocumentState = {\n source: string;\n read: (offset: number) => MarkdownInfoBlock | null;\n openers: number[];\n blocks: Map<number, MarkdownInfoBlock | null>;\n top: number;\n paragraph: { start: number; end: number };\n};\n\nconst documents = new WeakMap<object, DocumentState>();\n\n/** The closed block that starts at `offset`, read once per document. */\nconst closedBlockAt = (document: DocumentState, offset: number): MarkdownInfoBlock | null => {\n let block = document.blocks.get(offset);\n if (block === undefined) {\n const read = document.read(offset);\n block = read?.closed ? read : null;\n document.blocks.set(offset, block);\n }\n return block;\n};\n\n/** Index of the first of the ascending `offsets` at or after `from`. */\nconst firstAtOrAfter = (offsets: readonly number[], from: number): number => {\n let low = 0;\n let high = offsets.length;\n while (low < high) {\n const middle = (low + high) >> 1;\n if (offsets[middle]! < from) low = middle + 1;\n else high = middle;\n }\n return low;\n};\n\n/**\n * The first closed block after the top-level `position` inside the paragraph\n * that would start there. Openers further away cannot end that paragraph, and\n * a paragraph that starts inside the last measured one ends where it ends.\n */\nconst nextBlockStart = (document: DocumentState, position: number, rules: RegExp): number | undefined => {\n if (position < document.paragraph.start || position >= document.paragraph.end) {\n const extent = rules.exec(document.source.slice(position))?.[0].length ?? 0;\n document.paragraph = { start: position, end: position + extent };\n }\n for (let index = firstAtOrAfter(document.openers, position + 1); index < document.openers.length; index++) {\n const offset = document.openers[index]!;\n if (offset >= document.paragraph.end) return undefined;\n if (closedBlockAt(document, offset)) return offset;\n }\n return undefined;\n};\n\n/** The paragraph rule of the lexer's own flavour. */\nconst paragraphRule = (lexer: Lexer): RegExp =>\n Lexer.rules.block[lexer.options.pedantic ? \"pedantic\" : lexer.options.gfm ? \"gfm\" : \"normal\"].paragraph;\n\nconst createExtension = (messages: UiMessages): MarkedExtension => {\n const extension: TokenizerAndRendererExtension = {\n name: \"markdownInfoBlock\",\n level: \"block\",\n // marked ends a top-level paragraph where `start` points. Only a block\n // that closes renders, so `start` points at the first one inside the\n // paragraph; a split anywhere else would only make marked lex the rest of\n // the document again.\n start(source) {\n const document = documents.get(this.lexer);\n // marked asks right after the tokenizer below ran at the same top-level position.\n if (!document || source.length + 1 !== document.top) return undefined;\n const position = document.source.length - source.length - 1;\n const next = nextBlockStart(document, position, paragraphRule(this.lexer));\n return next === undefined ? undefined : next - position - 1;\n },\n tokenizer(source, tokens) {\n let document = documents.get(this.lexer);\n if (tokens !== this.lexer.tokens) {\n // Nested sources are rewritten text, not the rest of the document.\n if (document) document.top = -1;\n return undefined;\n }\n // The first top-level call sees the whole document; later ones see what is left of it.\n if (!document) {\n document = {\n source,\n read: infoBlockReader(source),\n openers: Array.from(source.matchAll(OPENERS), (opener) => opener.index),\n blocks: new Map(),\n top: source.length,\n paragraph: { start: -1, end: -1 },\n };\n documents.set(this.lexer, document);\n }\n document.top = source.length;\n if (!START.test(source)) return undefined;\n const block = closedBlockAt(document, document.source.length - source.length);\n if (!block) return undefined;\n return {\n type: \"markdownInfoBlock\",\n raw: source.slice(0, block.length),\n blockType: block.type,\n title: block.title,\n tokens: this.lexer.blockTokens(block.body, []),\n };\n },\n renderer(token) {\n return renderBlock(\n {\n type: token.blockType as MarkdownInfoBlockType,\n title: token.title as string | undefined,\n bodyHtml: this.parser.parse(token.tokens ?? []),\n },\n messages,\n );\n },\n };\n return {\n extensions: [extension],\n tokenizer: {\n // A setext heading's text is a paragraph, so it ends before a block as\n // well; marked does not clip it at `start` itself.\n lheading(source) {\n const document = documents.get(this.lexer);\n if (!document || source.length !== document.top) return false;\n const position = document.source.length - source.length;\n const next = nextBlockStart(document, position, paragraphRule(this.lexer));\n return next === undefined ? false : Tokenizer.prototype.lheading.call(this, source.slice(0, next - position));\n },\n },\n };\n};\n\nconst extensions = new WeakMap<UiMessages, MarkedExtension>();\n\n/**\n * `marked` extension for info blocks at the top level of a document. Blocks\n * inside lists, quotes, or another block stay ordinary text. The body goes\n * through the host's own Markdown renderer, so its escaping and link rules\n * apply unchanged. Locales that share a UI message catalog get the same\n * extension, so a host may cache one `marked` instance per extension.\n */\nexport const markdownInfoBlocks = (options: { locale?: string } = {}): MarkedExtension => {\n const messages = resolveUiMessages(options.locale);\n const extension = extensions.get(messages) ?? createExtension(messages);\n extensions.set(messages, extension);\n return extension;\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { template as _$template } from "solid-js/web";
|
|
2
|
+
import { memo as _$memo } from "solid-js/web";
|
|
3
|
+
import { setAttribute as _$setAttribute } from "solid-js/web";
|
|
4
|
+
import { className as _$className } from "solid-js/web";
|
|
5
|
+
import { effect as _$effect } from "solid-js/web";
|
|
6
|
+
import { getNextMarker as _$getNextMarker } from "solid-js/web";
|
|
7
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
8
|
+
import { getNextElement as _$getNextElement } from "solid-js/web";
|
|
9
|
+
import { insert as _$insert } from "solid-js/web";
|
|
10
|
+
var _tmpl$ = /*#__PURE__*/_$template(`<p role=alert>`),
|
|
11
|
+
_tmpl$2 = /*#__PURE__*/_$template(`<p role=status>`),
|
|
12
|
+
_tmpl$3 = /*#__PURE__*/_$template(`<div><!$><!/><!$><!/>`),
|
|
13
|
+
_tmpl$4 = /*#__PURE__*/_$template(`<p>`),
|
|
14
|
+
_tmpl$5 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
|
|
15
|
+
_tmpl$6 = /*#__PURE__*/_$template(`<code>`),
|
|
16
|
+
_tmpl$7 = /*#__PURE__*/_$template(`<ol class=k2b-install-guide__steps>`),
|
|
17
|
+
_tmpl$8 = /*#__PURE__*/_$template(`<li><span class=k2b-install-guide__step-icon><i aria-hidden=true></i></span><div><h3></h3><p>`);
|
|
18
|
+
import { clipboard } from "@k2b/stdlib/solid";
|
|
19
|
+
import { createMemo, For, Show } from "solid-js";
|
|
20
|
+
import { Button } from "../actions/Button";
|
|
21
|
+
import { useUiMessages } from "../intl/messages";
|
|
22
|
+
import { NoticeCard } from "../surfaces/NoticeCard";
|
|
23
|
+
/**
|
|
24
|
+
* Explains how to install the current page as an app on this platform: the browser's own dialog where it offers
|
|
25
|
+
* one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other
|
|
26
|
+
* browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or
|
|
27
|
+
* Chrome instead. The host owns the surrounding surface, its heading, and dismissal.
|
|
28
|
+
*/
|
|
29
|
+
export function InstallGuide(props) {
|
|
30
|
+
const messages = useUiMessages();
|
|
31
|
+
// The host may hand over another state after mount, such as the browser's own in place of the server's view.
|
|
32
|
+
const state = () => props.install;
|
|
33
|
+
const copy = clipboard.createWriter({
|
|
34
|
+
write: url => navigator.clipboard.writeText(url)
|
|
35
|
+
});
|
|
36
|
+
const embedded = () => state().platform === "in-app" || state().platform === "apple-in-app";
|
|
37
|
+
/** A browser that installs, but less reliably than the platform's own. */
|
|
38
|
+
const hint = createMemo(() => {
|
|
39
|
+
const t = messages();
|
|
40
|
+
if (state().platform === "apple-browser") return {
|
|
41
|
+
title: t.installAppleBrowserTitle,
|
|
42
|
+
detail: t.installAppleBrowserDetail
|
|
43
|
+
};
|
|
44
|
+
if (state().platform === "android-browser") return {
|
|
45
|
+
title: t.installAndroidBrowserTitle,
|
|
46
|
+
detail: t.installAndroidBrowserDetail
|
|
47
|
+
};
|
|
48
|
+
return undefined;
|
|
49
|
+
});
|
|
50
|
+
const steps = createMemo(() => {
|
|
51
|
+
const t = messages();
|
|
52
|
+
if (state().platform === "apple-mobile" || state().platform === "apple-browser") return [{
|
|
53
|
+
icon: "ti ti-share-2",
|
|
54
|
+
title: t.installOpenShare,
|
|
55
|
+
detail: state().platform === "apple-browser" ? t.installOpenShareBrowserDetail : t.installOpenShareDetail
|
|
56
|
+
}, {
|
|
57
|
+
icon: "ti ti-square-plus",
|
|
58
|
+
title: t.installHomeScreen,
|
|
59
|
+
detail: t.installHomeScreenDetail
|
|
60
|
+
}, {
|
|
61
|
+
icon: "ti ti-check",
|
|
62
|
+
title: t.installConfirmAdd,
|
|
63
|
+
detail: t.installConfirmAddDetail
|
|
64
|
+
}];
|
|
65
|
+
if (state().platform === "apple-desktop") return [{
|
|
66
|
+
icon: "ti ti-share-2",
|
|
67
|
+
title: t.installSafariMenu,
|
|
68
|
+
detail: t.installSafariMenuDetail
|
|
69
|
+
}, {
|
|
70
|
+
icon: "ti ti-app-window",
|
|
71
|
+
title: t.installAddDock,
|
|
72
|
+
detail: t.installAddDockDetail
|
|
73
|
+
}];
|
|
74
|
+
return [{
|
|
75
|
+
icon: "ti ti-dots",
|
|
76
|
+
title: t.installBrowserMenu,
|
|
77
|
+
detail: t.installBrowserMenuDetail
|
|
78
|
+
}, {
|
|
79
|
+
icon: "ti ti-square-plus",
|
|
80
|
+
title: t.installApp,
|
|
81
|
+
detail: state().platform === "android" || state().platform === "android-browser" ? t.installAndroidDetail : t.installBrowserDetail
|
|
82
|
+
}];
|
|
83
|
+
});
|
|
84
|
+
return (() => {
|
|
85
|
+
var _el$ = _$getNextElement(_tmpl$3),
|
|
86
|
+
_el$4 = _el$.firstChild,
|
|
87
|
+
[_el$5, _co$] = _$getNextMarker(_el$4.nextSibling),
|
|
88
|
+
_el$6 = _el$5.nextSibling,
|
|
89
|
+
[_el$7, _co$2] = _$getNextMarker(_el$6.nextSibling);
|
|
90
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
91
|
+
get when() {
|
|
92
|
+
return state().failed();
|
|
93
|
+
},
|
|
94
|
+
get children() {
|
|
95
|
+
var _el$2 = _$getNextElement(_tmpl$);
|
|
96
|
+
_$insert(_el$2, () => messages().installFailed);
|
|
97
|
+
return _el$2;
|
|
98
|
+
}
|
|
99
|
+
}), _el$5, _co$);
|
|
100
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
101
|
+
get when() {
|
|
102
|
+
return state().requested();
|
|
103
|
+
},
|
|
104
|
+
get fallback() {
|
|
105
|
+
return _$createComponent(Show, {
|
|
106
|
+
get when() {
|
|
107
|
+
return state().canPrompt() || state().busy();
|
|
108
|
+
},
|
|
109
|
+
get fallback() {
|
|
110
|
+
return _$createComponent(Show, {
|
|
111
|
+
get when() {
|
|
112
|
+
return embedded();
|
|
113
|
+
},
|
|
114
|
+
get fallback() {
|
|
115
|
+
return [_$createComponent(Show, {
|
|
116
|
+
get when() {
|
|
117
|
+
return hint();
|
|
118
|
+
},
|
|
119
|
+
children: hint => _$createComponent(NoticeCard, {
|
|
120
|
+
tone: "info",
|
|
121
|
+
get title() {
|
|
122
|
+
return hint().title;
|
|
123
|
+
},
|
|
124
|
+
get detail() {
|
|
125
|
+
return hint().detail;
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}), (() => {
|
|
129
|
+
var _el$11 = _$getNextElement(_tmpl$7);
|
|
130
|
+
_$insert(_el$11, _$createComponent(For, {
|
|
131
|
+
get each() {
|
|
132
|
+
return steps();
|
|
133
|
+
},
|
|
134
|
+
children: step => (() => {
|
|
135
|
+
var _el$15 = _$getNextElement(_tmpl$8),
|
|
136
|
+
_el$16 = _el$15.firstChild,
|
|
137
|
+
_el$17 = _el$16.firstChild,
|
|
138
|
+
_el$18 = _el$16.nextSibling,
|
|
139
|
+
_el$19 = _el$18.firstChild,
|
|
140
|
+
_el$20 = _el$19.nextSibling;
|
|
141
|
+
_$insert(_el$19, () => step.title);
|
|
142
|
+
_$insert(_el$20, () => step.detail);
|
|
143
|
+
_$effect(() => _$className(_el$17, step.icon));
|
|
144
|
+
return _el$15;
|
|
145
|
+
})()
|
|
146
|
+
}));
|
|
147
|
+
return _el$11;
|
|
148
|
+
})(), _$createComponent(Show, {
|
|
149
|
+
get when() {
|
|
150
|
+
return state().platform === "apple-mobile";
|
|
151
|
+
},
|
|
152
|
+
get children() {
|
|
153
|
+
var _el$12 = _$getNextElement(_tmpl$4);
|
|
154
|
+
_$insert(_el$12, () => messages().installSafariFallback);
|
|
155
|
+
return _el$12;
|
|
156
|
+
}
|
|
157
|
+
}), _$createComponent(Show, {
|
|
158
|
+
get when() {
|
|
159
|
+
return props.note;
|
|
160
|
+
},
|
|
161
|
+
get children() {
|
|
162
|
+
var _el$13 = _$getNextElement(_tmpl$4);
|
|
163
|
+
_$insert(_el$13, () => props.note);
|
|
164
|
+
return _el$13;
|
|
165
|
+
}
|
|
166
|
+
}), _$createComponent(Show, {
|
|
167
|
+
get when() {
|
|
168
|
+
return state().platform === "generic";
|
|
169
|
+
},
|
|
170
|
+
get children() {
|
|
171
|
+
var _el$14 = _$getNextElement(_tmpl$4);
|
|
172
|
+
_$insert(_el$14, () => messages().installUnavailable);
|
|
173
|
+
return _el$14;
|
|
174
|
+
}
|
|
175
|
+
})];
|
|
176
|
+
},
|
|
177
|
+
get children() {
|
|
178
|
+
return [_$createComponent(NoticeCard, {
|
|
179
|
+
tone: "warning",
|
|
180
|
+
get title() {
|
|
181
|
+
return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppTitle : messages().installInAppTitle;
|
|
182
|
+
},
|
|
183
|
+
get detail() {
|
|
184
|
+
return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppDetail({
|
|
185
|
+
appName: props.appName
|
|
186
|
+
}) : messages().installInAppDetail({
|
|
187
|
+
appName: props.appName
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}), _$createComponent(Button, {
|
|
191
|
+
variant: "secondary",
|
|
192
|
+
onClick: () => void copy.copy(props.url),
|
|
193
|
+
get children() {
|
|
194
|
+
return [(() => {
|
|
195
|
+
var _el$9 = _$getNextElement(_tmpl$5);
|
|
196
|
+
_$effect(() => _$className(_el$9, copy.wasCopied() ? "ti ti-check" : "ti ti-copy"));
|
|
197
|
+
return _el$9;
|
|
198
|
+
})(), _$memo(() => _$memo(() => !!copy.wasCopied())() ? messages().installLinkCopied : messages().installCopyLink)];
|
|
199
|
+
}
|
|
200
|
+
}), _$createComponent(Show, {
|
|
201
|
+
get when() {
|
|
202
|
+
return copy.error();
|
|
203
|
+
},
|
|
204
|
+
get children() {
|
|
205
|
+
return [(() => {
|
|
206
|
+
var _el$0 = _$getNextElement(_tmpl$);
|
|
207
|
+
_$insert(_el$0, () => messages().installCopyFailed);
|
|
208
|
+
return _el$0;
|
|
209
|
+
})(), (() => {
|
|
210
|
+
var _el$1 = _$getNextElement(_tmpl$6);
|
|
211
|
+
_$insert(_el$1, () => props.url);
|
|
212
|
+
return _el$1;
|
|
213
|
+
})()];
|
|
214
|
+
}
|
|
215
|
+
}), _$createComponent(Show, {
|
|
216
|
+
get when() {
|
|
217
|
+
return props.note;
|
|
218
|
+
},
|
|
219
|
+
get children() {
|
|
220
|
+
var _el$10 = _$getNextElement(_tmpl$4);
|
|
221
|
+
_$insert(_el$10, () => props.note);
|
|
222
|
+
return _el$10;
|
|
223
|
+
}
|
|
224
|
+
})];
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
get children() {
|
|
229
|
+
return [(() => {
|
|
230
|
+
var _el$8 = _$getNextElement(_tmpl$4);
|
|
231
|
+
_$insert(_el$8, () => messages().installNative({
|
|
232
|
+
appName: props.appName
|
|
233
|
+
}));
|
|
234
|
+
return _el$8;
|
|
235
|
+
})(), _$createComponent(Button, {
|
|
236
|
+
get loading() {
|
|
237
|
+
return state().busy();
|
|
238
|
+
},
|
|
239
|
+
onClick: () => void state().install(),
|
|
240
|
+
get children() {
|
|
241
|
+
return messages().installApp;
|
|
242
|
+
}
|
|
243
|
+
})];
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
get children() {
|
|
248
|
+
var _el$3 = _$getNextElement(_tmpl$2);
|
|
249
|
+
_$insert(_el$3, () => messages().installRequested);
|
|
250
|
+
return _el$3;
|
|
251
|
+
}
|
|
252
|
+
}), _el$7, _co$2);
|
|
253
|
+
_$effect(_p$ => {
|
|
254
|
+
var _v$ = props.class ? `k2b-install-guide ${props.class}` : "k2b-install-guide",
|
|
255
|
+
_v$2 = state().platform;
|
|
256
|
+
_v$ !== _p$.e && _$className(_el$, _p$.e = _v$);
|
|
257
|
+
_v$2 !== _p$.t && _$setAttribute(_el$, "data-platform", _p$.t = _v$2);
|
|
258
|
+
return _p$;
|
|
259
|
+
}, {
|
|
260
|
+
e: undefined,
|
|
261
|
+
t: undefined
|
|
262
|
+
});
|
|
263
|
+
return _el$;
|
|
264
|
+
})();
|
|
265
|
+
}
|
|
266
|
+
export default InstallGuide;
|
|
267
|
+
//# sourceMappingURL=InstallGuide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["feedback/InstallGuide.tsx"],"sourcesContent":["import { clipboard } from \"@k2b/stdlib/solid\";\nimport { createMemo, For, type JSX, Show } from \"solid-js\";\nimport { Button } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { NoticeCard } from \"../surfaces/NoticeCard\";\nimport type { InstallPrompt } from \"./install\";\n\nexport type InstallGuideProps = {\n /** The name people see on their Home Screen, used in the guidance text. */\n appName: string;\n install: InstallPrompt;\n /**\n * The address a browser inside another app copies, to open it in Safari or Chrome. Shown as text when copying\n * fails, so it can be copied by hand.\n */\n url: string;\n /**\n * An application note under the installation steps or the embedded browser's link, for example what else the\n * installed app enables.\n */\n note?: string;\n class?: string;\n};\n\n/**\n * Explains how to install the current page as an app on this platform: the browser's own dialog where it offers\n * one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other\n * browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or\n * Chrome instead. The host owns the surrounding surface, its heading, and dismissal.\n */\nexport function InstallGuide(props: InstallGuideProps): JSX.Element {\n const messages = useUiMessages();\n // The host may hand over another state after mount, such as the browser's own in place of the server's view.\n const state = () => props.install;\n const copy = clipboard.createWriter({ write: (url: string) => navigator.clipboard.writeText(url) });\n const embedded = () => state().platform === \"in-app\" || state().platform === \"apple-in-app\";\n /** A browser that installs, but less reliably than the platform's own. */\n const hint = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-browser\") return { title: t.installAppleBrowserTitle, detail: t.installAppleBrowserDetail };\n if (state().platform === \"android-browser\") return { title: t.installAndroidBrowserTitle, detail: t.installAndroidBrowserDetail };\n return undefined;\n });\n const steps = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-mobile\" || state().platform === \"apple-browser\")\n return [\n {\n icon: \"ti ti-share-2\",\n title: t.installOpenShare,\n detail: state().platform === \"apple-browser\" ? t.installOpenShareBrowserDetail : t.installOpenShareDetail,\n },\n { icon: \"ti ti-square-plus\", title: t.installHomeScreen, detail: t.installHomeScreenDetail },\n { icon: \"ti ti-check\", title: t.installConfirmAdd, detail: t.installConfirmAddDetail },\n ];\n if (state().platform === \"apple-desktop\")\n return [\n { icon: \"ti ti-share-2\", title: t.installSafariMenu, detail: t.installSafariMenuDetail },\n { icon: \"ti ti-app-window\", title: t.installAddDock, detail: t.installAddDockDetail },\n ];\n return [\n { icon: \"ti ti-dots\", title: t.installBrowserMenu, detail: t.installBrowserMenuDetail },\n {\n icon: \"ti ti-square-plus\",\n title: t.installApp,\n detail: state().platform === \"android\" || state().platform === \"android-browser\" ? t.installAndroidDetail : t.installBrowserDetail,\n },\n ];\n });\n return (\n <div class={props.class ? `k2b-install-guide ${props.class}` : \"k2b-install-guide\"} data-platform={state().platform}>\n <Show when={state().failed()}>\n <p role=\"alert\">{messages().installFailed}</p>\n </Show>\n <Show\n when={state().requested()}\n fallback={\n <Show\n when={state().canPrompt() || state().busy()}\n fallback={\n <Show\n when={embedded()}\n fallback={\n <>\n <Show when={hint()}>{(hint) => <NoticeCard tone=\"info\" title={hint().title} detail={hint().detail} />}</Show>\n <ol class=\"k2b-install-guide__steps\">\n <For each={steps()}>\n {(step) => (\n <li>\n <span class=\"k2b-install-guide__step-icon\">\n <i class={step.icon} aria-hidden=\"true\" />\n </span>\n <div>\n <h3>{step.title}</h3>\n <p>{step.detail}</p>\n </div>\n </li>\n )}\n </For>\n </ol>\n <Show when={state().platform === \"apple-mobile\"}>\n <p>{messages().installSafariFallback}</p>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n <Show when={state().platform === \"generic\"}>\n <p>{messages().installUnavailable}</p>\n </Show>\n </>\n }\n >\n <NoticeCard\n tone=\"warning\"\n title={state().platform === \"apple-in-app\" ? messages().installAppleInAppTitle : messages().installInAppTitle}\n detail={\n state().platform === \"apple-in-app\"\n ? messages().installAppleInAppDetail({ appName: props.appName })\n : messages().installInAppDetail({ appName: props.appName })\n }\n />\n <Button variant=\"secondary\" onClick={() => void copy.copy(props.url)}>\n <i class={copy.wasCopied() ? \"ti ti-check\" : \"ti ti-copy\"} aria-hidden=\"true\" />\n {copy.wasCopied() ? messages().installLinkCopied : messages().installCopyLink}\n </Button>\n <Show when={copy.error()}>\n <p role=\"alert\">{messages().installCopyFailed}</p>\n <code>{props.url}</code>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n </Show>\n }\n >\n <p>{messages().installNative({ appName: props.appName })}</p>\n <Button loading={state().busy()} onClick={() => void state().install()}>\n {messages().installApp}\n </Button>\n </Show>\n }\n >\n <p role=\"status\">{messages().installRequested}</p>\n </Show>\n </div>\n );\n}\n\nexport default InstallGuide;\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** A live region needs a moment between being found and being written to. */
|
|
2
|
+
const ANNOUNCE_DELAY_MS = 100;
|
|
3
|
+
const ANNOUNCEMENT_LIFETIME_MS = 7_000;
|
|
4
|
+
const LIVE_ATTRIBUTE = "data-k2b-live";
|
|
5
|
+
const VISUALLY_HIDDEN = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One pair of persistent, empty live regions per document, polite and assertive, shared by `announce` and every
|
|
9
|
+
* toast. A region that already exists when its text arrives is announced reliably, unlike one inserted with its text
|
|
10
|
+
* already inside. They sit directly in the body, outside every portal and the toast rail, which moves into a fresh
|
|
11
|
+
* top-layer element for every toast, so no scope that closes takes them along.
|
|
12
|
+
*/
|
|
13
|
+
export const ensureLiveRegions = doc => {
|
|
14
|
+
const existing = doc.querySelector(`[${LIVE_ATTRIBUTE}]`);
|
|
15
|
+
if (existing) return existing;
|
|
16
|
+
const live = doc.createElement("div");
|
|
17
|
+
live.setAttribute(LIVE_ATTRIBUTE, "");
|
|
18
|
+
live.style.cssText = VISUALLY_HIDDEN;
|
|
19
|
+
for (const politeness of ["polite", "assertive"]) {
|
|
20
|
+
const region = doc.createElement("div");
|
|
21
|
+
region.setAttribute("role", politeness === "assertive" ? "alert" : "status");
|
|
22
|
+
region.setAttribute("aria-live", politeness);
|
|
23
|
+
// Both roles are atomic by default, which would read every line still in the region again with each new one.
|
|
24
|
+
region.setAttribute("aria-atomic", "false");
|
|
25
|
+
region.dataset.politeness = politeness;
|
|
26
|
+
live.appendChild(region);
|
|
27
|
+
}
|
|
28
|
+
doc.body.appendChild(live);
|
|
29
|
+
return live;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tells screen readers about an outcome without showing anything: a change the screen shows but the focused control
|
|
34
|
+
* does not say, because a shortcut acted, the control was busy, or it was replaced. Each message is its own line, so
|
|
35
|
+
* a burst of messages or the same message twice is still read. Calls without `document`, including during SSR, do
|
|
36
|
+
* nothing.
|
|
37
|
+
*/
|
|
38
|
+
export const announce = (message, options) => {
|
|
39
|
+
if (typeof document === "undefined" || !message) return;
|
|
40
|
+
const politeness = options?.politeness ?? "polite";
|
|
41
|
+
const region = ensureLiveRegions(document).querySelector(`[data-politeness="${politeness}"]`);
|
|
42
|
+
if (!region) return;
|
|
43
|
+
const line = document.createElement("div");
|
|
44
|
+
line.textContent = message;
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
region.appendChild(line);
|
|
47
|
+
setTimeout(() => line.remove(), ANNOUNCEMENT_LIFETIME_MS);
|
|
48
|
+
}, ANNOUNCE_DELAY_MS);
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=announce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["feedback/announce.ts"],"sourcesContent":["export type AnnouncePoliteness = \"polite\" | \"assertive\";\n\nexport type AnnounceOptions = {\n /** `polite` waits until the screen reader is idle; `assertive` interrupts it. Defaults to `polite`. */\n politeness?: AnnouncePoliteness;\n};\n\n/** A live region needs a moment between being found and being written to. */\nconst ANNOUNCE_DELAY_MS = 100;\nconst ANNOUNCEMENT_LIFETIME_MS = 7_000;\nconst LIVE_ATTRIBUTE = \"data-k2b-live\";\nconst VISUALLY_HIDDEN =\n \"position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;\";\n\n/**\n * One pair of persistent, empty live regions per document, polite and assertive, shared by `announce` and every\n * toast. A region that already exists when its text arrives is announced reliably, unlike one inserted with its text\n * already inside. They sit directly in the body, outside every portal and the toast rail, which moves into a fresh\n * top-layer element for every toast, so no scope that closes takes them along.\n */\nexport const ensureLiveRegions = (doc: Document): HTMLElement => {\n const existing = doc.querySelector<HTMLElement>(`[${LIVE_ATTRIBUTE}]`);\n if (existing) return existing;\n const live = doc.createElement(\"div\");\n live.setAttribute(LIVE_ATTRIBUTE, \"\");\n live.style.cssText = VISUALLY_HIDDEN;\n for (const politeness of [\"polite\", \"assertive\"] as const) {\n const region = doc.createElement(\"div\");\n region.setAttribute(\"role\", politeness === \"assertive\" ? \"alert\" : \"status\");\n region.setAttribute(\"aria-live\", politeness);\n // Both roles are atomic by default, which would read every line still in the region again with each new one.\n region.setAttribute(\"aria-atomic\", \"false\");\n region.dataset.politeness = politeness;\n live.appendChild(region);\n }\n doc.body.appendChild(live);\n return live;\n};\n\n/**\n * Tells screen readers about an outcome without showing anything: a change the screen shows but the focused control\n * does not say, because a shortcut acted, the control was busy, or it was replaced. Each message is its own line, so\n * a burst of messages or the same message twice is still read. Calls without `document`, including during SSR, do\n * nothing.\n */\nexport const announce = (message: string, options?: AnnounceOptions): void => {\n if (typeof document === \"undefined\" || !message) return;\n const politeness = options?.politeness ?? \"polite\";\n const region = ensureLiveRegions(document).querySelector<HTMLElement>(`[data-politeness=\"${politeness}\"]`);\n if (!region) return;\n const line = document.createElement(\"div\");\n line.textContent = message;\n setTimeout(() => {\n region.appendChild(line);\n setTimeout(() => line.remove(), ANNOUNCEMENT_LIFETIME_MS);\n }, ANNOUNCE_DELAY_MS);\n};\n"],"mappings":"AAOA;AACA,MAAM,iBAAiB,GAAG,GAAG;AAC7B,MAAM,wBAAwB,GAAG,KAAK;AACtC,MAAM,cAAc,GAAG,eAAe;AACtC,MAAM,eAAe,GACnB,gIAAgI;;AAElI;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAM,iBAAiB,GAAI,GAAa,IAAkB;EAC/D,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAc,IAAI,cAAc,GAAG,CAAC;EACtE,IAAI,QAAQ,EAAE,OAAO,QAAQ;EAC7B,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC;EACrC,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,CAAC;EACrC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,eAAe;EACpC,KAAK,MAAM,UAAU,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAW;IACzD,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC;IACvC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5E,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC;IAC5C;IACA,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC;IAC3C,MAAM,CAAC,OAAO,CAAC,UAAU,GAAG,UAAU;IACtC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;EAC1B;EACA,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;EAC1B,OAAO,IAAI;AACb,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,OAAyB,KAAW;EAC5E,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,CAAC,OAAO,EAAE;EACjD,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,QAAQ;EAClD,MAAM,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAc,qBAAqB,UAAU,IAAI,CAAC;EAC1G,IAAI,CAAC,MAAM,EAAE;EACb,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;EAC1C,IAAI,CAAC,WAAW,GAAG,OAAO;EAC1B,UAAU,CAAC,MAAM;IACf,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;IACxB,UAAU,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,wBAAwB,CAAC;EAC3D,CAAC,EAAE,iBAAiB,CAAC;AACvB,CAAC","ignoreList":[]}
|
|
@@ -6,6 +6,12 @@ import { isPointInsideToast } from "./toast";
|
|
|
6
6
|
const DEFAULT_PANEL_CLASS = "k2b-dialog";
|
|
7
7
|
const DEFAULT_CONTENT_CLASS = "k2b-dialog__viewport";
|
|
8
8
|
let nextDialogTitleId = 0;
|
|
9
|
+
/** The history state key that marks a dialog's same-URL entry. */
|
|
10
|
+
const HISTORY_MARKER = "k2bDialog";
|
|
11
|
+
const historyRecord = () => typeof history.state === "object" && history.state !== null ? {
|
|
12
|
+
...history.state
|
|
13
|
+
} : {};
|
|
14
|
+
const historyMarker = () => historyRecord()[HISTORY_MARKER];
|
|
9
15
|
const resolveInitialFocusTarget = (entry, dialog) => {
|
|
10
16
|
const {
|
|
11
17
|
initialFocus
|
|
@@ -47,6 +53,67 @@ export const createDialogCore = () => {
|
|
|
47
53
|
const state = {
|
|
48
54
|
stack: []
|
|
49
55
|
};
|
|
56
|
+
/** Markers of this core's entries in the browser history, oldest first. */
|
|
57
|
+
let historyIds = [];
|
|
58
|
+
let nextHistoryId = 0;
|
|
59
|
+
let historySyncScheduled = false;
|
|
60
|
+
let listeningToHistory = false;
|
|
61
|
+
/** Settles when the synthetic Back that removes closed dialogs' entries has arrived. */
|
|
62
|
+
let returning;
|
|
63
|
+
|
|
64
|
+
/** Closed dialogs leave the history with one Back to the newest dialog that is still open, or to the page. */
|
|
65
|
+
const syncHistory = () => {
|
|
66
|
+
historySyncScheduled = false;
|
|
67
|
+
const openIds = new Set(state.stack.map(entry => entry.historyId));
|
|
68
|
+
const current = historyIds.at(-1);
|
|
69
|
+
let steps = 0;
|
|
70
|
+
while (historyIds.length > 0 && !openIds.has(historyIds.at(-1))) {
|
|
71
|
+
historyIds.pop();
|
|
72
|
+
steps++;
|
|
73
|
+
}
|
|
74
|
+
// Someone else moved the history meanwhile; leave it alone.
|
|
75
|
+
if (steps === 0 || historyMarker() !== current) return;
|
|
76
|
+
const back = new Promise(resolve => window.addEventListener("popstate", () => resolve(), {
|
|
77
|
+
once: true
|
|
78
|
+
}));
|
|
79
|
+
returning = back;
|
|
80
|
+
void back.then(() => {
|
|
81
|
+
if (returning === back) returning = undefined;
|
|
82
|
+
});
|
|
83
|
+
history.go(-steps);
|
|
84
|
+
};
|
|
85
|
+
const releaseHistory = entry => {
|
|
86
|
+
if (entry.historyId === undefined || historySyncScheduled) return;
|
|
87
|
+
historySyncScheduled = true;
|
|
88
|
+
queueMicrotask(syncHistory);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Back or Forward moved to another entry: close every dialog opened after the one it shows. */
|
|
92
|
+
const onPopState = () => {
|
|
93
|
+
const marker = historyMarker();
|
|
94
|
+
const index = typeof marker === "number" ? historyIds.indexOf(marker) : -1;
|
|
95
|
+
historyIds = historyIds.slice(0, index + 1);
|
|
96
|
+
const gone = entry => entry.historyId !== undefined && !historyIds.includes(entry.historyId);
|
|
97
|
+
while (state.stack.some(gone)) popTop(undefined);
|
|
98
|
+
};
|
|
99
|
+
const pushHistory = entry => {
|
|
100
|
+
if (!listeningToHistory) {
|
|
101
|
+
window.addEventListener("popstate", onPopState);
|
|
102
|
+
listeningToHistory = true;
|
|
103
|
+
}
|
|
104
|
+
const previous = historyRecord();
|
|
105
|
+
// A reload or Forward never brings back a dialog or its sensitive transient state.
|
|
106
|
+
if (historyIds.length === 0 && previous[HISTORY_MARKER] !== undefined) {
|
|
107
|
+
delete previous[HISTORY_MARKER];
|
|
108
|
+
history.replaceState(previous, "");
|
|
109
|
+
}
|
|
110
|
+
entry.historyId = ++nextHistoryId;
|
|
111
|
+
historyIds.push(entry.historyId);
|
|
112
|
+
history.pushState({
|
|
113
|
+
...previous,
|
|
114
|
+
[HISTORY_MARKER]: entry.historyId
|
|
115
|
+
}, "");
|
|
116
|
+
};
|
|
50
117
|
const ensureDialogElement = () => {
|
|
51
118
|
if (typeof document === "undefined") throw new Error("@k2b/ui dialogs can only be opened in the browser");
|
|
52
119
|
// Reuse the shared element whenever it is still in the document. Resolving
|
|
@@ -159,6 +226,7 @@ export const createDialogCore = () => {
|
|
|
159
226
|
const popTop = result => {
|
|
160
227
|
const top = state.stack.pop();
|
|
161
228
|
if (!top) return;
|
|
229
|
+
releaseHistory(top);
|
|
162
230
|
top.dispose?.();
|
|
163
231
|
top.container.remove();
|
|
164
232
|
const dialog = state.element;
|
|
@@ -201,6 +269,7 @@ export const createDialogCore = () => {
|
|
|
201
269
|
dialog?.remove();
|
|
202
270
|
unlockPageScroll();
|
|
203
271
|
for (const entry of entries.reverse()) {
|
|
272
|
+
releaseHistory(entry);
|
|
204
273
|
entry.dispose?.();
|
|
205
274
|
entry.container.remove();
|
|
206
275
|
entry.resolve?.(undefined);
|
|
@@ -217,6 +286,11 @@ export const createDialogCore = () => {
|
|
|
217
286
|
};
|
|
218
287
|
const open = (view, options = {}) => {
|
|
219
288
|
if (options.signal?.aborted) return Promise.resolve(undefined);
|
|
289
|
+
if (options.history) {
|
|
290
|
+
// Dialogs closed just now leave the history first, or this dialog's entry would be the one removed.
|
|
291
|
+
if (historySyncScheduled) syncHistory();
|
|
292
|
+
if (returning) return returning.then(() => open(view, options));
|
|
293
|
+
}
|
|
220
294
|
const dialog = ensureDialogElement();
|
|
221
295
|
const previousTop = state.stack[state.stack.length - 1];
|
|
222
296
|
if (previousTop) previousTop.container.style.display = "none";
|
|
@@ -244,6 +318,7 @@ export const createDialogCore = () => {
|
|
|
244
318
|
if (index < 0) return;
|
|
245
319
|
if (index === state.stack.length - 1) popTop(undefined);else {
|
|
246
320
|
state.stack.splice(index, 1);
|
|
321
|
+
releaseHistory(entry);
|
|
247
322
|
entry.dispose?.();
|
|
248
323
|
entry.container.remove();
|
|
249
324
|
entry.resolve?.(undefined);
|
|
@@ -251,12 +326,18 @@ export const createDialogCore = () => {
|
|
|
251
326
|
};
|
|
252
327
|
entry.resolve = value => {
|
|
253
328
|
options.signal?.removeEventListener("abort", abort);
|
|
254
|
-
|
|
329
|
+
if (entry.historyId === undefined) {
|
|
330
|
+
resolve(value);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// After the history sync, so a caller that navigates or opens the next dialog finds the history settled.
|
|
334
|
+
queueMicrotask(() => void (returning ?? Promise.resolve()).then(() => resolve(value)));
|
|
255
335
|
};
|
|
256
336
|
const closeTyped = result => {
|
|
257
337
|
if (state.stack[state.stack.length - 1] !== entry) return;
|
|
258
338
|
popTop(result);
|
|
259
339
|
};
|
|
340
|
+
if (options.history) pushHistory(entry);
|
|
260
341
|
state.stack.push(entry);
|
|
261
342
|
try {
|
|
262
343
|
entry.dispose = render(() => view(closeTyped, {
|
|
@@ -292,8 +373,13 @@ export const createDialogCore = () => {
|
|
|
292
373
|
once: true
|
|
293
374
|
});
|
|
294
375
|
if (options.signal?.aborted) abort();
|
|
376
|
+
// Where opening left focus, the native dialog's own pick included. Focus that the content or a person moved
|
|
377
|
+
// before the next frame stays: a browser can hold that frame back while keys already arrive.
|
|
378
|
+
const opened = document.activeElement;
|
|
295
379
|
schedule(() => {
|
|
296
|
-
if (state.stack[state.stack.length - 1]
|
|
380
|
+
if (state.stack[state.stack.length - 1] !== entry) return;
|
|
381
|
+
if (document.activeElement !== opened && !focusUnclaimed(dialog)) return;
|
|
382
|
+
resolveInitialFocusTarget(entry, dialog)?.focus();
|
|
297
383
|
});
|
|
298
384
|
} catch (error) {
|
|
299
385
|
entry.resolve = undefined;
|