autumnnote 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +74 -6
  2. package/dist/autumnnote.cjs +27 -22
  3. package/dist/autumnnote.es.js +474 -591
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.min.js +27 -22
  6. package/dist/autumnnote.umd.js +27 -22
  7. package/dist/autumnnote.umd.js.map +1 -1
  8. package/dist/icon-data-V0Xqv-wX.js +255 -0
  9. package/dist/icon-data-V0Xqv-wX.js.map +1 -0
  10. package/package.json +12 -3
  11. package/types/index.d.ts +8 -0
  12. package/src/js/Context.js +0 -854
  13. package/src/js/core/detectLang.js +0 -98
  14. package/src/js/core/dom.js +0 -372
  15. package/src/js/core/env.js +0 -38
  16. package/src/js/core/key.js +0 -66
  17. package/src/js/core/lists.js +0 -121
  18. package/src/js/core/markdown.js +0 -695
  19. package/src/js/core/range.js +0 -194
  20. package/src/js/core/sanitise.js +0 -304
  21. package/src/js/editing/History.js +0 -266
  22. package/src/js/editing/Style.js +0 -812
  23. package/src/js/editing/Table.js +0 -105
  24. package/src/js/editing/Typing.js +0 -397
  25. package/src/js/index.js +0 -193
  26. package/src/js/index.umd.js +0 -17
  27. package/src/js/module/AutoSaveRestore.js +0 -125
  28. package/src/js/module/BaseDialog.js +0 -133
  29. package/src/js/module/BaseMediaTooltip.js +0 -142
  30. package/src/js/module/BaseResizer.js +0 -322
  31. package/src/js/module/BubbleToolbar.js +0 -483
  32. package/src/js/module/Buttons.js +0 -399
  33. package/src/js/module/Clipboard.js +0 -579
  34. package/src/js/module/CodeTooltip.js +0 -493
  35. package/src/js/module/Codeview.js +0 -125
  36. package/src/js/module/ContextMenu.js +0 -621
  37. package/src/js/module/Editor.js +0 -747
  38. package/src/js/module/EmojiDialog.js +0 -254
  39. package/src/js/module/FindReplace.js +0 -512
  40. package/src/js/module/Fullscreen.js +0 -80
  41. package/src/js/module/IconDialog.js +0 -618
  42. package/src/js/module/ImageCropOverlay.js +0 -586
  43. package/src/js/module/ImageDialog.js +0 -193
  44. package/src/js/module/ImageResizer.js +0 -42
  45. package/src/js/module/ImageTooltip.js +0 -285
  46. package/src/js/module/LinkDialog.js +0 -145
  47. package/src/js/module/LinkTooltip.js +0 -250
  48. package/src/js/module/MarkdownShortcuts.js +0 -250
  49. package/src/js/module/Mention.js +0 -365
  50. package/src/js/module/Placeholder.js +0 -51
  51. package/src/js/module/ShortcutsDialog.js +0 -111
  52. package/src/js/module/SlashMenu.js +0 -376
  53. package/src/js/module/Statusbar.js +0 -246
  54. package/src/js/module/TableTooltip.js +0 -1392
  55. package/src/js/module/Toolbar.js +0 -855
  56. package/src/js/module/VideoDialog.js +0 -193
  57. package/src/js/module/VideoResizer.js +0 -66
  58. package/src/js/module/VideoTooltip.js +0 -248
  59. package/src/js/module/emoji-data.js +0 -496
  60. package/src/js/module/table-grid.js +0 -102
  61. package/src/js/module/table-icons.js +0 -33
  62. package/src/js/renderer.js +0 -120
  63. package/src/js/settings.js +0 -214
  64. package/src/styles/_variables.scss +0 -48
  65. package/src/styles/autumnnote.scss +0 -2877
