@k2b/ui 0.17.0 → 0.19.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 +3 -2
- package/dist/browser/actions/ContextMenu.js +3 -1
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/Dropdown.js +91 -46
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/FilterChip.js +4 -1
- package/dist/browser/actions/FilterChip.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +1311 -565
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/ChatPrimitives.js +9 -6
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/chat/chat-behavior.js +9 -0
- package/dist/browser/chat/chat-behavior.js.map +1 -1
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/chat/types.js.map +1 -1
- package/dist/browser/content/Calendar.js +579 -533
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/FileView.js +62 -36
- package/dist/browser/content/FileView.js.map +1 -1
- package/dist/browser/content/Lightbox.js +76 -40
- package/dist/browser/content/Lightbox.js.map +1 -1
- package/dist/browser/content/MarkdownView.js +9 -3
- package/dist/browser/content/MarkdownView.js.map +1 -1
- package/dist/browser/content/PdfPreview.js +146 -79
- package/dist/browser/content/PdfPreview.js.map +1 -1
- package/dist/browser/content/Timeline.js +1038 -0
- package/dist/browser/content/Timeline.js.map +1 -0
- package/dist/browser/content/VideoPlayer.js +299 -0
- package/dist/browser/content/VideoPlayer.js.map +1 -0
- package/dist/browser/content/chart-inspection.js +7 -2
- package/dist/browser/content/chart-inspection.js.map +1 -1
- package/dist/browser/content/file-view-preview.js +2 -2
- package/dist/browser/content/file-view-preview.js.map +1 -1
- package/dist/browser/content/index.js +3 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/markdown-links.js +178 -0
- package/dist/browser/content/markdown-links.js.map +1 -0
- package/dist/browser/content/timeline-model.js +579 -0
- package/dist/browser/content/timeline-model.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +26 -14
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/inputs/ChoiceChips.js +66 -0
- package/dist/browser/inputs/ChoiceChips.js.map +1 -0
- package/dist/browser/inputs/ChoiceGroups.js +53 -12
- package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
- package/dist/browser/inputs/FileDropTarget.js +129 -0
- package/dist/browser/inputs/FileDropTarget.js.map +1 -0
- package/dist/browser/inputs/FileInputs.js +84 -43
- package/dist/browser/inputs/FileInputs.js.map +1 -1
- package/dist/browser/inputs/SignatureInput.js +558 -0
- package/dist/browser/inputs/SignatureInput.js.map +1 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js +841 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-data.js +1903 -0
- package/dist/browser/inputs/emoji/emoji-data.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-index.js +209 -0
- package/dist/browser/inputs/emoji/emoji-index.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-messages.js +64 -0
- package/dist/browser/inputs/emoji/emoji-messages.js.map +1 -0
- package/dist/browser/inputs/file-drop.js +338 -0
- package/dist/browser/inputs/file-drop.js.map +1 -0
- package/dist/browser/inputs/index.js +6 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/inputs/markdown/MarkdownEditor.js +72 -78
- package/dist/browser/inputs/markdown/MarkdownEditor.js.map +1 -1
- package/dist/browser/inputs/markdown/actions.js +48 -2
- package/dist/browser/inputs/markdown/actions.js.map +1 -1
- package/dist/browser/inputs/markdown/code-zone.js +38 -2
- package/dist/browser/inputs/markdown/code-zone.js.map +1 -1
- package/dist/browser/inputs/signature-messages.js +39 -0
- package/dist/browser/inputs/signature-messages.js.map +1 -0
- package/dist/browser/inputs/signature.js +208 -0
- package/dist/browser/inputs/signature.js.map +1 -0
- package/dist/browser/internal/render-error.js +49 -0
- package/dist/browser/internal/render-error.js.map +1 -0
- package/dist/browser/intl/messages.js +205 -4
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +483 -218
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/FloatingWindow.js +4 -1
- package/dist/browser/layout/FloatingWindow.js.map +1 -1
- package/dist/browser/layout/Navigation.js +274 -197
- package/dist/browser/layout/Navigation.js.map +1 -1
- package/dist/browser/layout/SidebarItemPreview.js +5 -1
- package/dist/browser/layout/SidebarItemPreview.js.map +1 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/layout/navigation-model.js +2 -0
- package/dist/browser/layout/navigation-model.js.map +1 -1
- package/dist/browser/surfaces/NoticeCard.js +47 -44
- package/dist/browser/surfaces/NoticeCard.js.map +1 -1
- package/dist/browser/surfaces/ResourceCard.js +212 -0
- package/dist/browser/surfaces/ResourceCard.js.map +1 -0
- package/dist/browser/surfaces/index.js +1 -0
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/licenses/emojibase-data.txt +21 -0
- package/dist/ssr/actions/ContextMenu.js +3 -1
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/Dropdown.js +49 -12
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/FilterChip.js +4 -1
- package/dist/ssr/actions/FilterChip.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +823 -221
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +9 -6
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/chat/chat-behavior.js +9 -0
- package/dist/ssr/chat/chat-behavior.js.map +1 -1
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/chat/types.js.map +1 -1
- package/dist/ssr/content/Calendar.js +183 -142
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/FileView.js +30 -6
- package/dist/ssr/content/FileView.js.map +1 -1
- package/dist/ssr/content/Lightbox.js +19 -5
- package/dist/ssr/content/Lightbox.js.map +1 -1
- package/dist/ssr/content/MarkdownView.js +9 -3
- package/dist/ssr/content/MarkdownView.js.map +1 -1
- package/dist/ssr/content/PdfPreview.js +77 -36
- package/dist/ssr/content/PdfPreview.js.map +1 -1
- package/dist/ssr/content/Timeline.js +804 -0
- package/dist/ssr/content/Timeline.js.map +1 -0
- package/dist/ssr/content/VideoPlayer.js +228 -0
- package/dist/ssr/content/VideoPlayer.js.map +1 -0
- package/dist/ssr/content/chart-inspection.js +7 -2
- package/dist/ssr/content/chart-inspection.js.map +1 -1
- package/dist/ssr/content/file-view-preview.js +2 -2
- package/dist/ssr/content/file-view-preview.js.map +1 -1
- package/dist/ssr/content/index.js +3 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/markdown-links.js +178 -0
- package/dist/ssr/content/markdown-links.js.map +1 -0
- package/dist/ssr/content/timeline-model.js +579 -0
- package/dist/ssr/content/timeline-model.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +26 -14
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/inputs/ChoiceChips.js +66 -0
- package/dist/ssr/inputs/ChoiceChips.js.map +1 -0
- package/dist/ssr/inputs/ChoiceGroups.js +12 -4
- package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
- package/dist/ssr/inputs/FileDropTarget.js +103 -0
- package/dist/ssr/inputs/FileDropTarget.js.map +1 -0
- package/dist/ssr/inputs/FileInputs.js +49 -12
- package/dist/ssr/inputs/FileInputs.js.map +1 -1
- package/dist/ssr/inputs/SignatureInput.js +404 -0
- package/dist/ssr/inputs/SignatureInput.js.map +1 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js +551 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-data.js +1903 -0
- package/dist/ssr/inputs/emoji/emoji-data.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-index.js +209 -0
- package/dist/ssr/inputs/emoji/emoji-index.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-messages.js +64 -0
- package/dist/ssr/inputs/emoji/emoji-messages.js.map +1 -0
- package/dist/ssr/inputs/file-drop.js +338 -0
- package/dist/ssr/inputs/file-drop.js.map +1 -0
- package/dist/ssr/inputs/index.js +6 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/inputs/markdown/MarkdownEditor.js +14 -2
- package/dist/ssr/inputs/markdown/MarkdownEditor.js.map +1 -1
- package/dist/ssr/inputs/markdown/actions.js +48 -2
- package/dist/ssr/inputs/markdown/actions.js.map +1 -1
- package/dist/ssr/inputs/markdown/code-zone.js +38 -2
- package/dist/ssr/inputs/markdown/code-zone.js.map +1 -1
- package/dist/ssr/inputs/signature-messages.js +39 -0
- package/dist/ssr/inputs/signature-messages.js.map +1 -0
- package/dist/ssr/inputs/signature.js +208 -0
- package/dist/ssr/inputs/signature.js.map +1 -0
- package/dist/ssr/internal/render-error.js +49 -0
- package/dist/ssr/internal/render-error.js.map +1 -0
- package/dist/ssr/intl/messages.js +205 -4
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +264 -47
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/FloatingWindow.js +4 -1
- package/dist/ssr/layout/FloatingWindow.js.map +1 -1
- package/dist/ssr/layout/Navigation.js +169 -122
- package/dist/ssr/layout/Navigation.js.map +1 -1
- package/dist/ssr/layout/SidebarItemPreview.js +5 -1
- package/dist/ssr/layout/SidebarItemPreview.js.map +1 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/layout/navigation-model.js +2 -0
- package/dist/ssr/layout/navigation-model.js.map +1 -1
- package/dist/ssr/surfaces/NoticeCard.js +17 -12
- package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
- package/dist/ssr/surfaces/ResourceCard.js +115 -0
- package/dist/ssr/surfaces/ResourceCard.js.map +1 -0
- package/dist/ssr/surfaces/index.js +1 -0
- package/dist/ssr/surfaces/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/Dropdown.d.ts +13 -1
- package/dist/types/actions/FilterChip.d.ts +5 -0
- package/dist/types/chat/ChatComposer.d.ts +24 -1
- package/dist/types/chat/ChatPrimitives.d.ts +1 -1
- package/dist/types/chat/chat-behavior.d.ts +15 -0
- package/dist/types/chat/index.d.ts +1 -1
- package/dist/types/chat/types.d.ts +34 -0
- package/dist/types/content/Calendar.d.ts +28 -8
- package/dist/types/content/Lightbox.d.ts +8 -0
- package/dist/types/content/Timeline.d.ts +49 -0
- package/dist/types/content/VideoPlayer.d.ts +41 -0
- package/dist/types/content/index.d.ts +6 -2
- package/dist/types/content/markdown-links.d.ts +66 -0
- package/dist/types/content/timeline-model.d.ts +148 -0
- package/dist/types/feedback/dialog-core.d.ts +1 -1
- package/dist/types/inputs/ChoiceChips.d.ts +20 -0
- package/dist/types/inputs/ChoiceGroups.d.ts +11 -2
- package/dist/types/inputs/FileDropTarget.d.ts +23 -0
- package/dist/types/inputs/FileInputs.d.ts +8 -0
- package/dist/types/inputs/SignatureInput.d.ts +22 -0
- package/dist/types/inputs/emoji/EmojiPicker.d.ts +42 -0
- package/dist/types/inputs/emoji/emoji-data.d.ts +2 -0
- package/dist/types/inputs/emoji/emoji-index.d.ts +109 -0
- package/dist/types/inputs/emoji/emoji-messages.d.ts +30 -0
- package/dist/types/inputs/file-drop.d.ts +80 -0
- package/dist/types/inputs/index.d.ts +12 -0
- package/dist/types/inputs/markdown/actions.d.ts +4 -0
- package/dist/types/inputs/markdown/code-zone.d.ts +7 -0
- package/dist/types/inputs/signature-messages.d.ts +11 -0
- package/dist/types/inputs/signature.d.ts +72 -0
- package/dist/types/internal/render-error.d.ts +13 -0
- package/dist/types/intl/messages.d.ts +101 -0
- package/dist/types/layout/AppWorkspace.d.ts +38 -0
- package/dist/types/layout/SidebarItemPreview.d.ts +2 -0
- package/dist/types/layout/index.d.ts +2 -2
- package/dist/types/layout/navigation-model.d.ts +18 -0
- package/dist/types/surfaces/ResourceCard.d.ts +31 -0
- package/dist/types/surfaces/index.d.ts +2 -0
- package/package.json +5 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { replaceTextareaRange as replaceRange } from "../editor-dom";
|
|
2
|
+
import { closesCodeFence, openCodeFence } from "./code-zone";
|
|
2
3
|
const lineAt = (value, position) => {
|
|
3
4
|
const lineStart = value.lastIndexOf("\n", position - 1) + 1;
|
|
4
5
|
const nextNewline = value.indexOf("\n", position);
|
|
@@ -31,15 +32,17 @@ const toggleInlineWrap = (textarea, marker, placeholder) => {
|
|
|
31
32
|
selectionEnd
|
|
32
33
|
} = textarea;
|
|
33
34
|
const markerLength = marker.length;
|
|
35
|
+
// A one-character marker never takes half of a doubled one, so italic `*` leaves bold `**` alone.
|
|
36
|
+
const doubled = (text, from, step) => markerLength === 1 && text[from] === marker && text[from + step] === marker && text[from + 2 * step] !== marker;
|
|
34
37
|
const before = value.slice(Math.max(0, selectionStart - markerLength), selectionStart);
|
|
35
38
|
const after = value.slice(selectionEnd, selectionEnd + markerLength);
|
|
36
|
-
if (before === marker && after === marker) {
|
|
39
|
+
if (before === marker && after === marker && !doubled(value, selectionStart - 1, -1) && !doubled(value, selectionEnd, 1)) {
|
|
37
40
|
replaceRange(textarea, selectionStart - markerLength, selectionEnd + markerLength, value.slice(selectionStart, selectionEnd));
|
|
38
41
|
textarea.setSelectionRange(selectionStart - markerLength, selectionEnd - markerLength);
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
44
|
const selected = value.slice(selectionStart, selectionEnd);
|
|
42
|
-
if (selected.length >= markerLength * 2 && selected.startsWith(marker) && selected.endsWith(marker)) {
|
|
45
|
+
if (selected.length >= markerLength * 2 && selected.startsWith(marker) && selected.endsWith(marker) && !doubled(selected, 0, 1) && !doubled(selected, selected.length - 1, -1)) {
|
|
43
46
|
const stripped = selected.slice(markerLength, -markerLength);
|
|
44
47
|
replaceRange(textarea, selectionStart, selectionEnd, stripped);
|
|
45
48
|
textarea.setSelectionRange(selectionStart, selectionStart + stripped.length);
|
|
@@ -56,6 +59,8 @@ const toggleInlineWrap = (textarea, marker, placeholder) => {
|
|
|
56
59
|
export const toggleBold = textarea => toggleInlineWrap(textarea, "**", "bold text");
|
|
57
60
|
export const toggleItalic = textarea => toggleInlineWrap(textarea, "*", "italic text");
|
|
58
61
|
export const toggleCode = textarea => toggleInlineWrap(textarea, "`", "code");
|
|
62
|
+
/** Wraps the selection in `marker`, or unwraps it; without a selection the caret lands between two markers. */
|
|
63
|
+
export const toggleInlineMarker = (textarea, marker) => toggleInlineWrap(textarea, marker, "");
|
|
59
64
|
export const insertLink = (textarea, url) => {
|
|
60
65
|
const {
|
|
61
66
|
value,
|
|
@@ -112,6 +117,47 @@ export const toggleHeading = (textarea, level) => {
|
|
|
112
117
|
textarea.setSelectionRange(caret, caret);
|
|
113
118
|
};
|
|
114
119
|
export const toggleBulletList = textarea => togglePrefix(textarea, "- ");
|
|
120
|
+
|
|
121
|
+
/** Puts the selected lines between ``` fences. Inside a fenced block, from its opening line on, it removes that block's fences. */
|
|
122
|
+
export const toggleCodeBlock = textarea => {
|
|
123
|
+
const {
|
|
124
|
+
value
|
|
125
|
+
} = textarea;
|
|
126
|
+
const {
|
|
127
|
+
start,
|
|
128
|
+
end,
|
|
129
|
+
lines
|
|
130
|
+
} = selectedLineRange(textarea);
|
|
131
|
+
const open = openCodeFence(value, lineAt(value, start).lineEnd);
|
|
132
|
+
if (open) {
|
|
133
|
+
const bodyStart = Math.min(lineAt(value, open.start).lineEnd + 1, value.length);
|
|
134
|
+
// An unclosed block runs to the end of the text.
|
|
135
|
+
let bodyEnd = value.length;
|
|
136
|
+
let blockEnd = value.length;
|
|
137
|
+
for (let lineStart = bodyStart; lineStart < value.length;) {
|
|
138
|
+
const {
|
|
139
|
+
lineEnd,
|
|
140
|
+
line
|
|
141
|
+
} = lineAt(value, lineStart);
|
|
142
|
+
if (closesCodeFence(line, open.marker)) {
|
|
143
|
+
bodyEnd = Math.max(bodyStart, lineStart - 1);
|
|
144
|
+
blockEnd = lineEnd;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
lineStart = lineEnd + 1;
|
|
148
|
+
}
|
|
149
|
+
const body = value.slice(bodyStart, bodyEnd);
|
|
150
|
+
replaceRange(textarea, open.start, blockEnd, body);
|
|
151
|
+
textarea.setSelectionRange(open.start, open.start + body.length);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const body = lines.join("\n");
|
|
155
|
+
// A fence in the selection would close a ``` block early, so the new block's fence is longer than any of them.
|
|
156
|
+
const longest = Math.max(0, ...lines.map(line => /^(?:[ \t]*>)*[ \t]*(`{3,})/.exec(line)?.[1].length ?? 0));
|
|
157
|
+
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
158
|
+
replaceRange(textarea, start, end, `${fence}\n${body}\n${fence}`);
|
|
159
|
+
textarea.setSelectionRange(start + fence.length + 1, start + fence.length + 1 + body.length);
|
|
160
|
+
};
|
|
115
161
|
export const toggleQuote = textarea => togglePrefix(textarea, "> ");
|
|
116
162
|
export const toggleNumberedList = textarea => {
|
|
117
163
|
const {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["inputs/markdown/actions.ts"],"sourcesContent":["import { replaceTextareaRange as replaceRange } from \"../editor-dom\";\n\nconst lineAt = (value: string, position: number): { lineStart: number; lineEnd: number; line: string } => {\n const lineStart = value.lastIndexOf(\"\\n\", position - 1) + 1;\n const nextNewline = value.indexOf(\"\\n\", position);\n const lineEnd = nextNewline === -1 ? value.length : nextNewline;\n return { lineStart, lineEnd, line: value.slice(lineStart, lineEnd) };\n};\n\nconst selectedLineRange = (textarea: HTMLTextAreaElement): { start: number; end: number; lines: string[] } => {\n const { value, selectionStart, selectionEnd } = textarea;\n const startLine = lineAt(value, selectionStart);\n const adjustedEnd =\n selectionEnd > selectionStart && selectionEnd > 0 && value[selectionEnd - 1] === \"\\n\" ? selectionEnd - 1 : selectionEnd;\n const endLine = lineAt(value, adjustedEnd);\n return {\n start: startLine.lineStart,\n end: endLine.lineEnd,\n lines: value.slice(startLine.lineStart, endLine.lineEnd).split(\"\\n\"),\n };\n};\n\nconst toggleInlineWrap = (textarea: HTMLTextAreaElement, marker: string, placeholder: string): void => {\n const { value, selectionStart, selectionEnd } = textarea;\n const markerLength = marker.length;\n const before = value.slice(Math.max(0, selectionStart - markerLength), selectionStart);\n const after = value.slice(selectionEnd, selectionEnd + markerLength);\n if (before === marker && after === marker) {\n replaceRange(textarea, selectionStart - markerLength, selectionEnd + markerLength, value.slice(selectionStart, selectionEnd));\n textarea.setSelectionRange(selectionStart - markerLength, selectionEnd - markerLength);\n return;\n }\n\n const selected = value.slice(selectionStart, selectionEnd);\n if (selected.length >= markerLength * 2 && selected.startsWith(marker) && selected.endsWith(marker)) {\n const stripped = selected.slice(markerLength, -markerLength);\n replaceRange(textarea, selectionStart, selectionEnd, stripped);\n textarea.setSelectionRange(selectionStart, selectionStart + stripped.length);\n return;\n }\n\n if (selectionStart === selectionEnd) {\n replaceRange(textarea, selectionStart, selectionEnd, marker + placeholder + marker);\n textarea.setSelectionRange(selectionStart + markerLength, selectionStart + markerLength + placeholder.length);\n } else {\n replaceRange(textarea, selectionStart, selectionEnd, marker + selected + marker);\n textarea.setSelectionRange(selectionStart + markerLength, selectionStart + markerLength + selected.length);\n }\n};\n\nexport const toggleBold = (textarea: HTMLTextAreaElement): void => toggleInlineWrap(textarea, \"**\", \"bold text\");\nexport const toggleItalic = (textarea: HTMLTextAreaElement): void => toggleInlineWrap(textarea, \"*\", \"italic text\");\nexport const toggleCode = (textarea: HTMLTextAreaElement): void => toggleInlineWrap(textarea, \"`\", \"code\");\n\nexport const insertLink = (textarea: HTMLTextAreaElement, url?: string): void => {\n const { value, selectionStart, selectionEnd } = textarea;\n const selected = value.slice(selectionStart, selectionEnd);\n const label = selected || \"link\";\n const finalUrl = url ?? \"\";\n replaceRange(textarea, selectionStart, selectionEnd, `[${label}](${finalUrl})`);\n if (finalUrl) {\n textarea.setSelectionRange(selectionStart + 1, selectionStart + 1 + label.length);\n } else {\n const caret = selectionStart + label.length + 3;\n textarea.setSelectionRange(caret, caret);\n }\n};\n\nconst togglePrefix = (textarea: HTMLTextAreaElement, prefix: string): void => {\n const { start, end, lines } = selectedLineRange(textarea);\n const parts = lines.map((line) => {\n const match = /^(\\s*)(.*)$/.exec(line)!;\n return { indent: match[1]!, body: match[2]! };\n });\n const remove = parts.every(({ body }) => body.startsWith(prefix));\n const replacement = parts.map(({ indent, body }) => (remove ? indent + body.slice(prefix.length) : indent + prefix + body)).join(\"\\n\");\n replaceRange(textarea, start, end, replacement);\n textarea.setSelectionRange(start, start + replacement.length);\n};\n\nexport const toggleHeading = (textarea: HTMLTextAreaElement, level: 1 | 2 | 3): void => {\n const oldCaret = textarea.selectionStart;\n const { lineStart, lineEnd, line } = lineAt(textarea.value, oldCaret);\n const prefix = `${\"#\".repeat(level)} `;\n const existing = /^(#{1,6})\\s/.exec(line);\n const next =\n existing?.[1]?.length === level ? line.slice(existing[0].length) : existing ? prefix + line.slice(existing[0].length) : prefix + line;\n replaceRange(textarea, lineStart, lineEnd, next);\n const desired = Math.max(lineStart, oldCaret + next.length - line.length);\n const caret = Math.min(desired, lineStart + next.length);\n textarea.setSelectionRange(caret, caret);\n};\n\nexport const toggleBulletList = (textarea: HTMLTextAreaElement): void => togglePrefix(textarea, \"- \");\nexport const toggleQuote = (textarea: HTMLTextAreaElement): void => togglePrefix(textarea, \"> \");\n\nexport const toggleNumberedList = (textarea: HTMLTextAreaElement): void => {\n const { start, end, lines } = selectedLineRange(textarea);\n const parts = lines.map((line) => {\n const match = /^(\\s*)(\\d+\\.\\s)?(.*)$/.exec(line)!;\n return {\n indent: match[1] ?? \"\",\n marker: match[2],\n body: match[3] ?? \"\",\n };\n });\n const remove = parts.every(({ marker }) => Boolean(marker));\n const replacement = parts.map(({ indent, body }, index) => (remove ? indent + body : `${indent}${index + 1}. ${body}`)).join(\"\\n\");\n replaceRange(textarea, start, end, replacement);\n textarea.setSelectionRange(start, start + replacement.length);\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/markdown/actions.ts"],"sourcesContent":["import { replaceTextareaRange as replaceRange } from \"../editor-dom\";\nimport { closesCodeFence, openCodeFence } from \"./code-zone\";\n\nconst lineAt = (value: string, position: number): { lineStart: number; lineEnd: number; line: string } => {\n const lineStart = value.lastIndexOf(\"\\n\", position - 1) + 1;\n const nextNewline = value.indexOf(\"\\n\", position);\n const lineEnd = nextNewline === -1 ? value.length : nextNewline;\n return { lineStart, lineEnd, line: value.slice(lineStart, lineEnd) };\n};\n\nconst selectedLineRange = (textarea: HTMLTextAreaElement): { start: number; end: number; lines: string[] } => {\n const { value, selectionStart, selectionEnd } = textarea;\n const startLine = lineAt(value, selectionStart);\n const adjustedEnd =\n selectionEnd > selectionStart && selectionEnd > 0 && value[selectionEnd - 1] === \"\\n\" ? selectionEnd - 1 : selectionEnd;\n const endLine = lineAt(value, adjustedEnd);\n return {\n start: startLine.lineStart,\n end: endLine.lineEnd,\n lines: value.slice(startLine.lineStart, endLine.lineEnd).split(\"\\n\"),\n };\n};\n\nconst toggleInlineWrap = (textarea: HTMLTextAreaElement, marker: string, placeholder: string): void => {\n const { value, selectionStart, selectionEnd } = textarea;\n const markerLength = marker.length;\n // A one-character marker never takes half of a doubled one, so italic `*` leaves bold `**` alone.\n const doubled = (text: string, from: number, step: 1 | -1) =>\n markerLength === 1 && text[from] === marker && text[from + step] === marker && text[from + 2 * step] !== marker;\n const before = value.slice(Math.max(0, selectionStart - markerLength), selectionStart);\n const after = value.slice(selectionEnd, selectionEnd + markerLength);\n if (before === marker && after === marker && !doubled(value, selectionStart - 1, -1) && !doubled(value, selectionEnd, 1)) {\n replaceRange(textarea, selectionStart - markerLength, selectionEnd + markerLength, value.slice(selectionStart, selectionEnd));\n textarea.setSelectionRange(selectionStart - markerLength, selectionEnd - markerLength);\n return;\n }\n\n const selected = value.slice(selectionStart, selectionEnd);\n if (\n selected.length >= markerLength * 2 &&\n selected.startsWith(marker) &&\n selected.endsWith(marker) &&\n !doubled(selected, 0, 1) &&\n !doubled(selected, selected.length - 1, -1)\n ) {\n const stripped = selected.slice(markerLength, -markerLength);\n replaceRange(textarea, selectionStart, selectionEnd, stripped);\n textarea.setSelectionRange(selectionStart, selectionStart + stripped.length);\n return;\n }\n\n if (selectionStart === selectionEnd) {\n replaceRange(textarea, selectionStart, selectionEnd, marker + placeholder + marker);\n textarea.setSelectionRange(selectionStart + markerLength, selectionStart + markerLength + placeholder.length);\n } else {\n replaceRange(textarea, selectionStart, selectionEnd, marker + selected + marker);\n textarea.setSelectionRange(selectionStart + markerLength, selectionStart + markerLength + selected.length);\n }\n};\n\nexport const toggleBold = (textarea: HTMLTextAreaElement): void => toggleInlineWrap(textarea, \"**\", \"bold text\");\nexport const toggleItalic = (textarea: HTMLTextAreaElement): void => toggleInlineWrap(textarea, \"*\", \"italic text\");\nexport const toggleCode = (textarea: HTMLTextAreaElement): void => toggleInlineWrap(textarea, \"`\", \"code\");\n/** Wraps the selection in `marker`, or unwraps it; without a selection the caret lands between two markers. */\nexport const toggleInlineMarker = (textarea: HTMLTextAreaElement, marker: string): void => toggleInlineWrap(textarea, marker, \"\");\n\nexport const insertLink = (textarea: HTMLTextAreaElement, url?: string): void => {\n const { value, selectionStart, selectionEnd } = textarea;\n const selected = value.slice(selectionStart, selectionEnd);\n const label = selected || \"link\";\n const finalUrl = url ?? \"\";\n replaceRange(textarea, selectionStart, selectionEnd, `[${label}](${finalUrl})`);\n if (finalUrl) {\n textarea.setSelectionRange(selectionStart + 1, selectionStart + 1 + label.length);\n } else {\n const caret = selectionStart + label.length + 3;\n textarea.setSelectionRange(caret, caret);\n }\n};\n\nconst togglePrefix = (textarea: HTMLTextAreaElement, prefix: string): void => {\n const { start, end, lines } = selectedLineRange(textarea);\n const parts = lines.map((line) => {\n const match = /^(\\s*)(.*)$/.exec(line)!;\n return { indent: match[1]!, body: match[2]! };\n });\n const remove = parts.every(({ body }) => body.startsWith(prefix));\n const replacement = parts.map(({ indent, body }) => (remove ? indent + body.slice(prefix.length) : indent + prefix + body)).join(\"\\n\");\n replaceRange(textarea, start, end, replacement);\n textarea.setSelectionRange(start, start + replacement.length);\n};\n\nexport const toggleHeading = (textarea: HTMLTextAreaElement, level: 1 | 2 | 3): void => {\n const oldCaret = textarea.selectionStart;\n const { lineStart, lineEnd, line } = lineAt(textarea.value, oldCaret);\n const prefix = `${\"#\".repeat(level)} `;\n const existing = /^(#{1,6})\\s/.exec(line);\n const next =\n existing?.[1]?.length === level ? line.slice(existing[0].length) : existing ? prefix + line.slice(existing[0].length) : prefix + line;\n replaceRange(textarea, lineStart, lineEnd, next);\n const desired = Math.max(lineStart, oldCaret + next.length - line.length);\n const caret = Math.min(desired, lineStart + next.length);\n textarea.setSelectionRange(caret, caret);\n};\n\nexport const toggleBulletList = (textarea: HTMLTextAreaElement): void => togglePrefix(textarea, \"- \");\n\n/** Puts the selected lines between ``` fences. Inside a fenced block, from its opening line on, it removes that block's fences. */\nexport const toggleCodeBlock = (textarea: HTMLTextAreaElement): void => {\n const { value } = textarea;\n const { start, end, lines } = selectedLineRange(textarea);\n const open = openCodeFence(value, lineAt(value, start).lineEnd);\n if (open) {\n const bodyStart = Math.min(lineAt(value, open.start).lineEnd + 1, value.length);\n // An unclosed block runs to the end of the text.\n let bodyEnd = value.length;\n let blockEnd = value.length;\n for (let lineStart = bodyStart; lineStart < value.length; ) {\n const { lineEnd, line } = lineAt(value, lineStart);\n if (closesCodeFence(line, open.marker)) {\n bodyEnd = Math.max(bodyStart, lineStart - 1);\n blockEnd = lineEnd;\n break;\n }\n lineStart = lineEnd + 1;\n }\n const body = value.slice(bodyStart, bodyEnd);\n replaceRange(textarea, open.start, blockEnd, body);\n textarea.setSelectionRange(open.start, open.start + body.length);\n return;\n }\n const body = lines.join(\"\\n\");\n // A fence in the selection would close a ``` block early, so the new block's fence is longer than any of them.\n const longest = Math.max(0, ...lines.map((line) => /^(?:[ \\t]*>)*[ \\t]*(`{3,})/.exec(line)?.[1]!.length ?? 0));\n const fence = \"`\".repeat(Math.max(3, longest + 1));\n replaceRange(textarea, start, end, `${fence}\\n${body}\\n${fence}`);\n textarea.setSelectionRange(start + fence.length + 1, start + fence.length + 1 + body.length);\n};\n\nexport const toggleQuote = (textarea: HTMLTextAreaElement): void => togglePrefix(textarea, \"> \");\n\nexport const toggleNumberedList = (textarea: HTMLTextAreaElement): void => {\n const { start, end, lines } = selectedLineRange(textarea);\n const parts = lines.map((line) => {\n const match = /^(\\s*)(\\d+\\.\\s)?(.*)$/.exec(line)!;\n return {\n indent: match[1] ?? \"\",\n marker: match[2],\n body: match[3] ?? \"\",\n };\n });\n const remove = parts.every(({ marker }) => Boolean(marker));\n const replacement = parts.map(({ indent, body }, index) => (remove ? indent + body : `${indent}${index + 1}. ${body}`)).join(\"\\n\");\n replaceRange(textarea, start, end, replacement);\n textarea.setSelectionRange(start, start + replacement.length);\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,7 +1,43 @@
|
|
|
1
|
+
/** A fence line: optional quote markers and indentation, then three or more backticks or tildes, then the rest. */
|
|
2
|
+
const fenceLine = /^(?:[ \t]*>)*[ \t]*(`{3,}|~{3,})(.*)$/;
|
|
3
|
+
|
|
4
|
+
/** Whether `line` closes a block that `marker` opened: the same character, at least as many, nothing after it. */
|
|
5
|
+
export const closesCodeFence = (line, marker) => {
|
|
6
|
+
const fence = fenceLine.exec(line);
|
|
7
|
+
return Boolean(fence && fence[1][0] === marker[0] && fence[1].length >= marker.length && !fence[2].trim());
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** How many quotes `line` is in: the `>` markers before its content. */
|
|
11
|
+
const quoteDepth = line => (/^(?:[ \t]*>)*/.exec(line)[0].match(/>/g) ?? []).length;
|
|
12
|
+
|
|
13
|
+
/** The fenced code block that is still open at `position`, judged by the text before it: its opening line's start and marker. */
|
|
14
|
+
export const openCodeFence = (text, position) => {
|
|
15
|
+
let open = null;
|
|
16
|
+
let start = 0;
|
|
17
|
+
for (const line of text.slice(0, position).split("\n")) {
|
|
18
|
+
// A block inside a quote ends with that quote, for example at a blank line or an unquoted one.
|
|
19
|
+
if (open && quoteDepth(line) < open.depth) open = null;
|
|
20
|
+
if (open) {
|
|
21
|
+
if (closesCodeFence(line, open.marker)) open = null;
|
|
22
|
+
} else {
|
|
23
|
+
const fence = fenceLine.exec(line);
|
|
24
|
+
// A backtick in the info string makes the line inline code instead of a fence.
|
|
25
|
+
if (fence && !(fence[1][0] === "`" && fence[2].includes("`"))) open = {
|
|
26
|
+
start,
|
|
27
|
+
marker: fence[1],
|
|
28
|
+
depth: quoteDepth(line)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
start += line.length + 1;
|
|
32
|
+
}
|
|
33
|
+
return open && {
|
|
34
|
+
start: open.start,
|
|
35
|
+
marker: open.marker
|
|
36
|
+
};
|
|
37
|
+
};
|
|
1
38
|
export const isInCodeZone = (text, position) => {
|
|
39
|
+
if (openCodeFence(text, position)) return true;
|
|
2
40
|
const before = text.slice(0, position);
|
|
3
|
-
const fences = before.match(/^```/gm);
|
|
4
|
-
if (fences && fences.length % 2 !== 0) return true;
|
|
5
41
|
const line = before.slice(before.lastIndexOf("\n") + 1);
|
|
6
42
|
return (line.match(/`/g) ?? []).length % 2 !== 0;
|
|
7
43
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["inputs/markdown/code-zone.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/markdown/code-zone.ts"],"sourcesContent":["/** A fence line: optional quote markers and indentation, then three or more backticks or tildes, then the rest. */\nconst fenceLine = /^(?:[ \\t]*>)*[ \\t]*(`{3,}|~{3,})(.*)$/;\n\n/** Whether `line` closes a block that `marker` opened: the same character, at least as many, nothing after it. */\nexport const closesCodeFence = (line: string, marker: string): boolean => {\n const fence = fenceLine.exec(line);\n return Boolean(fence && fence[1]![0] === marker[0] && fence[1]!.length >= marker.length && !fence[2]!.trim());\n};\n\n/** How many quotes `line` is in: the `>` markers before its content. */\nconst quoteDepth = (line: string): number => (/^(?:[ \\t]*>)*/.exec(line)![0].match(/>/g) ?? []).length;\n\n/** The fenced code block that is still open at `position`, judged by the text before it: its opening line's start and marker. */\nexport const openCodeFence = (text: string, position: number): { start: number; marker: string } | null => {\n let open: { start: number; marker: string; depth: number } | null = null;\n let start = 0;\n for (const line of text.slice(0, position).split(\"\\n\")) {\n // A block inside a quote ends with that quote, for example at a blank line or an unquoted one.\n if (open && quoteDepth(line) < open.depth) open = null;\n if (open) {\n if (closesCodeFence(line, open.marker)) open = null;\n } else {\n const fence = fenceLine.exec(line);\n // A backtick in the info string makes the line inline code instead of a fence.\n if (fence && !(fence[1]![0] === \"`\" && fence[2]!.includes(\"`\"))) open = { start, marker: fence[1]!, depth: quoteDepth(line) };\n }\n start += line.length + 1;\n }\n return open && { start: open.start, marker: open.marker };\n};\n\nexport const isInCodeZone = (text: string, position: number): boolean => {\n if (openCodeFence(text, position)) return true;\n const before = text.slice(0, position);\n const line = before.slice(before.lastIndexOf(\"\\n\") + 1);\n return (line.match(/`/g) ?? []).length % 2 !== 0;\n};\n"],"mappings":"AAAA;AACA,MAAM,SAAS,GAAG,uCAAuC;;AAEzD;AACA,OAAO,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,MAAc,KAAc;EACxE,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;EAClC,OAAO,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAE,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,CAAC,CAAC;AAC/G,CAAC;;AAED;AACA,MAAM,UAAU,GAAI,IAAY,IAAa,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM;;AAEtG;AACA,OAAO,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,QAAgB,KAA+C;EACzG,IAAI,IAA6D,GAAG,IAAI;EACxE,IAAI,KAAK,GAAG,CAAC;EACb,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;IACtD;IACA,IAAI,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IACtD,IAAI,IAAI,EAAE;MACR,IAAI,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI;IACrD,CAAC,MAAM;MACL,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;MAClC;MACA,IAAI,KAAK,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG;QAAE,KAAK;QAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAE;QAAE,KAAK,EAAE,UAAU,CAAC,IAAI;MAAE,CAAC;IAC/H;IACA,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;EAC1B;EACA,OAAO,IAAI,IAAI;IAAE,KAAK,EAAE,IAAI,CAAC,KAAK;IAAE,MAAM,EAAE,IAAI,CAAC;EAAO,CAAC;AAC3D,CAAC;AAED,OAAO,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,QAAgB,KAAc;EACvE,IAAI,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,OAAO,IAAI;EAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;EACtC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;EACvD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC,KAAK,CAAC;AAClD,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { i18n } from "@k2b/stdlib";
|
|
2
|
+
import { useLocale } from "../intl/locale";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SignatureInput's own strings. They live beside the component instead of in
|
|
6
|
+
* the shared catalog, so applications that never render a signature field do
|
|
7
|
+
* not ship them.
|
|
8
|
+
*/
|
|
9
|
+
const signatureMessages = i18n.define({
|
|
10
|
+
baseLocale: "en",
|
|
11
|
+
messages: {
|
|
12
|
+
en: {
|
|
13
|
+
method: "Signature method",
|
|
14
|
+
draw: "Draw",
|
|
15
|
+
type: "Type",
|
|
16
|
+
signHere: "Sign here",
|
|
17
|
+
drawArea: "Signature area. Draw with a finger, pen, or mouse.",
|
|
18
|
+
drawn: "Drawn signature",
|
|
19
|
+
typeName: "Type your name",
|
|
20
|
+
undo: "Undo"
|
|
21
|
+
},
|
|
22
|
+
de: {
|
|
23
|
+
method: "Art der Unterschrift",
|
|
24
|
+
draw: "Zeichnen",
|
|
25
|
+
type: "Tippen",
|
|
26
|
+
signHere: "Hier unterschreiben",
|
|
27
|
+
drawArea: "Unterschriftsfeld. Zeichne mit Finger, Stift oder Maus.",
|
|
28
|
+
drawn: "Gezeichnete Unterschrift",
|
|
29
|
+
typeName: "Namen eintippen",
|
|
30
|
+
undo: "Rückgängig"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
export const useSignatureMessages = () => {
|
|
35
|
+
const locale = useLocale();
|
|
36
|
+
return () => signatureMessages.resolve([locale()]).t;
|
|
37
|
+
};
|
|
38
|
+
export const checkSignatureMessages = () => signatureMessages.check();
|
|
39
|
+
//# sourceMappingURL=signature-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/signature-messages.ts"],"sourcesContent":["import { i18n } from \"@k2b/stdlib\";\nimport { useLocale } from \"../intl/locale\";\n\n/**\n * SignatureInput's own strings. They live beside the component instead of in\n * the shared catalog, so applications that never render a signature field do\n * not ship them.\n */\nconst signatureMessages = i18n.define({\n baseLocale: \"en\",\n messages: {\n en: {\n method: \"Signature method\",\n draw: \"Draw\",\n type: \"Type\",\n signHere: \"Sign here\",\n drawArea: \"Signature area. Draw with a finger, pen, or mouse.\",\n drawn: \"Drawn signature\",\n typeName: \"Type your name\",\n undo: \"Undo\",\n },\n de: {\n method: \"Art der Unterschrift\",\n draw: \"Zeichnen\",\n type: \"Tippen\",\n signHere: \"Hier unterschreiben\",\n drawArea: \"Unterschriftsfeld. Zeichne mit Finger, Stift oder Maus.\",\n drawn: \"Gezeichnete Unterschrift\",\n typeName: \"Namen eintippen\",\n undo: \"Rückgängig\",\n },\n },\n});\n\nexport const useSignatureMessages = () => {\n const locale = useLocale();\n return () => signatureMessages.resolve([locale()]).t;\n};\n\nexport const checkSignatureMessages = () => signatureMessages.check();\n"],"mappings":"AAAA,SAAS,IAAI,QAAQ,aAAa;AAClC,SAAS,SAAS,QAAQ,gBAAgB;;AAE1C;AACA;AACA;AACA;AACA;AACA,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC;EACpC,UAAU,EAAE,IAAI;EAChB,QAAQ,EAAE;IACR,EAAE,EAAE;MACF,MAAM,EAAE,kBAAkB;MAC1B,IAAI,EAAE,MAAM;MACZ,IAAI,EAAE,MAAM;MACZ,QAAQ,EAAE,WAAW;MACrB,QAAQ,EAAE,oDAAoD;MAC9D,KAAK,EAAE,iBAAiB;MACxB,QAAQ,EAAE,gBAAgB;MAC1B,IAAI,EAAE;IACR,CAAC;IACD,EAAE,EAAE;MACF,MAAM,EAAE,sBAAsB;MAC9B,IAAI,EAAE,UAAU;MAChB,IAAI,EAAE,QAAQ;MACd,QAAQ,EAAE,qBAAqB;MAC/B,QAAQ,EAAE,yDAAyD;MACnE,KAAK,EAAE,0BAA0B;MACjC,QAAQ,EAAE,iBAAiB;MAC3B,IAAI,EAAE;IACR;EACF;AACF,CAAC,CAAC;AAEF,OAAO,MAAM,oBAAoB,GAAG,MAAM;EACxC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;EAC1B,OAAO,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,OAAO,MAAM,sBAAsB,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signature as the field reports it. `kind` records how it was given, so the
|
|
3
|
+
* application can keep that fact next to the image. `svg` is a standalone SVG
|
|
4
|
+
* document whose ink uses `currentColor`, so it renders black as an image. A
|
|
5
|
+
* value that arrives from a browser is untrusted input: read it with
|
|
6
|
+
* `parseSignature` before storing or embedding it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** One sampled pointer position in drawing units, with a pressure from 0 to 1. */
|
|
10
|
+
|
|
11
|
+
/** The drawing space as an SVG viewBox and one closed outline path per stroke. */
|
|
12
|
+
|
|
13
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
14
|
+
/** Typed names are laid out at this size; the SVG scales as a whole. */
|
|
15
|
+
const TYPED_FONT_SIZE = 48;
|
|
16
|
+
const TYPED_PADDING = 12;
|
|
17
|
+
const round = value => Math.round(value * 10) / 10;
|
|
18
|
+
const point = (x, y) => `${round(x)} ${round(y)}`;
|
|
19
|
+
|
|
20
|
+
/** Control characters, lone surrogates, and the two noncharacters XML forbids, which a strict XML parser rejects. */
|
|
21
|
+
const NOT_XML_TEXT = /[\p{Cc}\p{Cs}\uFFFE\uFFFF]/gu;
|
|
22
|
+
|
|
23
|
+
/** Escapes text for XML content or attributes and drops characters XML cannot hold, so the document stays well-formed. */
|
|
24
|
+
export const escapeXml = text => text.replace(NOT_XML_TEXT, "").replace(/[&<>"']/g, char => ({
|
|
25
|
+
"&": "&",
|
|
26
|
+
"<": "<",
|
|
27
|
+
">": ">",
|
|
28
|
+
'"': """,
|
|
29
|
+
"'": "'"
|
|
30
|
+
})[char] ?? char);
|
|
31
|
+
const unescapeXml = text => text.replace(/&(amp|lt|gt|quot|apos);/g, (_, name) => ({
|
|
32
|
+
amp: "&",
|
|
33
|
+
lt: "<",
|
|
34
|
+
gt: ">",
|
|
35
|
+
quot: '"',
|
|
36
|
+
apos: "'"
|
|
37
|
+
})[name] ?? "");
|
|
38
|
+
|
|
39
|
+
/** The name as a typed signature stores it: without characters an SVG cannot hold and without surrounding space. */
|
|
40
|
+
export const signatureName = name => name.replace(NOT_XML_TEXT, "").trim();
|
|
41
|
+
|
|
42
|
+
/** Stroke width in drawing units at a pressure: light pressure still leaves a readable line. */
|
|
43
|
+
export const strokeWidth = (size, pressure) => size * (0.35 + 0.65 * Math.min(1, Math.max(0, pressure)));
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Turns sampled points into one filled outline: both edges follow the points at
|
|
47
|
+
* half the local stroke width and are smoothed through their midpoints, and
|
|
48
|
+
* round caps close the ends. A filled outline, unlike a stroked path, can change
|
|
49
|
+
* width along the line, so pen pressure and drawing speed show in the ink.
|
|
50
|
+
*/
|
|
51
|
+
export const strokeOutline = (points, size) => {
|
|
52
|
+
const first = points[0];
|
|
53
|
+
if (!first) return "";
|
|
54
|
+
if (points.length === 1) {
|
|
55
|
+
const radius = round(strokeWidth(size, first.pressure) / 2);
|
|
56
|
+
const arc = x => `A${radius} ${radius} 0 1 0 ${point(x, first.y)}`;
|
|
57
|
+
return `M${point(first.x - radius, first.y)}${arc(first.x + radius)}${arc(first.x - radius)}Z`;
|
|
58
|
+
}
|
|
59
|
+
const left = [];
|
|
60
|
+
const right = [];
|
|
61
|
+
points.forEach((current, index) => {
|
|
62
|
+
const before = points[index - 1] ?? current;
|
|
63
|
+
const after = points[index + 1] ?? current;
|
|
64
|
+
const dx = after.x - before.x;
|
|
65
|
+
const dy = after.y - before.y;
|
|
66
|
+
const length = Math.hypot(dx, dy) || 1;
|
|
67
|
+
const radius = strokeWidth(size, current.pressure) / 2;
|
|
68
|
+
const nx = -dy / length * radius;
|
|
69
|
+
const ny = dx / length * radius;
|
|
70
|
+
left.push({
|
|
71
|
+
x: current.x + nx,
|
|
72
|
+
y: current.y + ny,
|
|
73
|
+
radius
|
|
74
|
+
});
|
|
75
|
+
right.push({
|
|
76
|
+
x: current.x - nx,
|
|
77
|
+
y: current.y - ny,
|
|
78
|
+
radius
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
right.reverse();
|
|
82
|
+
// Quadratic segments through the midpoints of neighbouring edge points keep the outline smooth without overshoot.
|
|
83
|
+
const edge = side => side.map((current, index) => {
|
|
84
|
+
if (index === 0) return "";
|
|
85
|
+
const next = side[index + 1];
|
|
86
|
+
if (!next) return `L${point(current.x, current.y)}`;
|
|
87
|
+
return `Q${point(current.x, current.y)} ${point((current.x + next.x) / 2, (current.y + next.y) / 2)}`;
|
|
88
|
+
}).join("");
|
|
89
|
+
const cap = (from, to) => `A${round(from.radius)} ${round(from.radius)} 0 0 0 ${point(to.x, to.y)}`;
|
|
90
|
+
const start = left[0] ?? right[0];
|
|
91
|
+
const end = left[left.length - 1];
|
|
92
|
+
const back = right[0];
|
|
93
|
+
const backEnd = right[right.length - 1];
|
|
94
|
+
if (!start || !end || !back || !backEnd) return "";
|
|
95
|
+
return `M${point(start.x, start.y)}${edge(left)}${cap(end, back)}${edge(right)}${cap(backEnd, start)}Z`;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Serializes a drawing as a standalone SVG document; an empty drawing has no value. */
|
|
99
|
+
export const drawingToSvg = drawing => {
|
|
100
|
+
if (drawing.strokes.length === 0) return null;
|
|
101
|
+
const width = round(drawing.width);
|
|
102
|
+
const height = round(drawing.height);
|
|
103
|
+
const paths = drawing.strokes.map(stroke => `<path d="${escapeXml(stroke)}"/>`).join("");
|
|
104
|
+
return `<svg xmlns="${SVG_NS}" viewBox="${round(drawing.x)} ${round(drawing.y)} ${width} ${height}" width="${width}" height="${height}"><g fill="currentColor">${paths}</g></svg>`;
|
|
105
|
+
};
|
|
106
|
+
const NUMBER = String.raw`-?\d+(?:\.\d+)?`;
|
|
107
|
+
const SVG_NS_PATTERN = String.raw`http://www\.w3\.org/2000/svg`;
|
|
108
|
+
// Exactly the markup drawingToSvg and typedToSvg write. Path data may only hold the commands and numbers strokeOutline
|
|
109
|
+
// emits, and no attribute or text may hold markup, so a match cannot carry scripts, links, or other elements.
|
|
110
|
+
const DRAWN_SVG = new RegExp(`^<svg xmlns="${SVG_NS_PATTERN}" viewBox="(${NUMBER}) (${NUMBER}) (${NUMBER}) (${NUMBER})" width="${NUMBER}" height="${NUMBER}">` + `<g fill="currentColor">((?:<path d="[MLQAZ\\d .-]+"/>)+)</g></svg>$`);
|
|
111
|
+
const TYPED_SVG = new RegExp(`^<svg xmlns="${SVG_NS_PATTERN}" viewBox="0 0 (\\d+) \\d+" width="\\d+" height="\\d+">` + `<text x="\\d+" y="\\d+" font-family="([^"<>]*)" font-size="\\d+" fill="currentColor">[^<>]*</text></svg>$`);
|
|
112
|
+
|
|
113
|
+
/** Reads the drawing space and strokes back from an SVG drawingToSvg wrote, so a stored signature can be extended or undone. */
|
|
114
|
+
export const svgToDrawing = svg => {
|
|
115
|
+
const match = DRAWN_SVG.exec(svg);
|
|
116
|
+
if (!match) return null;
|
|
117
|
+
const [x = 0, y = 0, width = 0, height = 0] = match.slice(1, 5).map(Number);
|
|
118
|
+
if (width <= 0 || height <= 0) return null;
|
|
119
|
+
const strokes = [...(match[5] ?? "").matchAll(/<path d="([^"]+)"\/>/g)].flatMap(path => path[1] ? [path[1]] : []);
|
|
120
|
+
return {
|
|
121
|
+
x,
|
|
122
|
+
y,
|
|
123
|
+
width,
|
|
124
|
+
height,
|
|
125
|
+
strokes
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Lays out a typed name as an SVG document in the given font family. `measure`
|
|
131
|
+
* returns the text width at `TYPED_FONT_SIZE`; without one, the width is estimated.
|
|
132
|
+
*/
|
|
133
|
+
export const typedToSvg = (name, fontFamily, measure) => {
|
|
134
|
+
const font = `${TYPED_FONT_SIZE}px ${fontFamily}`;
|
|
135
|
+
const textWidth = measure?.(name, font) ?? name.length * TYPED_FONT_SIZE * 0.5;
|
|
136
|
+
const width = Math.ceil(textWidth + TYPED_PADDING * 2);
|
|
137
|
+
const height = Math.ceil(TYPED_FONT_SIZE * 1.6);
|
|
138
|
+
const baseline = Math.round(TYPED_FONT_SIZE * 1.15);
|
|
139
|
+
return `<svg xmlns="${SVG_NS}" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">` + `<text x="${TYPED_PADDING}" y="${baseline}" font-family="${escapeXml(fontFamily)}" font-size="${TYPED_FONT_SIZE}" fill="currentColor">${escapeXml(name)}</text></svg>`;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reads a submitted signature on the server: the JSON string of the field's
|
|
144
|
+
* hidden input, or a value the browser sent as JSON. It accepts only markup
|
|
145
|
+
* the field writes and returns it rebuilt, so the result is safe to store and
|
|
146
|
+
* to embed. An empty or invalid value returns `null`.
|
|
147
|
+
*/
|
|
148
|
+
export const parseSignature = input => {
|
|
149
|
+
let value = input;
|
|
150
|
+
if (typeof input === "string") {
|
|
151
|
+
if (!input) return null;
|
|
152
|
+
try {
|
|
153
|
+
value = JSON.parse(input);
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (typeof value !== "object" || value === null || !("kind" in value) || !("svg" in value) || typeof value.svg !== "string") return null;
|
|
159
|
+
if (value.kind === "drawn") {
|
|
160
|
+
const drawing = svgToDrawing(value.svg);
|
|
161
|
+
const svg = drawing && drawingToSvg(drawing);
|
|
162
|
+
return svg ? {
|
|
163
|
+
kind: "drawn",
|
|
164
|
+
svg
|
|
165
|
+
} : null;
|
|
166
|
+
}
|
|
167
|
+
if (value.kind !== "typed" || !("name" in value) || typeof value.name !== "string") return null;
|
|
168
|
+
const name = signatureName(value.name);
|
|
169
|
+
const match = TYPED_SVG.exec(value.svg);
|
|
170
|
+
if (!name || !match) return null;
|
|
171
|
+
const textWidth = Number(match[1]) - TYPED_PADDING * 2;
|
|
172
|
+
return {
|
|
173
|
+
kind: "typed",
|
|
174
|
+
name,
|
|
175
|
+
svg: typedToSvg(name, unescapeXml(match[2] ?? ""), () => Math.max(0, textWidth))
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Rasterizes a signature into a PNG data URL in the browser, for a document or
|
|
180
|
+
* service that needs a bitmap. A typed signature renders with the fonts
|
|
181
|
+
* installed on this device: an SVG image cannot use the page's web fonts.
|
|
182
|
+
*/
|
|
183
|
+
export const signatureToPng = async (value, options = {}) => {
|
|
184
|
+
const viewBox = /viewBox="-?[\d.]+ -?[\d.]+ ([\d.]+) ([\d.]+)"/.exec(value.svg);
|
|
185
|
+
if (!viewBox) throw new Error("The signature SVG has no drawing size.");
|
|
186
|
+
const scale = options.scale ?? 2;
|
|
187
|
+
const width = Math.max(1, Math.round(Number(viewBox[1]) * scale));
|
|
188
|
+
const height = Math.max(1, Math.round(Number(viewBox[2]) * scale));
|
|
189
|
+
const svg = value.svg.replace("<svg ", `<svg color="${escapeXml(options.color ?? "#000000")}" `);
|
|
190
|
+
const image = new Image();
|
|
191
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
192
|
+
await image.decode();
|
|
193
|
+
const canvas = document.createElement("canvas");
|
|
194
|
+
canvas.width = width;
|
|
195
|
+
canvas.height = height;
|
|
196
|
+
const context = canvas.getContext("2d");
|
|
197
|
+
if (!context) throw new Error("This browser cannot draw a signature image.");
|
|
198
|
+
if (options.background) {
|
|
199
|
+
context.fillStyle = options.background;
|
|
200
|
+
context.fillRect(0, 0, width, height);
|
|
201
|
+
}
|
|
202
|
+
context.drawImage(image, 0, 0, width, height);
|
|
203
|
+
const url = canvas.toDataURL("image/png");
|
|
204
|
+
// A canvas beyond the browser's size limit returns "data:," instead of failing.
|
|
205
|
+
if (!url.startsWith("data:image/png")) throw new Error("The signature is too large for a PNG at this scale.");
|
|
206
|
+
return url;
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/signature.ts"],"sourcesContent":["/**\n * A signature as the field reports it. `kind` records how it was given, so the\n * application can keep that fact next to the image. `svg` is a standalone SVG\n * document whose ink uses `currentColor`, so it renders black as an image. A\n * value that arrives from a browser is untrusted input: read it with\n * `parseSignature` before storing or embedding it.\n */\nexport type SignatureValue = { kind: \"drawn\"; svg: string } | { kind: \"typed\"; name: string; svg: string };\n\n/** One sampled pointer position in drawing units, with a pressure from 0 to 1. */\nexport type SignaturePoint = { x: number; y: number; pressure: number };\n\n/** The drawing space as an SVG viewBox and one closed outline path per stroke. */\nexport type SignatureDrawing = { x: number; y: number; width: number; height: number; strokes: readonly string[] };\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n/** Typed names are laid out at this size; the SVG scales as a whole. */\nconst TYPED_FONT_SIZE = 48;\nconst TYPED_PADDING = 12;\n\nconst round = (value: number) => Math.round(value * 10) / 10;\nconst point = (x: number, y: number) => `${round(x)} ${round(y)}`;\n\n/** Control characters, lone surrogates, and the two noncharacters XML forbids, which a strict XML parser rejects. */\nconst NOT_XML_TEXT = /[\\p{Cc}\\p{Cs}\\uFFFE\\uFFFF]/gu;\n\n/** Escapes text for XML content or attributes and drops characters XML cannot hold, so the document stays well-formed. */\nexport const escapeXml = (text: string): string =>\n text\n .replace(NOT_XML_TEXT, \"\")\n .replace(/[&<>\"']/g, (char) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[char] ?? char);\n\nconst unescapeXml = (text: string): string =>\n text.replace(/&(amp|lt|gt|quot|apos);/g, (_, name: string) => ({ amp: \"&\", lt: \"<\", gt: \">\", quot: '\"', apos: \"'\" })[name] ?? \"\");\n\n/** The name as a typed signature stores it: without characters an SVG cannot hold and without surrounding space. */\nexport const signatureName = (name: string): string => name.replace(NOT_XML_TEXT, \"\").trim();\n\n/** Stroke width in drawing units at a pressure: light pressure still leaves a readable line. */\nexport const strokeWidth = (size: number, pressure: number): number => size * (0.35 + 0.65 * Math.min(1, Math.max(0, pressure)));\n\n/**\n * Turns sampled points into one filled outline: both edges follow the points at\n * half the local stroke width and are smoothed through their midpoints, and\n * round caps close the ends. A filled outline, unlike a stroked path, can change\n * width along the line, so pen pressure and drawing speed show in the ink.\n */\nexport const strokeOutline = (points: readonly SignaturePoint[], size: number): string => {\n const first = points[0];\n if (!first) return \"\";\n if (points.length === 1) {\n const radius = round(strokeWidth(size, first.pressure) / 2);\n const arc = (x: number) => `A${radius} ${radius} 0 1 0 ${point(x, first.y)}`;\n return `M${point(first.x - radius, first.y)}${arc(first.x + radius)}${arc(first.x - radius)}Z`;\n }\n type Edge = { x: number; y: number; radius: number };\n const left: Edge[] = [];\n const right: Edge[] = [];\n points.forEach((current, index) => {\n const before = points[index - 1] ?? current;\n const after = points[index + 1] ?? current;\n const dx = after.x - before.x;\n const dy = after.y - before.y;\n const length = Math.hypot(dx, dy) || 1;\n const radius = strokeWidth(size, current.pressure) / 2;\n const nx = (-dy / length) * radius;\n const ny = (dx / length) * radius;\n left.push({ x: current.x + nx, y: current.y + ny, radius });\n right.push({ x: current.x - nx, y: current.y - ny, radius });\n });\n right.reverse();\n // Quadratic segments through the midpoints of neighbouring edge points keep the outline smooth without overshoot.\n const edge = (side: readonly Edge[]) =>\n side\n .map((current, index) => {\n if (index === 0) return \"\";\n const next = side[index + 1];\n if (!next) return `L${point(current.x, current.y)}`;\n return `Q${point(current.x, current.y)} ${point((current.x + next.x) / 2, (current.y + next.y) / 2)}`;\n })\n .join(\"\");\n const cap = (from: Edge, to: Edge) => `A${round(from.radius)} ${round(from.radius)} 0 0 0 ${point(to.x, to.y)}`;\n const start = left[0] ?? right[0];\n const end = left[left.length - 1];\n const back = right[0];\n const backEnd = right[right.length - 1];\n if (!start || !end || !back || !backEnd) return \"\";\n return `M${point(start.x, start.y)}${edge(left)}${cap(end, back)}${edge(right)}${cap(backEnd, start)}Z`;\n};\n\n/** Serializes a drawing as a standalone SVG document; an empty drawing has no value. */\nexport const drawingToSvg = (drawing: SignatureDrawing): string | null => {\n if (drawing.strokes.length === 0) return null;\n const width = round(drawing.width);\n const height = round(drawing.height);\n const paths = drawing.strokes.map((stroke) => `<path d=\"${escapeXml(stroke)}\"/>`).join(\"\");\n return `<svg xmlns=\"${SVG_NS}\" viewBox=\"${round(drawing.x)} ${round(drawing.y)} ${width} ${height}\" width=\"${width}\" height=\"${height}\"><g fill=\"currentColor\">${paths}</g></svg>`;\n};\n\nconst NUMBER = String.raw`-?\\d+(?:\\.\\d+)?`;\nconst SVG_NS_PATTERN = String.raw`http://www\\.w3\\.org/2000/svg`;\n// Exactly the markup drawingToSvg and typedToSvg write. Path data may only hold the commands and numbers strokeOutline\n// emits, and no attribute or text may hold markup, so a match cannot carry scripts, links, or other elements.\nconst DRAWN_SVG = new RegExp(\n `^<svg xmlns=\"${SVG_NS_PATTERN}\" viewBox=\"(${NUMBER}) (${NUMBER}) (${NUMBER}) (${NUMBER})\" width=\"${NUMBER}\" height=\"${NUMBER}\">` +\n `<g fill=\"currentColor\">((?:<path d=\"[MLQAZ\\\\d .-]+\"/>)+)</g></svg>$`,\n);\nconst TYPED_SVG = new RegExp(\n `^<svg xmlns=\"${SVG_NS_PATTERN}\" viewBox=\"0 0 (\\\\d+) \\\\d+\" width=\"\\\\d+\" height=\"\\\\d+\">` +\n `<text x=\"\\\\d+\" y=\"\\\\d+\" font-family=\"([^\"<>]*)\" font-size=\"\\\\d+\" fill=\"currentColor\">[^<>]*</text></svg>$`,\n);\n\n/** Reads the drawing space and strokes back from an SVG drawingToSvg wrote, so a stored signature can be extended or undone. */\nexport const svgToDrawing = (svg: string): SignatureDrawing | null => {\n const match = DRAWN_SVG.exec(svg);\n if (!match) return null;\n const [x = 0, y = 0, width = 0, height = 0] = match.slice(1, 5).map(Number);\n if (width <= 0 || height <= 0) return null;\n const strokes = [...(match[5] ?? \"\").matchAll(/<path d=\"([^\"]+)\"\\/>/g)].flatMap((path) => (path[1] ? [path[1]] : []));\n return { x, y, width, height, strokes };\n};\n\n/**\n * Lays out a typed name as an SVG document in the given font family. `measure`\n * returns the text width at `TYPED_FONT_SIZE`; without one, the width is estimated.\n */\nexport const typedToSvg = (name: string, fontFamily: string, measure?: (text: string, font: string) => number | undefined): string => {\n const font = `${TYPED_FONT_SIZE}px ${fontFamily}`;\n const textWidth = measure?.(name, font) ?? name.length * TYPED_FONT_SIZE * 0.5;\n const width = Math.ceil(textWidth + TYPED_PADDING * 2);\n const height = Math.ceil(TYPED_FONT_SIZE * 1.6);\n const baseline = Math.round(TYPED_FONT_SIZE * 1.15);\n return (\n `<svg xmlns=\"${SVG_NS}\" viewBox=\"0 0 ${width} ${height}\" width=\"${width}\" height=\"${height}\">` +\n `<text x=\"${TYPED_PADDING}\" y=\"${baseline}\" font-family=\"${escapeXml(fontFamily)}\" font-size=\"${TYPED_FONT_SIZE}\" fill=\"currentColor\">${escapeXml(name)}</text></svg>`\n );\n};\n\n/**\n * Reads a submitted signature on the server: the JSON string of the field's\n * hidden input, or a value the browser sent as JSON. It accepts only markup\n * the field writes and returns it rebuilt, so the result is safe to store and\n * to embed. An empty or invalid value returns `null`.\n */\nexport const parseSignature = (input: unknown): SignatureValue | null => {\n let value: unknown = input;\n if (typeof input === \"string\") {\n if (!input) return null;\n try {\n value = JSON.parse(input);\n } catch {\n return null;\n }\n }\n if (typeof value !== \"object\" || value === null || !(\"kind\" in value) || !(\"svg\" in value) || typeof value.svg !== \"string\") return null;\n if (value.kind === \"drawn\") {\n const drawing = svgToDrawing(value.svg);\n const svg = drawing && drawingToSvg(drawing);\n return svg ? { kind: \"drawn\", svg } : null;\n }\n if (value.kind !== \"typed\" || !(\"name\" in value) || typeof value.name !== \"string\") return null;\n const name = signatureName(value.name);\n const match = TYPED_SVG.exec(value.svg);\n if (!name || !match) return null;\n const textWidth = Number(match[1]) - TYPED_PADDING * 2;\n return { kind: \"typed\", name, svg: typedToSvg(name, unescapeXml(match[2] ?? \"\"), () => Math.max(0, textWidth)) };\n};\n\nexport type SignaturePngOptions = {\n /** Device pixels per SVG unit. Defaults to `2`. */\n scale?: number;\n /** Ink colour. Defaults to `\"#000000\"`. */\n color?: string;\n /** Background fill. Defaults to transparent. */\n background?: string;\n};\n\n/**\n * Rasterizes a signature into a PNG data URL in the browser, for a document or\n * service that needs a bitmap. A typed signature renders with the fonts\n * installed on this device: an SVG image cannot use the page's web fonts.\n */\nexport const signatureToPng = async (value: SignatureValue, options: SignaturePngOptions = {}): Promise<string> => {\n const viewBox = /viewBox=\"-?[\\d.]+ -?[\\d.]+ ([\\d.]+) ([\\d.]+)\"/.exec(value.svg);\n if (!viewBox) throw new Error(\"The signature SVG has no drawing size.\");\n const scale = options.scale ?? 2;\n const width = Math.max(1, Math.round(Number(viewBox[1]) * scale));\n const height = Math.max(1, Math.round(Number(viewBox[2]) * scale));\n const svg = value.svg.replace(\"<svg \", `<svg color=\"${escapeXml(options.color ?? \"#000000\")}\" `);\n const image = new Image();\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await image.decode();\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n if (!context) throw new Error(\"This browser cannot draw a signature image.\");\n if (options.background) {\n context.fillStyle = options.background;\n context.fillRect(0, 0, width, height);\n }\n context.drawImage(image, 0, 0, width, height);\n const url = canvas.toDataURL(\"image/png\");\n // A canvas beyond the browser's size limit returns \"data:,\" instead of failing.\n if (!url.startsWith(\"data:image/png\")) throw new Error(\"The signature is too large for a PNG at this scale.\");\n return url;\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { memo as _$memo } from "solid-js/web";
|
|
2
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
|
+
import { ErrorBoundary, untrack } from "solid-js";
|
|
4
|
+
import { Button } from "../actions/Button";
|
|
5
|
+
import { useUiMessages } from "../intl/messages";
|
|
6
|
+
import Placeholder from "../surfaces/Placeholder";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Guards a Solid root that @k2b/ui creates itself, such as a dialog or a floating window. These roots are mostly
|
|
10
|
+
* opened from a click handler, outside any owner and any island boundary. Without a boundary of their own, content
|
|
11
|
+
* that throws on an update throws into the code that wrote the signal and leaves other parts of the page stale.
|
|
12
|
+
* The content is created untracked, as `render()` and components create it, so a signal it reads directly does not
|
|
13
|
+
* rebuild it. `onClose` adds a Close button for a root whose content draws its own close control.
|
|
14
|
+
*/
|
|
15
|
+
export function RenderErrorBoundary(props) {
|
|
16
|
+
return _$createComponent(ErrorBoundary, {
|
|
17
|
+
fallback: error => {
|
|
18
|
+
globalThis.reportError(error);
|
|
19
|
+
props.onError?.();
|
|
20
|
+
return _$createComponent(RenderErrorNotice, {
|
|
21
|
+
get onClose() {
|
|
22
|
+
return props.onClose;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
get children() {
|
|
27
|
+
return untrack(props.content);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function RenderErrorNotice(props) {
|
|
32
|
+
const messages = useUiMessages();
|
|
33
|
+
return _$createComponent(Placeholder, {
|
|
34
|
+
state: "error",
|
|
35
|
+
get title() {
|
|
36
|
+
return messages().couldNotDisplayContent;
|
|
37
|
+
},
|
|
38
|
+
get action() {
|
|
39
|
+
return _$memo(() => !!props.onClose)() ? _$createComponent(Button, {
|
|
40
|
+
variant: "secondary",
|
|
41
|
+
onClick: () => props.onClose?.(),
|
|
42
|
+
get children() {
|
|
43
|
+
return messages().close;
|
|
44
|
+
}
|
|
45
|
+
}) : undefined;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=render-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["internal/render-error.tsx"],"sourcesContent":["import { ErrorBoundary, type JSX, untrack } from \"solid-js\";\nimport { Button } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport Placeholder from \"../surfaces/Placeholder\";\n\n/**\n * Guards a Solid root that @k2b/ui creates itself, such as a dialog or a floating window. These roots are mostly\n * opened from a click handler, outside any owner and any island boundary. Without a boundary of their own, content\n * that throws on an update throws into the code that wrote the signal and leaves other parts of the page stale.\n * The content is created untracked, as `render()` and components create it, so a signal it reads directly does not\n * rebuild it. `onClose` adds a Close button for a root whose content draws its own close control.\n */\nexport function RenderErrorBoundary(props: { content: () => JSX.Element; onClose?: () => void; onError?: () => void }): JSX.Element {\n return (\n <ErrorBoundary\n fallback={(error: unknown) => {\n globalThis.reportError(error);\n props.onError?.();\n return <RenderErrorNotice onClose={props.onClose} />;\n }}\n >\n {untrack(props.content)}\n </ErrorBoundary>\n );\n}\n\nfunction RenderErrorNotice(props: { onClose?: () => void }): JSX.Element {\n const messages = useUiMessages();\n return (\n <Placeholder\n state=\"error\"\n title={messages().couldNotDisplayContent}\n action={\n props.onClose ? (\n <Button variant=\"secondary\" onClick={() => props.onClose?.()}>\n {messages().close}\n </Button>\n ) : undefined\n }\n />\n );\n}\n"],"mappings":";;AAAA,SAAS,aAAa,EAAY,OAAO,QAAQ,UAAU;AAC3D,SAAS,MAAM,QAAQ,mBAAmB;AAC1C,SAAS,aAAa,QAAQ,kBAAkB;AAChD,OAAO,WAAW,MAAM,yBAAyB;;AAEjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS,mBAAmB,CAAC,KAAiF,EAAe;EAClI,yBACG,aAAa;IACZ,QAAQ,EAAG,KAAc,IAAK;MAC5B,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC;MAC7B,KAAK,CAAC,OAAO,GAAG,CAAC;MACjB,yBAAQ,iBAAiB;QAAA,IAAC,OAAO;UAAA,OAAE,KAAK,CAAC,OAAO;QAAA;MAAA;IAClD,CAAC;IAAA;MAAA,OAEA,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IAAA;EAAA;AAG7B;AAEA,SAAS,iBAAiB,CAAC,KAA+B,EAAe;EACvE,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC;EAChC,yBACG,WAAW;IACV,KAAK;IAAA,IACL,KAAK;MAAA,OAAE,QAAQ,CAAC,CAAC,CAAC,sBAAsB;IAAA;IAAA,IACxC,MAAM;MAAA,OACJ,oBAAK,CAAC,OAAO,wBACV,MAAM;QAAC,OAAO;QAAa,OAAO,EAAE,MAAM,KAAK,CAAC,OAAO,GAAG,CAAC;QAAA;UAAA,OACzD,QAAQ,CAAC,CAAC,CAAC,KAAK;QAAA;MAAA,KAEjB,SAAS;IAAA;EAAA;AAIrB","ignoreList":[]}
|