@@ -1,98 +0,0 @@
1
- /**
2
- * detectLang.js — Heuristic programming-language detection for code snippets.
3
- *
4
- * Returns a Prism.js language identifier or null when no language can be
5
- * determined with reasonable confidence.
6
- *
7
- * Detection order (conflicts in parentheses):
8
- * TypeScript → Rust → PHP → Java → Kotlin → Swift → Go
9
- * → JavaScript → HTML → CSS → JSON → SQL → Python → Ruby
10
- * → Bash → C++ → C# → C → XML
11
- *
12
- * @param {string} code
13
- * @returns {string|null}
14
- */
15
- export function detectLang(code) {
16
- if (!code?.trim()) return null;
17
- const s = code.trim();
18
-
19
- // ── TypeScript ─────────────────────────────────────────────────────────────
20
- // Must be first — TS is a JS superset; its markers are specific.
21
- if (/(:\s*(string|number|boolean|void|never|any|unknown)\b|interface\s+\w+\s*\{|type\s+\w+\s*[=<(]|<\w+>\s*[;,)]|readonly\s+\w|enum\s+\w+\s*\{|\?\s*:\s*\w|as\s+\w+\s*[;,)\]])/.test(s)) return 'typescript';
22
-
23
- // ── Rust ────────────────────────────────────────────────────────────────────
24
- // Before JavaScript — both have `let`. `!` macros and `pub fn` are unique.
25
- if (/\bprintln!\s*\(|\bprint!\s*\(|\bfn\s+\w+\s*(<[^>]*>)?\s*\(|\blet\s+mut\s|\bpub\s+fn\s|\buse\s+std::|\bimpl\s+\w+|\bOption<|\bResult<\w+/.test(s)) return 'rust';
26
-
27
- // ── PHP ─────────────────────────────────────────────────────────────────────
28
- // Before Bash — both have `echo`. `<?php` and `$var` are unique to PHP.
29
- if (/(<\?php\b|<\?=|\becho\s+.*\$\w|\$this->|\$\w+\s*=\s*\w|\bforeach\s*\(\s*\$|Illuminate\\)/.test(s)) return 'php';
30
-
31
- // ── Java ────────────────────────────────────────────────────────────────────
32
- // Before Kotlin — `System.out.println()` would otherwise match Kotlin's
33
- // `println\s*\(` pattern.
34
- if (/\bpublic\s+(class|static|void|int|String)\s+\w|System\.out\.(print|println)\s*\(|@(Override|Autowired|Component|Service|Controller)\b|import\s+java\.(util|io|lang|net)\.|throws\s+\w+Exception/.test(s)) return 'java';
35
-
36
- // ── Kotlin ──────────────────────────────────────────────────────────────────
37
- // Kotlin uses `val` (immutable) unlike Swift/JS which use `let`.
38
- // `fun`, `data class`, `companion object`, `println()` are Kotlin-specific.
39
- if (/\bfun\s+\w+\s*\(|\bdata\s+class\s+\w+|\bcompanion\s+object\b|\bval\s+\w+\s*:\s*\w|\bprintln\s*\(/.test(s)) return 'kotlin';
40
-
41
- // ── Swift ───────────────────────────────────────────────────────────────────
42
- // Swift uses `let` for constants (Kotlin uses `val`). `guard let`, `protocol`,
43
- // `extension`, `func ... -> ReturnType`, and `let x: UppercaseType` are signals.
44
- if (/\bguard\s+(let|var)\b|\bprotocol\s+\w+\s*\{|\bextension\s+\w+|\bfunc\s+\w+[^(]*\([^)]*\)\s*->\s*\w|\blet\s+\w+\s*:\s*[A-Z]\w*|\bSwiftUI\b/.test(s)) return 'swift';
45
-
46
- // ── Go ──────────────────────────────────────────────────────────────────────
47
- // `package`, `:=` short assignment, `fmt.Print*`, `chan`, `goroutine`.
48
- if (/\bpackage\s+\w+\b|\bfmt\.(Print|Println|Sprintf|Errorf|Fprintf)\s*\(|:=\s*\w|\bgoroutine\b|\bchan\s+\w|\bgo\s+func\b/.test(s)) return 'go';
49
-
50
- // ── JavaScript ──────────────────────────────────────────────────────────────
51
- // Before HTML to handle JSX (`<div/>` would otherwise trigger HTML).
52
- // `var\s+\w+\s*=` (not just `var\s+\w`) to avoid matching Swift `var x: Int`.
53
- if (/\b(const\s+\w|let\s+\w+\s*=|var\s+\w+\s*=|function\s+\w|\=>\s*[{(]|import\s+.*\bfrom\b\s*['"]|require\s*\(|console\.(log|error|warn|info)|document\.\w|window\.\w|async\s+function|\bPromise\b|React\.|useState\s*\(|\.then\s*\()/.test(s)) return 'javascript';
54
-
55
- // ── HTML ────────────────────────────────────────────────────────────────────
56
- if (/^<!DOCTYPE html/i.test(s) || /<(html|head|body|div|section|article|nav|p|a|img|ul|ol|li|table|form|input|button|script|style)\b[^>]*>/i.test(s)) return 'html';
57
-
58
- // ── SCSS ─────────────────────────────────────────────────────────────────────
59
- // Before CSS — SCSS is a superset. Unique markers: `//` line comments,
60
- // `&` nesting, `$variable`, `@mixin/@include/@extend`, `#{interpolation}`.
61
- if (/(^|\n)\s*(\/\/\s+\S|&[:.[\w]|\$\w+\s*:|@(mixin|include|extend|each|if|for|use|forward)\b|#\{)/.test(s) && /[\w#.*&[\]:(),>+~ -]+\s*\{/.test(s)) return 'scss';
62
-
63
- // ── CSS ──────────────────────────────────────────────────────────────────────
64
- if (/(^|\n)\s*[\w#.*:[\]&, +-]+\s*\{[^}]*[\w-]+\s*:[^{}:;]+[;}\n]/m.test(s) && !/<\w|function\s|def\s|:\s*(string|number)/.test(s)) return 'css';
65
-
66
- // ── JSON ────────────────────────────────────────────────────────────────────
67
- if (/^\s*[{[]/.test(s) && /"\w[\w\s-]*"\s*:/.test(s) && !/\bfunction\b|\bdef\b/.test(s)) return 'json';
68
-
69
- // ── SQL ─────────────────────────────────────────────────────────────────────
70
- if (/(^|\n)\s*(SELECT\s|INSERT\s+INTO|UPDATE\s+\w|DELETE\s+FROM|CREATE\s+(TABLE|DATABASE|INDEX|VIEW)|DROP\s+(TABLE|DATABASE)|ALTER\s+TABLE|WITH\s+\w+\s+AS\s*\()/im.test(s)) return 'sql';
71
-
72
- // ── Python ──────────────────────────────────────────────────────────────────
73
- // `def` requires trailing `:` (Python rule); `class` checked at line start.
74
- if (/\bdef\s+\w+\s*\([^)]*\)\s*:|(^|\n)\s*class\s+\w+.*:\s*$|(^|\n)\s*import\s+\w|(^|\n)\s*from\s+\w+\s+import\s+|\bprint\s*\(|if\s+__name__\s*==\s*['"]__main__['"]/m.test(s)) return 'python';
75
-
76
- // ── Ruby ────────────────────────────────────────────────────────────────────
77
- // `.each do |x|`, `attr_*`, `puts` with value, multi-line `def...end`.
78
- if (/\bputs\s+\S|\battr_(accessor|reader|writer)\s|\.each\s+do\s*\|\w+\s*\||\bdo\s*\|\w+\s*\|.*\bend\b|\bdef\s+\w+[^:]*\n[\s\S]*?\bend\b/.test(s)) return 'ruby';
79
-
80
- // ── Bash / Shell ─────────────────────────────────────────────────────────────
81
- if (/^#!.*\/(ba|z|da|fi|k)?sh\b/m.test(s) || /\b(echo\s+["']|grep\s+|awk\s+|sed\s+['"\\/-]|chmod\s+|sudo\s+|apt(-get)?\s+install|brew\s+install|npm\s+(install|run|start|build)|pip\s+(install|3\s)|docker\s+(run|build|compose)|kubectl\s+|git\s+(clone|add|commit|push|pull|checkout))\b/.test(s)) return 'bash';
82
-
83
- // ── C++ ─────────────────────────────────────────────────────────────────────
84
- // Before C and C# — `cout<<`, `using namespace std`, `std::`, `template<>`.
85
- if (/\bcout\s*<<|\bcin\s*>>|using\s+namespace\s+std\b|std::\w|\btemplate\s*<\w|\b#include\s*<(iostream|vector|map|set|algorithm|string|memory)>/.test(s)) return 'cpp';
86
-
87
- // ── C# ──────────────────────────────────────────────────────────────────────
88
- // After C++ to avoid matching C++'s `using namespace std`.
89
- if (/\busing\s+System\b|Console\.(Write|WriteLine)\s*\(|\bget;\s*set;|\basync\s+Task[<\s]|IEnumerable<|\bLINQ\b|\.Select\s*\(|\.Where\s*\(/.test(s)) return 'csharp';
90
-
91
- // ── C ───────────────────────────────────────────────────────────────────────
92
- if (/\b#include\s*<(stdio|stdlib|string|math|time|ctype)\.h>|\bprintf\s*\(|\bscanf\s*\(|int\s+main\s*\(\s*(void|int\s+argc)|\bmalloc\s*\(|\bfree\s*\(/.test(s) && !/namespace|cout|cin|std::/.test(s)) return 'c';
93
-
94
- // ── XML ─────────────────────────────────────────────────────────────────────
95
- if (/^<\?xml\s/i.test(s) || /xmlns:|<\/[\w:]+>/.test(s)) return 'xml';
96
-
97
- return null;
98
- }
@@ -1,372 +0,0 @@
1
- /**
2
- * dom.js - DOM manipulation utilities
3
- * Inspired by Summernote's dom.js — rewritten for vanilla JS without jQuery
4
- */
5
-
6
- // ---------------------------------------------------------------------------
7
- // Node type helpers
8
- // ---------------------------------------------------------------------------
9
-
10
- export const ELEMENT_NODE = 1;
11
- export const TEXT_NODE = 3;
12
-
13
- /** @param {Node} node */
14
- export const isElement = (node) => node?.nodeType === ELEMENT_NODE;
15
- /** @param {Node} node */
16
- export const isText = (node) => node?.nodeType === TEXT_NODE;
17
- /** @param {Node} node */
18
- export const isVoid = (node) => isElement(node) && /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i.test(node.nodeName);
19
- /** @param {Node} node */
20
- export const isPara = (node) => isElement(node) && /^(p|div|li|h[1-6]|blockquote|td|th|pre)$/i.test(node.nodeName);
21
- /** @param {Node} node */
22
- export const isLi = (node) => isElement(node) && /^(li)$/i.test(node.nodeName);
23
- /** @param {Node} node */
24
- export const isList = (node) => isElement(node) && /^(ul|ol)$/i.test(node.nodeName);
25
- /** @param {Node} node */
26
- export const isTable = (node) => isElement(node) && node.nodeName.toUpperCase() === 'TABLE';
27
- /** @param {Node} node */
28
- export const isInline = (node) =>
29
- isElement(node) &&
30
- /^(a|abbr|acronym|b|bdo|big|br|button|cite|code|dfn|em|i|img|input|kbd|label|map|object|output|q|s|samp|select|small|span|strong|sub|sup|textarea|time|tt|u|var)$/i.test(node.nodeName);
31
- /** @param {Node} node */
32
- export const isEditable = (node) => isElement(node) && /** @type {HTMLElement} */ (node).isContentEditable;
33
- /** @param {Node} node */
34
- export const isAnchor = (node) => isElement(node) && node.nodeName.toUpperCase() === 'A';
35
- /** @param {Node} node */
36
- export const isImage = (node) => isElement(node) && node.nodeName.toUpperCase() === 'IMG';
37
-
38
- // ---------------------------------------------------------------------------
39
- // Tree traversal
40
- // ---------------------------------------------------------------------------
41
-
42
- /**
43
- * Walk up the DOM tree from node, returning the first element matching predicate (inclusive).
44
- * @param {Node} node
45
- * @param {(node: Node) => boolean} predicate
46
- * @param {Node} [stopAt] - stop traversal at this ancestor (exclusive)
47
- * @returns {Node|null}
48
- */
49
- export function closest(node, predicate, stopAt) {
50
- let cur = node;
51
- while (cur && cur !== stopAt) {
52
- if (predicate(cur)) return cur;
53
- cur = cur.parentNode;
54
- }
55
- return null;
56
- }
57
-
58
- /**
59
- * Returns the nearest ancestor that is a paragraph-like block.
60
- * @param {Node} node
61
- * @param {Node} [editable]
62
- * @returns {Node|null}
63
- */
64
- export function closestPara(node, editable) {
65
- return closest(node, isPara, editable);
66
- }
67
-
68
- /**
69
- * Returns all ancestors of node up to (but not including) stopAt.
70
- * @param {Node} node
71
- * @param {Node} [stopAt]
72
- * @returns {Node[]}
73
- */
74
- export function ancestors(node, stopAt) {
75
- const result = [];
76
- let cur = node.parentNode;
77
- while (cur && cur !== stopAt) {
78
- result.push(cur);
79
- cur = cur.parentNode;
80
- }
81
- return result;
82
- }
83
-
84
- /**
85
- * Returns all children of node as an Array.
86
- * @param {Node} node
87
- * @returns {Node[]}
88
- */
89
- export function children(node) {
90
- return Array.from(node.childNodes);
91
- }
92
-
93
- /**
94
- * Returns the previous sibling element (skipping text/comment nodes).
95
- * @param {Node} node
96
- * @returns {Element|null}
97
- */
98
- export function prevElement(node) {
99
- let sibling = node.previousSibling;
100
- while (sibling && !isElement(sibling)) {
101
- sibling = sibling.previousSibling;
102
- }
103
- return /** @type {Element|null} */ (sibling);
104
- }
105
-
106
- /**
107
- * Returns the next sibling element.
108
- * @param {Node} node
109
- * @returns {Element|null}
110
- */
111
- export function nextElement(node) {
112
- let sibling = node.nextSibling;
113
- while (sibling && !isElement(sibling)) {
114
- sibling = sibling.nextSibling;
115
- }
116
- return /** @type {Element|null} */ (sibling);
117
- }
118
-
119
- // ---------------------------------------------------------------------------
120
- // DOM mutation helpers
121
- // ---------------------------------------------------------------------------
122
-
123
- /**
124
- * Creates an element with optional attributes and children.
125
- * @param {string} tag
126
- * @param {Record<string, string>} [attrs]
127
- * @param {(Node|string)[]} [childNodes]
128
- * @returns {HTMLElement}
129
- */
130
- export function createElement(tag, attrs = {}, childNodes = []) {
131
- const el = document.createElement(tag);
132
- for (const [k, v] of Object.entries(attrs)) {
133
- el.setAttribute(k, v);
134
- }
135
- for (const child of childNodes) {
136
- if (typeof child === 'string') {
137
- el.appendChild(document.createTextNode(child));
138
- } else {
139
- el.appendChild(child);
140
- }
141
- }
142
- return el;
143
- }
144
-
145
- /**
146
- * Removes a node from its parent.
147
- * @param {Node} node
148
- */
149
- export function remove(node) {
150
- if (node?.parentNode) {
151
- /** @type {ChildNode} */ (node).remove();
152
- }
153
- }
154
-
155
- /**
156
- * Unwraps a node — replaces the node with its children.
157
- * @param {Node} node
158
- */
159
- export function unwrap(node) {
160
- const parent = node.parentNode;
161
- if (!parent) return;
162
- while (node.firstChild) {
163
- parent.insertBefore(node.firstChild, node);
164
- }
165
- /** @type {ChildNode} */ (node).remove();
166
- }
167
-
168
- /**
169
- * Wraps a node with a wrapper element.
170
- * @param {Node} node
171
- * @param {HTMLElement} wrapper
172
- * @returns {HTMLElement} the wrapper
173
- */
174
- export function wrap(node, wrapper) {
175
- node.parentNode.insertBefore(wrapper, node);
176
- wrapper.appendChild(node);
177
- return wrapper;
178
- }
179
-
180
- /**
181
- * Insert node after reference node.
182
- * @param {Node} newNode
183
- * @param {Node} refNode
184
- */
185
- export function insertAfter(newNode, refNode) {
186
- if (refNode.nextSibling) {
187
- refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
188
- } else {
189
- refNode.parentNode.appendChild(newNode);
190
- }
191
- }
192
-
193
- // ---------------------------------------------------------------------------
194
- // Content helpers
195
- // ---------------------------------------------------------------------------
196
-
197
- /**
198
- * Returns the text content of a node (safe).
199
- * @param {Node} node
200
- * @returns {string}
201
- */
202
- export function nodeValue(node) {
203
- return isText(node) ? node.nodeValue : node.textContent || '';
204
- }
205
-
206
- /**
207
- * Determine whether a DOM node contains no visible content.
208
- *
209
- * Text nodes are considered empty when their `nodeValue` is empty. Void elements (e.g., `img`, `br`, `input`) are considered non-empty. An element with exactly one `<br>` child is treated as empty. For other elements, emptiness means trimmed `textContent` is empty and there are no descendant `img`, `video`, `hr`, or `table` elements.
210
- * @param {Node} node - Node to inspect for visible content.
211
- * @returns {boolean} `true` if the node has no visible content, `false` otherwise.
212
- */
213
- export function isEmpty(node) {
214
- if (isText(node)) return !node.nodeValue;
215
- if (isVoid(node)) return false;
216
- if (node.childNodes.length === 1 && node.firstChild?.nodeName === 'BR') return true;
217
- return !node.textContent.trim() && !/** @type {Element} */ (node).querySelector('img, video, hr, table');
218
- }
219
-
220
- /**
221
- * Returns the outerHTML of an element.
222
- * @param {Element} el
223
- * @returns {string}
224
- */
225
- export function outerHtml(el) {
226
- return el.outerHTML;
227
- }
228
-
229
- // ---------------------------------------------------------------------------
230
- // Selection / editing helpers
231
- // ---------------------------------------------------------------------------
232
-
233
- /**
234
- * Places the caret at the end of a contenteditable element.
235
- * @param {HTMLElement} el
236
- */
237
- export function placeCaret(el) {
238
- const range = document.createRange();
239
- range.selectNodeContents(el);
240
- range.collapse(false);
241
- const sel = globalThis.getSelection();
242
- if (sel) {
243
- sel.removeAllRanges();
244
- sel.addRange(range);
245
- }
246
- }
247
-
248
- /**
249
- * Returns true if the node is inside a contenteditable root.
250
- * @param {Node} node
251
- * @returns {boolean}
252
- */
253
- export function isInsideEditable(node) {
254
- return !!closest(node, isEditable);
255
- }
256
-
257
- // ---------------------------------------------------------------------------
258
- // Event helpers
259
- // ---------------------------------------------------------------------------
260
-
261
- /**
262
- * Adds an event listener and returns a disposer function.
263
- * @param {EventTarget} target
264
- * @param {string} type
265
- * @param {EventListener} handler
266
- * @param {AddEventListenerOptions} [options]
267
- * @returns {() => void} disposer
268
- */
269
- export function on(target, type, handler, options) {
270
- target.addEventListener(type, handler, options);
271
- return () => target.removeEventListener(type, handler, options);
272
- }
273
-
274
- /**
275
- * Installs a keyboard focus trap inside a dialog container.
276
- * - Tab / Shift+Tab cycles focus within the container's focusable children.
277
- * - Escape calls `onEscape` and removes the trap listener.
278
- *
279
- * Returns a disposer function that removes the listener (call on dialog close).
280
- *
281
- * @param {HTMLElement} container - the dialog element to trap focus inside
282
- * @param {() => void} onEscape - called when Escape is pressed
283
- * @returns {() => void} disposer
284
- */
285
- export function trapFocus(container, onEscape) {
286
- const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
287
-
288
- const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE)).filter(
289
- (el) => !el.closest('[style*="display: none"]') && !el.closest('[style*="display:none"]'),
290
- );
291
-
292
- const handler = (e) => {
293
- if (e.key === 'Escape') {
294
- e.stopPropagation();
295
- onEscape?.();
296
- return;
297
- }
298
- if (e.key !== 'Tab') return;
299
- const els = getFocusable();
300
- if (!els.length) return;
301
- const first = els[0];
302
- const last = els.at(-1);
303
- if (e.shiftKey) {
304
- if (document.activeElement === first) {
305
- e.preventDefault();
306
- /** @type {HTMLElement} */ (last).focus();
307
- }
308
- } else if (document.activeElement === last) {
309
- e.preventDefault();
310
- /** @type {HTMLElement} */ (first).focus();
311
- }
312
- };
313
-
314
- document.addEventListener('keydown', handler);
315
- return () => document.removeEventListener('keydown', handler);
316
- }
317
-
318
- /**
319
- * Makes a dialog box draggable by its handle element.
320
- * On first drag the box is pinned to its current viewport coordinates via
321
- * `position:fixed`, freeing it from the parent flex container's centering.
322
- * The position is clamped to the visible viewport.
323
- *
324
- * @param {HTMLElement} handle Element the user grabs (title bar / header)
325
- * @param {HTMLElement} box Element that actually moves
326
- * @returns {Function} Cleanup function (removes the mousedown listener)
327
- */
328
- export function makeDraggable(handle, box) {
329
- handle.style.cursor = 'grab';
330
-
331
- const onMousedown = (e) => {
332
- if (e.button !== 0) return;
333
- // Don't start drag when clicking on interactive children of the handle
334
- if (/** @type {Element} */ (e.target).closest('button, input, select, textarea, a')) return;
335
-
336
- e.preventDefault();
337
-
338
- // First drag: snapshot position and pin to viewport with position:fixed
339
- if (!box.dataset.anDragPinned) {
340
- const r = box.getBoundingClientRect();
341
- box.style.position = 'fixed';
342
- box.style.margin = '0';
343
- box.style.left = `${r.left}px`;
344
- box.style.top = `${r.top}px`;
345
- box.dataset.anDragPinned = '1';
346
- }
347
-
348
- const startX = e.clientX - Number.parseFloat(box.style.left);
349
- const startY = e.clientY - Number.parseFloat(box.style.top);
350
-
351
- handle.style.cursor = 'grabbing';
352
-
353
- const onMove = (ev) => {
354
- const bw = box.offsetWidth;
355
- const bh = box.offsetHeight;
356
- box.style.left = `${Math.max(0, Math.min(ev.clientX - startX, globalThis.innerWidth - bw))}px`;
357
- box.style.top = `${Math.max(0, Math.min(ev.clientY - startY, globalThis.innerHeight - bh))}px`;
358
- };
359
-
360
- const onUp = () => {
361
- handle.style.cursor = 'grab';
362
- document.removeEventListener('mousemove', onMove);
363
- document.removeEventListener('mouseup', onUp);
364
- };
365
-
366
- document.addEventListener('mousemove', onMove);
367
- document.addEventListener('mouseup', onUp);
368
- };
369
-
370
- handle.addEventListener('mousedown', onMousedown);
371
- return () => handle.removeEventListener('mousedown', onMousedown);
372
- }
@@ -1,38 +0,0 @@
1
- /**
2
- * env.js - Environment / browser detection
3
- * Inspired by Summernote's env.js
4
- *
5
- * Every field is a lazy getter rather than a value computed at module load.
6
- * This module is re-exported from the package entry point, so reading
7
- * `navigator` eagerly meant that merely `import`ing autumnnote threw
8
- * `ReferenceError: navigator is not defined` under SSR on any runtime without
9
- * a global `navigator` — including Node 20, which package.json still supports.
10
- * Nothing inside the library reads these fields, so the crash happened before
11
- * an editor was ever created.
12
- */
13
-
14
- /** @returns {string} the current user agent, or '' when there is no navigator (SSR). */
15
- function ua() {
16
- return globalThis.navigator?.userAgent ?? '';
17
- }
18
-
19
- export const env = {
20
- /** True if browser is Chrome (excludes Edge, whose UA also contains "Chrome/") */
21
- get isChrome() { return /Chrome\//.test(ua()) && !/Edg\//.test(ua()); },
22
- /** True if browser is Firefox */
23
- get isFF() { return /Firefox\//.test(ua()); },
24
- /** True if browser is Safari (not Chrome) */
25
- get isSafari() { return /^((?!chrome|android).)*safari/i.test(ua()); },
26
- /** True if browser is Edge (Chromium) */
27
- get isEdge() { return /Edg\//.test(ua()); },
28
- /** True if running on macOS */
29
- get isMac() { return /Macintosh/.test(ua()); },
30
- /** True if running on mobile */
31
- get isMobile() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua()); },
32
- /** True if touch is supported */
33
- get isTouch() {
34
- return 'ontouchstart' in globalThis || (globalThis.navigator?.maxTouchPoints ?? 0) > 0;
35
- },
36
- /** Modifier key name depending on platform */
37
- get modifierKey() { return /Macintosh/.test(ua()) ? 'metaKey' : 'ctrlKey'; },
38
- };
@@ -1,66 +0,0 @@
1
- /**
2
- * key.js - Keyboard key code constants
3
- * Inspired by Summernote's key.js
4
- */
5
-
6
- export const key = {
7
- BACKSPACE: 'Backspace',
8
- TAB: 'Tab',
9
- ENTER: 'Enter',
10
- ESCAPE: 'Escape',
11
- SPACE: ' ',
12
- PAGE_UP: 'PageUp',
13
- PAGE_DOWN: 'PageDown',
14
- END: 'End',
15
- HOME: 'Home',
16
- LEFT: 'ArrowLeft',
17
- UP: 'ArrowUp',
18
- RIGHT: 'ArrowRight',
19
- DOWN: 'ArrowDown',
20
- DELETE: 'Delete',
21
- // Numbers
22
- NUM0: '0',
23
- NUM1: '1',
24
- NUM2: '2',
25
- NUM3: '3',
26
- NUM4: '4',
27
- NUM5: '5',
28
- NUM6: '6',
29
- NUM7: '7',
30
- NUM8: '8',
31
- // Letters
32
- B: 'b',
33
- E: 'e',
34
- I: 'i',
35
- J: 'j',
36
- K: 'k',
37
- L: 'l',
38
- R: 'r',
39
- S: 's',
40
- U: 'u',
41
- V: 'v',
42
- Y: 'y',
43
- Z: 'z',
44
- SLASH: '/',
45
- PERIOD: '.',
46
- };
47
-
48
- /**
49
- * Returns true if the event matches the given key
50
- * @param {KeyboardEvent} event
51
- * @param {string} keyName - one of key.*
52
- * @returns {boolean}
53
- */
54
- export function isKey(event, keyName) {
55
- return event.key === keyName || event.key === keyName.toUpperCase();
56
- }
57
-
58
- /**
59
- * Returns true if the event is a modifier key press (Ctrl/Cmd + key)
60
- * @param {KeyboardEvent} event
61
- * @param {string} keyName
62
- * @returns {boolean}
63
- */
64
- export function isModifier(event, keyName) {
65
- return (event.ctrlKey || event.metaKey) && isKey(event, keyName);
66
- }