@codeauradev/ngx-wind-editor 0.0.0-stage → 1.0.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.
@@ -0,0 +1,2596 @@
1
+ import * as Y from "yjs";
2
+ import * as i0 from "@angular/core";
3
+ import { ChangeDetectionStrategy, Component, DestroyRef, ViewEncapsulation, afterNextRender, computed, effect, inject, input, model, signal, viewChild } from "@angular/core";
4
+ const SLASH_ITEMS = [
5
+ {
6
+ id: "paragraph",
7
+ label: "Text",
8
+ hint: "Plain paragraph",
9
+ keys: "p text paragraph"
10
+ },
11
+ {
12
+ id: "h1",
13
+ label: "Heading 1",
14
+ hint: "Large title",
15
+ keys: "h1 heading title"
16
+ },
17
+ {
18
+ id: "h2",
19
+ label: "Heading 2",
20
+ hint: "Section",
21
+ keys: "h2 heading"
22
+ },
23
+ {
24
+ id: "h3",
25
+ label: "Heading 3",
26
+ hint: "Subsection",
27
+ keys: "h3 heading"
28
+ },
29
+ {
30
+ id: "bullet",
31
+ label: "Bullet list",
32
+ hint: "Unordered",
33
+ keys: "ul list bullet"
34
+ },
35
+ {
36
+ id: "ordered",
37
+ label: "Numbered list",
38
+ hint: "Ordered",
39
+ keys: "ol list number"
40
+ },
41
+ {
42
+ id: "quote",
43
+ label: "Quote",
44
+ hint: "Blockquote",
45
+ keys: "quote blockquote"
46
+ },
47
+ {
48
+ id: "code",
49
+ label: "Code",
50
+ hint: "Monospace block",
51
+ keys: "code pre"
52
+ },
53
+ {
54
+ id: "rule",
55
+ label: "Divider",
56
+ hint: "Horizontal rule",
57
+ keys: "hr rule divider line"
58
+ },
59
+ {
60
+ id: "image",
61
+ label: "Image",
62
+ hint: "From a URL",
63
+ keys: "img image picture"
64
+ }
65
+ ];
66
+ function filterSlash(query) {
67
+ const needle = query.trim().toLowerCase();
68
+ if (!needle) return [...SLASH_ITEMS];
69
+ return SLASH_ITEMS.filter((item) => `${item.label} ${item.keys}`.toLowerCase().includes(needle));
70
+ }
71
+ const MARK_KEYS = [
72
+ "bold",
73
+ "italic",
74
+ "underline",
75
+ "strike",
76
+ "code",
77
+ "href"
78
+ ];
79
+ function packAttrs(attrs) {
80
+ if (!attrs) return void 0;
81
+ const out = {};
82
+ if (attrs.bold) out.bold = true;
83
+ if (attrs.italic) out.italic = true;
84
+ if (attrs.underline) out.underline = true;
85
+ if (attrs.strike) out.strike = true;
86
+ if (attrs.code) out.code = true;
87
+ if (typeof attrs.href === "string" && attrs.href.length > 0) out.href = attrs.href;
88
+ return Object.keys(out).length > 0 ? out : void 0;
89
+ }
90
+ function attrsEqual(a, b) {
91
+ const left = packAttrs(a) ?? {};
92
+ const right = packAttrs(b) ?? {};
93
+ return MARK_KEYS.every((key) => left[key] === right[key]);
94
+ }
95
+ function mergeDelta(delta) {
96
+ const out = [];
97
+ for (const op of delta) {
98
+ if (!op.insert) continue;
99
+ const prev = out[out.length - 1];
100
+ if (prev && attrsEqual(prev.attributes, op.attributes)) prev.insert += op.insert;
101
+ else out.push({
102
+ insert: op.insert,
103
+ attributes: packAttrs(op.attributes)
104
+ });
105
+ }
106
+ return out;
107
+ }
108
+ function deltaLength(delta) {
109
+ return delta.reduce((sum, op) => sum + op.insert.length, 0);
110
+ }
111
+ function deltaToText(delta) {
112
+ return delta.map((op) => op.insert).join("");
113
+ }
114
+ function sliceDelta(delta, start, end) {
115
+ const out = [];
116
+ let pos = 0;
117
+ for (const op of delta) {
118
+ const len = op.insert.length;
119
+ const from = Math.max(start, pos);
120
+ const to = Math.min(end, pos + len);
121
+ if (to > from) out.push({
122
+ insert: op.insert.slice(from - pos, to - pos),
123
+ attributes: packAttrs(op.attributes)
124
+ });
125
+ pos += len;
126
+ }
127
+ return out;
128
+ }
129
+ function attrsAt(delta, index) {
130
+ if (index <= 0 || delta.length === 0) return {};
131
+ let pos = 0;
132
+ for (const op of delta) {
133
+ const len = op.insert.length;
134
+ if (index <= pos + len) return { ...packAttrs(op.attributes) ?? {} };
135
+ pos += len;
136
+ }
137
+ return { ...packAttrs(delta[delta.length - 1]?.attributes) ?? {} };
138
+ }
139
+ function commonEdges(prev, next) {
140
+ let start = 0;
141
+ const limit = Math.min(prev.length, next.length);
142
+ while (start < limit && prev[start] === next[start]) start += 1;
143
+ let prevEnd = prev.length;
144
+ let nextEnd = next.length;
145
+ while (prevEnd > start && nextEnd > start && prev[prevEnd - 1] === next[nextEnd - 1]) {
146
+ prevEnd -= 1;
147
+ nextEnd -= 1;
148
+ }
149
+ return {
150
+ start,
151
+ prevEnd,
152
+ nextEnd
153
+ };
154
+ }
155
+ function graphemeSegmenter() {
156
+ const intl = Intl;
157
+ if (typeof intl.Segmenter !== "function") return null;
158
+ return new intl.Segmenter(void 0, { granularity: "grapheme" });
159
+ }
160
+ function graphemeBefore(text, offset) {
161
+ if (offset <= 0) return 0;
162
+ const segmenter = graphemeSegmenter();
163
+ if (!segmenter) return offset - 1;
164
+ let last = 0;
165
+ for (const part of segmenter.segment(text.slice(0, offset))) last = part.index;
166
+ return last;
167
+ }
168
+ function graphemeAfter(text, offset) {
169
+ if (offset >= text.length) return text.length;
170
+ const segmenter = graphemeSegmenter();
171
+ if (!segmenter) return offset + 1;
172
+ return offset + (segmenter.segment(text.slice(offset))[Symbol.iterator]().next().value?.segment.length ?? 1);
173
+ }
174
+ const VOID = /* @__PURE__ */ new Set([
175
+ "area",
176
+ "base",
177
+ "br",
178
+ "col",
179
+ "embed",
180
+ "hr",
181
+ "img",
182
+ "input",
183
+ "link",
184
+ "meta",
185
+ "source",
186
+ "track",
187
+ "wbr"
188
+ ]);
189
+ function safeUrl(value) {
190
+ const trimmed = value.trim();
191
+ if (!trimmed) return null;
192
+ if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("./") || trimmed.startsWith("../")) return trimmed;
193
+ try {
194
+ const url = new URL(trimmed, "https://example.com");
195
+ if (url.protocol === "http:" || url.protocol === "https:" || url.protocol === "mailto:" || url.protocol === "tel:") return trimmed;
196
+ } catch {
197
+ return null;
198
+ }
199
+ return null;
200
+ }
201
+ function escapeText(value) {
202
+ return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
203
+ }
204
+ function escapeAttr(value) {
205
+ return escapeText(value).replace(/"/g, "\"");
206
+ }
207
+ function decodeEntity(body) {
208
+ const named = {
209
+ amp: "&",
210
+ lt: "<",
211
+ gt: ">",
212
+ quot: "\"",
213
+ apos: "'",
214
+ nbsp: "\xA0"
215
+ };
216
+ if (named[body]) return named[body];
217
+ if (body.startsWith("#x") || body.startsWith("#X")) {
218
+ const code = Number.parseInt(body.slice(2), 16);
219
+ return Number.isFinite(code) ? String.fromCodePoint(code) : `&${body};`;
220
+ }
221
+ if (body.startsWith("#")) {
222
+ const code = Number.parseInt(body.slice(1), 10);
223
+ return Number.isFinite(code) ? String.fromCodePoint(code) : `&${body};`;
224
+ }
225
+ return `&${body};`;
226
+ }
227
+ function decode(value) {
228
+ return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (_, body) => decodeEntity(body));
229
+ }
230
+ function tokenize(html) {
231
+ const tokens = [];
232
+ let i = 0;
233
+ while (i < html.length) {
234
+ if (html[i] !== "<") {
235
+ const next = html.indexOf("<", i);
236
+ const raw = html.slice(i, next === -1 ? html.length : next);
237
+ tokens.push({
238
+ kind: "text",
239
+ value: decode(raw)
240
+ });
241
+ i = next === -1 ? html.length : next;
242
+ continue;
243
+ }
244
+ const next = html[i + 1];
245
+ if (!next || !/[a-zA-Z/!?]/.test(next)) {
246
+ tokens.push({
247
+ kind: "text",
248
+ value: "<"
249
+ });
250
+ i += 1;
251
+ continue;
252
+ }
253
+ if (html.startsWith("<!--", i)) {
254
+ const end = html.indexOf("-->", i + 4);
255
+ i = end === -1 ? html.length : end + 3;
256
+ continue;
257
+ }
258
+ const end = html.indexOf(">", i + 1);
259
+ if (end === -1) {
260
+ tokens.push({
261
+ kind: "text",
262
+ value: html.slice(i)
263
+ });
264
+ break;
265
+ }
266
+ const raw = html.slice(i + 1, end).trim();
267
+ i = end + 1;
268
+ if (!raw) continue;
269
+ if (raw.startsWith("/")) {
270
+ tokens.push({
271
+ kind: "close",
272
+ name: raw.slice(1).trim().split(/\s+/)[0]?.toLowerCase() ?? ""
273
+ });
274
+ continue;
275
+ }
276
+ if (raw.startsWith("!") || raw.startsWith("?")) continue;
277
+ const self = raw.endsWith("/");
278
+ const body = self ? raw.slice(0, -1).trim() : raw;
279
+ const name = body.split(/\s+/)[0]?.toLowerCase() ?? "";
280
+ const attrs = {};
281
+ const attrRe = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
282
+ let match;
283
+ const rest = body.slice(name.length);
284
+ while (match = attrRe.exec(rest)) {
285
+ const key = match[1]?.toLowerCase();
286
+ if (!key) continue;
287
+ attrs[key] = decode(match[2] ?? match[3] ?? match[4] ?? "");
288
+ }
289
+ tokens.push({
290
+ kind: "open",
291
+ name,
292
+ attrs
293
+ });
294
+ if (self || VOID.has(name)) tokens.push({
295
+ kind: "close",
296
+ name
297
+ });
298
+ }
299
+ return tokens;
300
+ }
301
+ function pushText(draft, value, attrs) {
302
+ if (!value) return;
303
+ draft.delta.push({
304
+ insert: value,
305
+ attributes: packAttrs(attrs)
306
+ });
307
+ }
308
+ function finish(blocks, draft) {
309
+ if (!draft) return null;
310
+ let delta = mergeDelta(draft.delta);
311
+ if (draft.type !== "code") {
312
+ const text = deltaToText(delta).replace(/\u00a0/g, " ");
313
+ if (text.trim() === "" && !text.includes("\n")) delta = [];
314
+ else if (delta.length === 1 && delta[0] && !delta[0].attributes) {
315
+ const trimmed = delta[0].insert.replace(/^[ \t]+|[ \t]+$/g, "");
316
+ delta = trimmed ? [{ insert: trimmed }] : [];
317
+ }
318
+ }
319
+ blocks.push({
320
+ id: "",
321
+ type: draft.type,
322
+ level: draft.type === "heading" ? draft.level : 0,
323
+ src: draft.src,
324
+ alt: draft.alt,
325
+ delta: draft.type === "rule" || draft.type === "image" ? [] : delta
326
+ });
327
+ return null;
328
+ }
329
+ function parseHtml(html) {
330
+ const tokens = tokenize(html);
331
+ const blocks = [];
332
+ const marks = [];
333
+ const lists = [];
334
+ let quote = 0;
335
+ let pre = 0;
336
+ let ignore = 0;
337
+ let draft = null;
338
+ const attrs = () => marks.reduce((acc, mark) => ({
339
+ ...acc,
340
+ ...mark.attrs
341
+ }), {});
342
+ const popTag = (tag) => {
343
+ for (let index = marks.length - 1; index >= 0; index -= 1) if (marks[index]?.tag === tag) {
344
+ marks.splice(index, 1);
345
+ return;
346
+ }
347
+ };
348
+ const start = (type, extra) => {
349
+ draft = finish(blocks, draft);
350
+ draft = {
351
+ type,
352
+ level: extra?.level ?? (type === "heading" ? 1 : 0),
353
+ src: extra?.src ?? "",
354
+ alt: extra?.alt ?? "",
355
+ delta: []
356
+ };
357
+ };
358
+ for (const token of tokens) {
359
+ if (token.kind === "text") {
360
+ if (ignore) continue;
361
+ if (pre) {
362
+ if (!draft) start("code");
363
+ pushText(draft, token.value.replace(/^\n/, ""), {});
364
+ continue;
365
+ }
366
+ const collapsed = token.value.replace(/\u00a0/g, " ").replace(/[ \t\r\n]+/g, " ");
367
+ if (!draft) {
368
+ if (!collapsed.trim()) continue;
369
+ start(quote > 0 ? "blockquote" : lists.at(-1) ?? "paragraph");
370
+ }
371
+ if (!draft) continue;
372
+ pushText(draft, collapsed, attrs());
373
+ continue;
374
+ }
375
+ if (token.kind === "close") {
376
+ const name = token.name;
377
+ if (name === "script" || name === "style") ignore = Math.max(0, ignore - 1);
378
+ if (name === "strong" || name === "b") popTag(name);
379
+ if (name === "em" || name === "i") popTag(name);
380
+ if (name === "u") popTag(name);
381
+ if (name === "s" || name === "strike" || name === "del") popTag(name);
382
+ if (name === "code" && pre === 0) popTag("code");
383
+ if (name === "a") popTag("a");
384
+ if (name === "ul" || name === "ol") lists.pop();
385
+ if (name === "blockquote") quote = Math.max(0, quote - 1);
386
+ if (name === "pre") {
387
+ pre = Math.max(0, pre - 1);
388
+ draft = finish(blocks, draft);
389
+ }
390
+ if ([
391
+ "p",
392
+ "div",
393
+ "li",
394
+ "h1",
395
+ "h2",
396
+ "h3",
397
+ "h4",
398
+ "h5",
399
+ "h6",
400
+ "blockquote"
401
+ ].includes(name) && pre === 0) draft = finish(blocks, draft);
402
+ continue;
403
+ }
404
+ const name = token.name;
405
+ if (name === "script" || name === "style") {
406
+ ignore += 1;
407
+ continue;
408
+ }
409
+ if (ignore) continue;
410
+ if (name === "strong" || name === "b") marks.push({
411
+ tag: name,
412
+ attrs: { bold: true }
413
+ });
414
+ else if (name === "em" || name === "i") marks.push({
415
+ tag: name,
416
+ attrs: { italic: true }
417
+ });
418
+ else if (name === "u") marks.push({
419
+ tag: "u",
420
+ attrs: { underline: true }
421
+ });
422
+ else if (name === "s" || name === "strike" || name === "del") marks.push({
423
+ tag: name,
424
+ attrs: { strike: true }
425
+ });
426
+ else if (name === "code" && pre === 0) marks.push({
427
+ tag: "code",
428
+ attrs: { code: true }
429
+ });
430
+ else if (name === "a") {
431
+ const href = safeUrl(token.attrs["href"] ?? "");
432
+ if (href) marks.push({
433
+ tag: "a",
434
+ attrs: { href }
435
+ });
436
+ } else if (name === "br") {
437
+ if (!draft) start(quote > 0 ? "blockquote" : lists.at(-1) ?? "paragraph");
438
+ if (draft && deltaToText(draft.delta).length > 0) pushText(draft, "\n", attrs());
439
+ } else if (name === "hr") {
440
+ draft = finish(blocks, draft);
441
+ blocks.push({
442
+ id: "",
443
+ type: "rule",
444
+ level: 0,
445
+ src: "",
446
+ alt: "",
447
+ delta: []
448
+ });
449
+ } else if (name === "img") {
450
+ const src = safeUrl(token.attrs["src"] ?? "");
451
+ if (src) {
452
+ draft = finish(blocks, draft);
453
+ blocks.push({
454
+ id: "",
455
+ type: "image",
456
+ level: 0,
457
+ src,
458
+ alt: token.attrs["alt"] ?? "",
459
+ delta: []
460
+ });
461
+ }
462
+ } else if (name === "ul") {
463
+ draft = finish(blocks, draft);
464
+ lists.push("bullet");
465
+ } else if (name === "ol") {
466
+ draft = finish(blocks, draft);
467
+ lists.push("ordered");
468
+ } else if (name === "blockquote") {
469
+ draft = finish(blocks, draft);
470
+ quote += 1;
471
+ } else if (name === "pre") {
472
+ draft = finish(blocks, draft);
473
+ pre += 1;
474
+ start("code");
475
+ } else if (/^h[1-6]$/.test(name)) start("heading", { level: Math.min(3, Number(name[1])) });
476
+ else if (name === "li") start(lists.at(-1) ?? "bullet");
477
+ else if (name === "p" || name === "div") {
478
+ if (pre > 0) continue;
479
+ if (draft && deltaToText(draft.delta).length > 0) draft = finish(blocks, draft);
480
+ if (!draft) start(quote > 0 ? "blockquote" : lists.at(-1) ?? "paragraph");
481
+ }
482
+ }
483
+ finish(blocks, draft);
484
+ return blocks;
485
+ }
486
+ function inlineHtml(delta, pre) {
487
+ let html = "";
488
+ for (const op of mergeDelta(delta)) {
489
+ let piece = escapeText(op.insert);
490
+ if (!pre) piece = piece.replace(/\n/g, "<br>");
491
+ const attrs = op.attributes ?? {};
492
+ if (attrs.code) piece = `<code>${piece}</code>`;
493
+ if (attrs.strike) piece = `<s>${piece}</s>`;
494
+ if (attrs.underline) piece = `<u>${piece}</u>`;
495
+ if (attrs.italic) piece = `<em>${piece}</em>`;
496
+ if (attrs.bold) piece = `<strong>${piece}</strong>`;
497
+ if (attrs.href) piece = `<a href="${escapeAttr(attrs.href)}">${piece}</a>`;
498
+ html += piece;
499
+ }
500
+ return html;
501
+ }
502
+ function blockHtml(block) {
503
+ if (block.type === "rule") return "<hr>";
504
+ if (block.type === "image") {
505
+ const alt = block.alt ? ` alt="${escapeAttr(block.alt)}"` : "";
506
+ return `<img src="${escapeAttr(block.src)}"${alt}>`;
507
+ }
508
+ if (block.type === "code") return `<pre><code>${inlineHtml(block.delta, true)}</code></pre>`;
509
+ const inner = inlineHtml(block.delta, false);
510
+ if (block.type === "heading") return `<h${Math.min(3, Math.max(1, block.level || 1))}>${inner}</h${Math.min(3, Math.max(1, block.level || 1))}>`;
511
+ if (block.type === "blockquote") return `<blockquote>${inner}</blockquote>`;
512
+ if (block.type === "bullet" || block.type === "ordered") return `<li>${inner}</li>`;
513
+ return `<p>${inner}</p>`;
514
+ }
515
+ function serializeBlocks(blocks) {
516
+ let html = "";
517
+ let i = 0;
518
+ while (i < blocks.length) {
519
+ const block = blocks[i];
520
+ if (!block) break;
521
+ if (block.type === "bullet" || block.type === "ordered") {
522
+ const tag = block.type === "bullet" ? "ul" : "ol";
523
+ html += `<${tag}>`;
524
+ while (i < blocks.length && blocks[i]?.type === block.type) {
525
+ html += blockHtml(blocks[i]);
526
+ i += 1;
527
+ }
528
+ html += `</${tag}>`;
529
+ continue;
530
+ }
531
+ html += blockHtml(block);
532
+ i += 1;
533
+ }
534
+ return html;
535
+ }
536
+ function plainText(blocks) {
537
+ return blocks.map((block) => {
538
+ if (block.type === "rule") return "";
539
+ if (block.type === "image") return block.alt || block.src;
540
+ return deltaToText(block.delta);
541
+ }).filter((line, index, all) => line.length > 0 || all.length === 1).join("\n");
542
+ }
543
+ function countWords(text) {
544
+ const trimmed = text.trim();
545
+ if (!trimmed) return 0;
546
+ return trimmed.split(/\s+/).length;
547
+ }
548
+ const BLOCK_TYPES = [
549
+ "paragraph",
550
+ "heading",
551
+ "blockquote",
552
+ "code",
553
+ "bullet",
554
+ "ordered",
555
+ "rule",
556
+ "image"
557
+ ];
558
+ function isBlockType(value) {
559
+ return typeof value === "string" && BLOCK_TYPES.includes(value);
560
+ }
561
+ const BLOCKS = "blocks";
562
+ let idSeq = 0;
563
+ function nextId() {
564
+ idSeq += 1;
565
+ const rand = Math.random().toString(36).slice(2, 8);
566
+ return `b${idSeq.toString(36)}${rand}`;
567
+ }
568
+ function blocksOf(doc) {
569
+ return doc.getArray(BLOCKS);
570
+ }
571
+ function makeBlock(type, fields) {
572
+ const map = new Y.Map();
573
+ map.set("id", nextId());
574
+ map.set("type", type);
575
+ if (type === "heading") map.set("level", fields?.level ?? 1);
576
+ if (type === "image") {
577
+ map.set("src", fields?.src ?? "");
578
+ map.set("alt", fields?.alt ?? "");
579
+ }
580
+ if (type !== "rule" && type !== "image") {
581
+ const text = new Y.Text();
582
+ if (fields?.delta?.length) writeDelta(text, fields.delta, 0);
583
+ map.set("text", text);
584
+ }
585
+ return map;
586
+ }
587
+ function writeDelta(text, delta, at) {
588
+ let index = at;
589
+ for (const op of delta) {
590
+ if (!op.insert) continue;
591
+ text.insert(index, op.insert, packAttrs(op.attributes));
592
+ index += op.insert.length;
593
+ }
594
+ }
595
+ function readDelta(text) {
596
+ const ops = [];
597
+ for (const op of text.toDelta()) {
598
+ if (typeof op.insert !== "string" || op.insert.length === 0) continue;
599
+ ops.push({
600
+ insert: op.insert,
601
+ attributes: packAttrs(op.attributes)
602
+ });
603
+ }
604
+ return mergeDelta(ops);
605
+ }
606
+ function readMap(map, index) {
607
+ const rawType = map.get("type");
608
+ const type = isBlockType(rawType) ? rawType : "paragraph";
609
+ const text = map.get("text");
610
+ return {
611
+ map,
612
+ index,
613
+ id: String(map.get("id") ?? ""),
614
+ type,
615
+ level: Number(map.get("level") ?? 1) || 1,
616
+ text: text instanceof Y.Text ? text : null
617
+ };
618
+ }
619
+ function readBlocks(doc) {
620
+ const blocks = blocksOf(doc);
621
+ const out = [];
622
+ for (let i = 0; i < blocks.length; i += 1) {
623
+ const map = blocks.get(i);
624
+ const loc = readMap(map, i);
625
+ out.push({
626
+ id: loc.id,
627
+ type: loc.type,
628
+ level: loc.type === "heading" ? loc.level : 0,
629
+ src: String(map.get("src") ?? ""),
630
+ alt: String(map.get("alt") ?? ""),
631
+ delta: loc.text ? readDelta(loc.text) : []
632
+ });
633
+ }
634
+ return out;
635
+ }
636
+ function locate(doc, id) {
637
+ const blocks = blocksOf(doc);
638
+ for (let i = 0; i < blocks.length; i += 1) {
639
+ const loc = readMap(blocks.get(i), i);
640
+ if (loc.id === id) return loc;
641
+ }
642
+ return null;
643
+ }
644
+ function locateAt(doc, index) {
645
+ const blocks = blocksOf(doc);
646
+ if (index < 0 || index >= blocks.length) return null;
647
+ return readMap(blocks.get(index), index);
648
+ }
649
+ function cloneSelection(selection) {
650
+ return {
651
+ anchor: { ...selection.anchor },
652
+ head: { ...selection.head }
653
+ };
654
+ }
655
+ function collapsed(blockId, offset) {
656
+ return {
657
+ anchor: {
658
+ blockId,
659
+ offset
660
+ },
661
+ head: {
662
+ blockId,
663
+ offset
664
+ }
665
+ };
666
+ }
667
+ function boundsOf(doc, selection) {
668
+ const anchor = locate(doc, selection.anchor.blockId);
669
+ const head = locate(doc, selection.head.blockId);
670
+ if (!anchor || !head) return null;
671
+ const anchorFirst = anchor.index < head.index || anchor.index === head.index && selection.anchor.offset <= selection.head.offset;
672
+ return {
673
+ start: anchorFirst ? selection.anchor : selection.head,
674
+ end: anchorFirst ? selection.head : selection.anchor,
675
+ startIndex: anchorFirst ? anchor.index : head.index,
676
+ endIndex: anchorFirst ? head.index : anchor.index
677
+ };
678
+ }
679
+ function hasRange(doc, selection) {
680
+ const bounds = boundsOf(doc, selection);
681
+ if (!bounds) return false;
682
+ return bounds.start.blockId !== bounds.end.blockId || bounds.start.offset !== bounds.end.offset;
683
+ }
684
+ var WindSession = class {
685
+ constructor(doc) {
686
+ this.origin = {};
687
+ this.stored = null;
688
+ this.pendingBefore = null;
689
+ this.listeners = /* @__PURE__ */ new Set();
690
+ this.doc = doc;
691
+ if (blocksOf(doc).length === 0) doc.transact(() => {
692
+ if (blocksOf(doc).length === 0) blocksOf(doc).insert(0, [makeBlock("paragraph")]);
693
+ }, "seed");
694
+ const first = locateAt(doc, 0);
695
+ this.selection = collapsed(first?.id ?? "", 0);
696
+ this.undoManager = new Y.UndoManager(blocksOf(doc), {
697
+ trackedOrigins: /* @__PURE__ */ new Set([this.origin]),
698
+ captureTimeout: 500
699
+ });
700
+ this.undoManager.on("stack-item-added", (event) => {
701
+ const item = event.stackItem;
702
+ item.meta.set("before", this.pendingBefore);
703
+ item.meta.set("after", cloneSelection(this.selection));
704
+ });
705
+ this.undoManager.on("stack-item-popped", (event) => {
706
+ const item = event.stackItem;
707
+ const stored = event.type === "undo" ? item.meta.get("before") : item.meta.get("after");
708
+ if (isSelection(stored)) this.selection = cloneSelection(stored);
709
+ this.stored = null;
710
+ this.clampSelection();
711
+ this.emit(true);
712
+ });
713
+ blocksOf(doc).observeDeep((_events, transaction) => {
714
+ if (transaction.origin === this.origin) return;
715
+ this.clampSelection();
716
+ this.stored = null;
717
+ this.emit(true);
718
+ });
719
+ }
720
+ onChange(listener) {
721
+ this.listeners.add(listener);
722
+ return () => this.listeners.delete(listener);
723
+ }
724
+ get html() {
725
+ return serializeBlocks(readBlocks(this.doc));
726
+ }
727
+ get text() {
728
+ return plainText(readBlocks(this.doc));
729
+ }
730
+ get words() {
731
+ return countWords(this.text);
732
+ }
733
+ get canUndo() {
734
+ return this.undoManager.undoStack.length > 0;
735
+ }
736
+ get canRedo() {
737
+ return this.undoManager.redoStack.length > 0;
738
+ }
739
+ undo() {
740
+ this.undoManager.undo();
741
+ }
742
+ redo() {
743
+ this.undoManager.redo();
744
+ }
745
+ select(selection) {
746
+ this.selection = cloneSelection(selection);
747
+ this.stored = null;
748
+ this.clampSelection();
749
+ this.emit(false);
750
+ }
751
+ focusPoint(blockId, offset) {
752
+ this.select(collapsed(blockId, offset));
753
+ }
754
+ insertText(value) {
755
+ if (!value) return;
756
+ this.transact(() => {
757
+ if (hasRange(this.doc, this.selection)) this.deleteRange();
758
+ const loc = this.current();
759
+ if (!loc) return;
760
+ if (!loc.text) {
761
+ const created = makeBlock("paragraph");
762
+ created.get("text").insert(0, value, packAttrs(this.typingAttrs(null, 0)));
763
+ blocksOf(this.doc).insert(loc.index + 1, [created]);
764
+ this.selection = collapsed(String(created.get("id")), value.length);
765
+ return;
766
+ }
767
+ const attrs = this.typingAttrs(loc.text, this.selection.head.offset);
768
+ loc.text.insert(this.selection.head.offset, value, packAttrs(attrs));
769
+ this.selection = collapsed(loc.id, this.selection.head.offset + value.length);
770
+ });
771
+ }
772
+ deleteBackward() {
773
+ this.transact(() => {
774
+ if (hasRange(this.doc, this.selection)) {
775
+ this.deleteRange();
776
+ return;
777
+ }
778
+ const loc = this.current();
779
+ if (!loc) return;
780
+ if (!loc.text) {
781
+ blocksOf(this.doc).delete(loc.index, 1);
782
+ this.ensureBlock();
783
+ const next = locateAt(this.doc, Math.min(loc.index, blocksOf(this.doc).length - 1));
784
+ if (next) this.selection = collapsed(next.id, 0);
785
+ return;
786
+ }
787
+ const offset = this.selection.head.offset;
788
+ if (offset > 0) {
789
+ const from = graphemeBefore(loc.text.toString(), offset);
790
+ loc.text.delete(from, offset - from);
791
+ this.selection = collapsed(loc.id, from);
792
+ return;
793
+ }
794
+ if ((loc.type === "bullet" || loc.type === "ordered") && loc.text.length === 0) {
795
+ loc.map.set("type", "paragraph");
796
+ return;
797
+ }
798
+ if (loc.type !== "paragraph" && loc.type !== "bullet" && loc.type !== "ordered" && loc.index === 0) {
799
+ loc.map.set("type", "paragraph");
800
+ loc.map.delete("level");
801
+ return;
802
+ }
803
+ const prev = locateAt(this.doc, loc.index - 1);
804
+ if (!prev) return;
805
+ if (!prev.text) {
806
+ blocksOf(this.doc).delete(prev.index, 1);
807
+ this.selection = collapsed(loc.id, 0);
808
+ return;
809
+ }
810
+ if (!loc.text.length && (loc.type === "bullet" || loc.type === "ordered" || loc.type === "heading" || loc.type === "code" || loc.type === "blockquote")) {
811
+ loc.map.set("type", "paragraph");
812
+ return;
813
+ }
814
+ const join = prev.text.length;
815
+ writeDelta(prev.text, readDelta(loc.text), join);
816
+ blocksOf(this.doc).delete(loc.index, 1);
817
+ this.selection = collapsed(prev.id, join);
818
+ });
819
+ }
820
+ deleteForward() {
821
+ this.transact(() => {
822
+ if (hasRange(this.doc, this.selection)) {
823
+ this.deleteRange();
824
+ return;
825
+ }
826
+ const loc = this.current();
827
+ if (!loc?.text) {
828
+ if (loc) this.deleteBackward();
829
+ return;
830
+ }
831
+ const offset = this.selection.head.offset;
832
+ const text = loc.text.toString();
833
+ if (offset < text.length) {
834
+ const to = graphemeAfter(text, offset);
835
+ loc.text.delete(offset, to - offset);
836
+ this.selection = collapsed(loc.id, offset);
837
+ return;
838
+ }
839
+ const next = locateAt(this.doc, loc.index + 1);
840
+ if (!next) return;
841
+ if (!next.text) {
842
+ blocksOf(this.doc).delete(next.index, 1);
843
+ return;
844
+ }
845
+ writeDelta(loc.text, readDelta(next.text), loc.text.length);
846
+ blocksOf(this.doc).delete(next.index, 1);
847
+ this.selection = collapsed(loc.id, offset);
848
+ });
849
+ }
850
+ split(mode) {
851
+ this.transact(() => {
852
+ if (hasRange(this.doc, this.selection)) this.deleteRange();
853
+ const loc = this.current();
854
+ if (!loc) return;
855
+ if (!loc.text) {
856
+ const created = makeBlock("paragraph");
857
+ blocksOf(this.doc).insert(loc.index + 1, [created]);
858
+ this.selection = collapsed(String(created.get("id")), 0);
859
+ return;
860
+ }
861
+ if (mode === "hard" || mode === "enter" && loc.type === "code") {
862
+ const attrs = this.typingAttrs(loc.text, this.selection.head.offset);
863
+ loc.text.insert(this.selection.head.offset, "\n", packAttrs(attrs));
864
+ this.selection = collapsed(loc.id, this.selection.head.offset + 1);
865
+ return;
866
+ }
867
+ if (mode === "exit" || loc.type === "code" && mode === "enter") {
868
+ const created = makeBlock("paragraph");
869
+ blocksOf(this.doc).insert(loc.index + 1, [created]);
870
+ this.selection = collapsed(String(created.get("id")), 0);
871
+ return;
872
+ }
873
+ if ((loc.type === "bullet" || loc.type === "ordered") && loc.text.length === 0) {
874
+ loc.map.set("type", "paragraph");
875
+ this.selection = collapsed(loc.id, 0);
876
+ return;
877
+ }
878
+ const offset = this.selection.head.offset;
879
+ const delta = readDelta(loc.text);
880
+ const right = sliceDelta(delta, offset, deltaLength(delta));
881
+ if (offset < loc.text.length) loc.text.delete(offset, loc.text.length - offset);
882
+ const created = makeBlock(loc.type === "heading" ? "paragraph" : loc.type, {
883
+ level: loc.level,
884
+ delta: right
885
+ });
886
+ blocksOf(this.doc).insert(loc.index + 1, [created]);
887
+ this.selection = collapsed(String(created.get("id")), 0);
888
+ this.stored = null;
889
+ });
890
+ }
891
+ toggleMark(mark) {
892
+ this.transact(() => {
893
+ if (!hasRange(this.doc, this.selection)) {
894
+ const current = this.effectiveMarks();
895
+ this.stored = {
896
+ ...current,
897
+ [mark]: !current[mark]
898
+ };
899
+ if (!this.stored[mark]) delete this.stored[mark];
900
+ return;
901
+ }
902
+ const enable = !this.rangeHas(mark);
903
+ this.formatRange({ [mark]: enable ? true : null });
904
+ this.stored = null;
905
+ });
906
+ }
907
+ setLink(href) {
908
+ const safe = href ? href : null;
909
+ this.transact(() => {
910
+ if (!hasRange(this.doc, this.selection)) {
911
+ const current = this.effectiveMarks();
912
+ this.stored = { ...current };
913
+ if (safe) this.stored.href = safe;
914
+ else delete this.stored.href;
915
+ return;
916
+ }
917
+ this.formatRange({ href: safe });
918
+ this.stored = null;
919
+ });
920
+ }
921
+ clearMarks() {
922
+ this.transact(() => {
923
+ if (!hasRange(this.doc, this.selection)) {
924
+ this.stored = {};
925
+ return;
926
+ }
927
+ this.formatRange({
928
+ bold: null,
929
+ italic: null,
930
+ underline: null,
931
+ strike: null,
932
+ code: null,
933
+ href: null
934
+ });
935
+ this.stored = null;
936
+ });
937
+ }
938
+ setBlockType(type, level = 1) {
939
+ this.transact(() => {
940
+ for (const loc of this.selectedBlocks()) {
941
+ if (!loc.text && type !== "rule" && type !== "image") {
942
+ const text = new Y.Text();
943
+ loc.map.set("text", text);
944
+ }
945
+ loc.map.set("type", type);
946
+ if (type === "heading") loc.map.set("level", level);
947
+ else loc.map.delete("level");
948
+ if (type === "rule" || type === "image") loc.map.delete("text");
949
+ }
950
+ });
951
+ }
952
+ insertDivider() {
953
+ this.transact(() => {
954
+ if (hasRange(this.doc, this.selection)) this.deleteRange();
955
+ const loc = this.current();
956
+ if (!loc) return;
957
+ const empty = !!loc.text && loc.text.length === 0;
958
+ const rule = makeBlock("rule");
959
+ const para = makeBlock("paragraph");
960
+ if (empty) {
961
+ blocksOf(this.doc).delete(loc.index, 1);
962
+ blocksOf(this.doc).insert(loc.index, [rule, para]);
963
+ this.selection = collapsed(String(para.get("id")), 0);
964
+ return;
965
+ }
966
+ blocksOf(this.doc).insert(loc.index + 1, [rule, para]);
967
+ this.selection = collapsed(String(para.get("id")), 0);
968
+ });
969
+ }
970
+ insertImage(src, alt = "") {
971
+ this.transact(() => {
972
+ this.consumeSlash();
973
+ const loc = this.current();
974
+ if (!loc) return;
975
+ const image = makeBlock("image", {
976
+ src,
977
+ alt
978
+ });
979
+ const para = makeBlock("paragraph");
980
+ if (!!loc.text && loc.text.length === 0) {
981
+ blocksOf(this.doc).delete(loc.index, 1);
982
+ blocksOf(this.doc).insert(loc.index, [image, para]);
983
+ } else blocksOf(this.doc).insert(loc.index + 1, [image, para]);
984
+ this.selection = collapsed(String(para.get("id")), 0);
985
+ });
986
+ }
987
+ applySlash(choice) {
988
+ if (choice === "image") {
989
+ this.transact(() => this.consumeSlash());
990
+ return "image";
991
+ }
992
+ this.transact(() => {
993
+ this.consumeSlash();
994
+ if (choice === "rule") {
995
+ const loc = this.current();
996
+ if (loc?.text && loc.text.length === 0) {
997
+ const rule = makeBlock("rule");
998
+ const para = makeBlock("paragraph");
999
+ blocksOf(this.doc).delete(loc.index, 1);
1000
+ blocksOf(this.doc).insert(loc.index, [rule, para]);
1001
+ this.selection = collapsed(String(para.get("id")), 0);
1002
+ }
1003
+ return;
1004
+ }
1005
+ const type = choice === "h1" || choice === "h2" || choice === "h3" ? "heading" : choice === "quote" ? "blockquote" : choice === "paragraph" ? "paragraph" : choice;
1006
+ const level = choice === "h1" ? 1 : choice === "h2" ? 2 : choice === "h3" ? 3 : 1;
1007
+ const loc = this.current();
1008
+ if (!loc) return;
1009
+ loc.map.set("type", type);
1010
+ if (type === "heading") loc.map.set("level", level);
1011
+ else loc.map.delete("level");
1012
+ });
1013
+ return "applied";
1014
+ }
1015
+ paste(html, plain) {
1016
+ const parsed = html?.trim() ? parseHtml(html) : [];
1017
+ if (parsed.length === 1 && parsed[0] && parsed[0].type === "paragraph") {
1018
+ this.transact(() => {
1019
+ if (hasRange(this.doc, this.selection)) this.deleteRange();
1020
+ const loc = this.ensureText();
1021
+ if (!loc?.text) return;
1022
+ writeDelta(loc.text, parsed[0].delta, this.selection.head.offset);
1023
+ this.selection = collapsed(loc.id, this.selection.head.offset + deltaLength(parsed[0].delta));
1024
+ });
1025
+ return;
1026
+ }
1027
+ if (parsed.length > 0) {
1028
+ this.insertParsed(parsed);
1029
+ return;
1030
+ }
1031
+ if (!plain) return;
1032
+ const lines = plain.replace(/\r\n/g, "\n").split("\n");
1033
+ this.transact(() => {
1034
+ if (hasRange(this.doc, this.selection)) this.deleteRange();
1035
+ const loc = this.ensureText();
1036
+ if (!loc?.text) return;
1037
+ loc.text.insert(this.selection.head.offset, lines[0] ?? "", packAttrs(this.typingAttrs(loc.text, this.selection.head.offset)));
1038
+ let offset = this.selection.head.offset + (lines[0]?.length ?? 0);
1039
+ this.selection = collapsed(loc.id, offset);
1040
+ if (lines.length === 1) return;
1041
+ const rest = lines.slice(1).map((line) => makeBlock("paragraph", { delta: line ? [{ insert: line }] : [] }));
1042
+ const right = sliceDelta(readDelta(loc.text), offset, loc.text.length);
1043
+ if (offset < loc.text.length) loc.text.delete(offset, loc.text.length - offset);
1044
+ const tail = makeBlock(loc.type === "heading" ? "paragraph" : loc.type, {
1045
+ level: loc.level,
1046
+ delta: right
1047
+ });
1048
+ blocksOf(this.doc).insert(loc.index + 1, [...rest, tail]);
1049
+ const last = rest[rest.length - 1];
1050
+ this.selection = collapsed(String(last?.get("id") ?? loc.id), deltaLength(last ? readDelta(last.get("text")) : []));
1051
+ });
1052
+ }
1053
+ replaceHtml(html) {
1054
+ const parsed = parseHtml(html);
1055
+ const blocks = parsed.length > 0 ? parsed : [{
1056
+ id: "",
1057
+ type: "paragraph",
1058
+ level: 0,
1059
+ src: "",
1060
+ alt: "",
1061
+ delta: []
1062
+ }];
1063
+ this.doc.transact(() => {
1064
+ const list = blocksOf(this.doc);
1065
+ if (list.length > 0) list.delete(0, list.length);
1066
+ list.insert(0, blocks.map((block) => makeBlock(block.type, block)));
1067
+ }, this.origin);
1068
+ this.undoManager.clear();
1069
+ const first = locateAt(this.doc, 0);
1070
+ this.selection = collapsed(first?.id ?? "", 0);
1071
+ this.stored = null;
1072
+ this.emit(true);
1073
+ }
1074
+ reconcileText(blockId, next) {
1075
+ const loc = locate(this.doc, blockId);
1076
+ if (!loc?.text) return;
1077
+ const prev = loc.text.toString();
1078
+ if (prev === next) return;
1079
+ const edges = commonEdges(prev, next);
1080
+ const attrs = attrsAt(readDelta(loc.text), edges.start);
1081
+ this.transact(() => {
1082
+ const again = locate(this.doc, blockId);
1083
+ if (!again?.text) return;
1084
+ if (edges.prevEnd > edges.start) again.text.delete(edges.start, edges.prevEnd - edges.start);
1085
+ const insert = next.slice(edges.start, edges.nextEnd);
1086
+ if (insert) again.text.insert(edges.start, insert, packAttrs(this.stored ?? attrs));
1087
+ this.selection = collapsed(blockId, edges.start + insert.length);
1088
+ });
1089
+ }
1090
+ snapshot() {
1091
+ const loc = this.current();
1092
+ const marks = this.toolbarMarks();
1093
+ return {
1094
+ blockType: loc?.type ?? "paragraph",
1095
+ level: loc?.type === "heading" ? loc.level : 0,
1096
+ marks,
1097
+ href: marks.href ?? null,
1098
+ slash: this.slashQuery(),
1099
+ canUndo: this.canUndo,
1100
+ canRedo: this.canRedo
1101
+ };
1102
+ }
1103
+ dismissSlash() {
1104
+ this.transact(() => this.consumeSlash());
1105
+ }
1106
+ slashQuery() {
1107
+ if (hasRange(this.doc, this.selection)) return null;
1108
+ const loc = this.current();
1109
+ if (!loc?.text) return null;
1110
+ const text = loc.text.toString();
1111
+ if (!/^\/[a-zA-Z0-9]*$/.test(text)) return null;
1112
+ if (this.selection.head.offset !== text.length) return null;
1113
+ return text.slice(1);
1114
+ }
1115
+ consumeSlash() {
1116
+ const loc = this.current();
1117
+ if (!loc?.text) return;
1118
+ const text = loc.text.toString();
1119
+ if (/^\/[a-zA-Z0-9]*$/.test(text)) {
1120
+ loc.text.delete(0, text.length);
1121
+ this.selection = collapsed(loc.id, 0);
1122
+ }
1123
+ }
1124
+ typingAttrs(text, offset) {
1125
+ if (this.stored) return { ...this.stored };
1126
+ if (!text) return {};
1127
+ return attrsAt(readDelta(text), offset);
1128
+ }
1129
+ effectiveMarks() {
1130
+ const loc = this.current();
1131
+ return this.typingAttrs(loc?.text ?? null, this.selection.head.offset);
1132
+ }
1133
+ toolbarMarks() {
1134
+ if (!hasRange(this.doc, this.selection)) return this.effectiveMarks();
1135
+ const marks = {
1136
+ bold: true,
1137
+ italic: true,
1138
+ underline: true,
1139
+ strike: true,
1140
+ code: true
1141
+ };
1142
+ let href;
1143
+ let any = false;
1144
+ for (const slice of this.iterRange()) {
1145
+ any = true;
1146
+ for (const op of slice.delta) {
1147
+ const attrs = op.attributes ?? {};
1148
+ if (!attrs.bold) marks.bold = false;
1149
+ if (!attrs.italic) marks.italic = false;
1150
+ if (!attrs.underline) marks.underline = false;
1151
+ if (!attrs.strike) marks.strike = false;
1152
+ if (!attrs.code) marks.code = false;
1153
+ const link = attrs.href ?? null;
1154
+ if (href === void 0) href = link;
1155
+ else if (href !== link) href = null;
1156
+ }
1157
+ }
1158
+ if (!any) return {};
1159
+ if (href) marks.href = href;
1160
+ return marks;
1161
+ }
1162
+ rangeHas(mark) {
1163
+ let any = false;
1164
+ for (const slice of this.iterRange()) for (const op of slice.delta) {
1165
+ any = true;
1166
+ if (!op.attributes?.[mark]) return false;
1167
+ }
1168
+ return any;
1169
+ }
1170
+ formatRange(attrs) {
1171
+ for (const slice of this.iterRange()) {
1172
+ if (slice.delta.length === 0) continue;
1173
+ const loc = locate(this.doc, slice.id);
1174
+ if (!loc?.text) continue;
1175
+ const length = deltaLength(slice.delta);
1176
+ if (length > 0) loc.text.format(slice.from, length, attrs);
1177
+ }
1178
+ }
1179
+ iterRange() {
1180
+ const bounds = boundsOf(this.doc, this.selection);
1181
+ if (!bounds) return [];
1182
+ const out = [];
1183
+ for (let index = bounds.startIndex; index <= bounds.endIndex; index += 1) {
1184
+ const loc = locateAt(this.doc, index);
1185
+ if (!loc?.text) continue;
1186
+ const delta = readDelta(loc.text);
1187
+ const from = index === bounds.startIndex ? bounds.start.offset : 0;
1188
+ const to = index === bounds.endIndex ? bounds.end.offset : loc.text.length;
1189
+ out.push({
1190
+ id: loc.id,
1191
+ from,
1192
+ delta: sliceDelta(delta, from, to)
1193
+ });
1194
+ }
1195
+ return out;
1196
+ }
1197
+ deleteRange() {
1198
+ const bounds = boundsOf(this.doc, this.selection);
1199
+ if (!bounds) return;
1200
+ if (bounds.startIndex === bounds.endIndex) {
1201
+ const loc = locateAt(this.doc, bounds.startIndex);
1202
+ if (!loc) return;
1203
+ if (!loc.text) {
1204
+ blocksOf(this.doc).delete(loc.index, 1);
1205
+ this.ensureBlock();
1206
+ const next = locateAt(this.doc, Math.min(loc.index, blocksOf(this.doc).length - 1));
1207
+ if (next) this.selection = collapsed(next.id, 0);
1208
+ return;
1209
+ }
1210
+ loc.text.delete(bounds.start.offset, bounds.end.offset - bounds.start.offset);
1211
+ this.selection = collapsed(loc.id, bounds.start.offset);
1212
+ return;
1213
+ }
1214
+ const start = locateAt(this.doc, bounds.startIndex);
1215
+ const end = locateAt(this.doc, bounds.endIndex);
1216
+ if (!start || !end) return;
1217
+ const endSurvives = !!end.text && bounds.end.offset < end.text.length;
1218
+ if (end.text && bounds.end.offset > 0) end.text.delete(0, Math.min(bounds.end.offset, end.text.length));
1219
+ const startSurvives = !!start.text;
1220
+ if (start.text && bounds.start.offset < start.text.length) start.text.delete(bounds.start.offset, start.text.length - bounds.start.offset);
1221
+ const from = startSurvives ? start.index + 1 : start.index;
1222
+ const to = endSurvives ? end.index - 1 : end.index;
1223
+ if (to >= from) blocksOf(this.doc).delete(from, to - from + 1);
1224
+ const startNow = locate(this.doc, start.id);
1225
+ const endNow = locate(this.doc, end.id);
1226
+ if (startNow?.text && endNow?.text && startNow.id !== endNow.id) {
1227
+ const join = startNow.text.length;
1228
+ writeDelta(startNow.text, readDelta(endNow.text), join);
1229
+ blocksOf(this.doc).delete(endNow.index, 1);
1230
+ this.selection = collapsed(startNow.id, join);
1231
+ return;
1232
+ }
1233
+ this.ensureBlock();
1234
+ if (startNow) this.selection = collapsed(startNow.id, Math.min(bounds.start.offset, startNow.text?.length ?? 0));
1235
+ else {
1236
+ const fallback = locateAt(this.doc, Math.min(from, blocksOf(this.doc).length - 1));
1237
+ if (fallback) this.selection = collapsed(fallback.id, 0);
1238
+ }
1239
+ }
1240
+ insertParsed(parsed) {
1241
+ this.transact(() => {
1242
+ if (hasRange(this.doc, this.selection)) this.deleteRange();
1243
+ const loc = this.current() ?? locateAt(this.doc, 0);
1244
+ if (!loc) return;
1245
+ const maps = parsed.map((block) => makeBlock(block.type, block));
1246
+ if (loc.text && loc.text.length === 0) {
1247
+ blocksOf(this.doc).delete(loc.index, 1);
1248
+ blocksOf(this.doc).insert(loc.index, maps);
1249
+ } else blocksOf(this.doc).insert(loc.index + 1, maps);
1250
+ const last = maps[maps.length - 1];
1251
+ const text = last?.get("text");
1252
+ const offset = text instanceof Y.Text ? text.length : 0;
1253
+ this.selection = collapsed(String(last?.get("id") ?? loc.id), offset);
1254
+ });
1255
+ }
1256
+ selectedBlocks() {
1257
+ const bounds = boundsOf(this.doc, this.selection);
1258
+ if (!bounds) return [];
1259
+ const out = [];
1260
+ for (let index = bounds.startIndex; index <= bounds.endIndex; index += 1) {
1261
+ const loc = locateAt(this.doc, index);
1262
+ if (loc) out.push(loc);
1263
+ }
1264
+ return out;
1265
+ }
1266
+ ensureText() {
1267
+ const loc = this.current();
1268
+ if (!loc) return null;
1269
+ if (loc.text) return loc;
1270
+ const created = makeBlock("paragraph");
1271
+ blocksOf(this.doc).insert(loc.index + 1, [created]);
1272
+ const next = locate(this.doc, String(created.get("id")));
1273
+ if (next) this.selection = collapsed(next.id, 0);
1274
+ return next;
1275
+ }
1276
+ ensureBlock() {
1277
+ if (blocksOf(this.doc).length === 0) blocksOf(this.doc).insert(0, [makeBlock("paragraph")]);
1278
+ }
1279
+ current() {
1280
+ return locate(this.doc, this.selection.head.blockId) ?? locateAt(this.doc, 0);
1281
+ }
1282
+ clampSelection() {
1283
+ if (blocksOf(this.doc).length === 0) return;
1284
+ const clampPoint = (point) => {
1285
+ const loc = locate(this.doc, point.blockId);
1286
+ if (!loc) return {
1287
+ blockId: locateAt(this.doc, 0)?.id ?? "",
1288
+ offset: 0
1289
+ };
1290
+ const max = loc.text?.length ?? 0;
1291
+ return {
1292
+ blockId: loc.id,
1293
+ offset: Math.max(0, Math.min(point.offset, max))
1294
+ };
1295
+ };
1296
+ this.selection = {
1297
+ anchor: clampPoint(this.selection.anchor),
1298
+ head: clampPoint(this.selection.head)
1299
+ };
1300
+ }
1301
+ transact(fn) {
1302
+ this.pendingBefore = cloneSelection(this.selection);
1303
+ this.doc.transact(fn, this.origin);
1304
+ this.clampSelection();
1305
+ this.emit(true);
1306
+ }
1307
+ emit(structural) {
1308
+ for (const listener of this.listeners) listener(structural);
1309
+ }
1310
+ };
1311
+ function isSelection(value) {
1312
+ if (!value || typeof value !== "object") return false;
1313
+ const selection = value;
1314
+ return !!selection.anchor && !!selection.head && typeof selection.anchor.blockId === "string" && typeof selection.head.offset === "number";
1315
+ }
1316
+ function loadHtml(doc, html, origin = "seed") {
1317
+ const parsed = parseHtml(html);
1318
+ const blocks = parsed.length > 0 ? parsed : [{
1319
+ id: "",
1320
+ type: "paragraph",
1321
+ level: 0,
1322
+ src: "",
1323
+ alt: "",
1324
+ delta: []
1325
+ }];
1326
+ doc.transact(() => {
1327
+ const list = blocksOf(doc);
1328
+ if (list.length > 0) list.delete(0, list.length);
1329
+ list.insert(0, blocks.map((block) => makeBlock(block.type, block)));
1330
+ }, origin);
1331
+ }
1332
+ const WIND_TOOLS = [
1333
+ "paragraph",
1334
+ "h1",
1335
+ "h2",
1336
+ "bold",
1337
+ "italic",
1338
+ "underline",
1339
+ "strike",
1340
+ "code",
1341
+ "link",
1342
+ "bullet",
1343
+ "ordered",
1344
+ "quote",
1345
+ "codeblock",
1346
+ "rule",
1347
+ "image",
1348
+ "clear",
1349
+ "undo",
1350
+ "redo",
1351
+ "source"
1352
+ ];
1353
+ const WIND_TOOLBAR_PRESETS = {
1354
+ full: WIND_TOOLS,
1355
+ writing: [
1356
+ "paragraph",
1357
+ "h1",
1358
+ "h2",
1359
+ "bold",
1360
+ "italic",
1361
+ "link",
1362
+ "bullet",
1363
+ "ordered",
1364
+ "quote",
1365
+ "undo",
1366
+ "redo"
1367
+ ],
1368
+ basic: [
1369
+ "bold",
1370
+ "italic",
1371
+ "underline",
1372
+ "strike",
1373
+ "link",
1374
+ "bullet",
1375
+ "ordered",
1376
+ "undo",
1377
+ "redo"
1378
+ ],
1379
+ minimal: [
1380
+ "bold",
1381
+ "italic",
1382
+ "link"
1383
+ ]
1384
+ };
1385
+ const KNOWN = new Set(WIND_TOOLS);
1386
+ function isWindTool(value) {
1387
+ return KNOWN.has(value);
1388
+ }
1389
+ function isToolbarPreset(value) {
1390
+ return value === "full" || value === "writing" || value === "basic" || value === "minimal";
1391
+ }
1392
+ function resolveToolbar(value) {
1393
+ if (value == null) return [...WIND_TOOLS];
1394
+ if (typeof value === "string") return [...isToolbarPreset(value) ? WIND_TOOLBAR_PRESETS[value] : WIND_TOOLS];
1395
+ const picked = [];
1396
+ for (const id of value) if (isWindTool(id) && !picked.includes(id)) picked.push(id);
1397
+ return picked;
1398
+ }
1399
+ function mountWindSurface(host, options) {
1400
+ const doc = new Y.Doc();
1401
+ if (options.html) loadHtml(doc, options.html);
1402
+ const session = new WindSession(doc);
1403
+ const frame = host;
1404
+ frame.classList.add("wind-frame");
1405
+ const docEl = document.createElement("div");
1406
+ docEl.className = "wind-doc";
1407
+ docEl.setAttribute("role", "textbox");
1408
+ docEl.setAttribute("aria-multiline", "true");
1409
+ docEl.setAttribute("aria-label", "Document");
1410
+ docEl.spellcheck = true;
1411
+ frame.replaceChildren(docEl);
1412
+ let placeholder = options.placeholder ?? "Write something…";
1413
+ let readonly = options.readonly ?? false;
1414
+ let silent = 0;
1415
+ let composing = false;
1416
+ let pending = false;
1417
+ let destroyed = false;
1418
+ const applyEditable = () => {
1419
+ docEl.contentEditable = readonly ? "false" : "true";
1420
+ frame.classList.toggle("is-readonly", readonly);
1421
+ };
1422
+ applyEditable();
1423
+ const focused = () => docEl === document.activeElement || docEl.contains(document.activeElement);
1424
+ const blockEl = (id) => docEl.querySelector(`[data-id="${CSS.escape(id)}"]`);
1425
+ const pointToDom = (point) => {
1426
+ const el = blockEl(point.blockId);
1427
+ if (!el) return null;
1428
+ if (el.dataset["atomic"] === "true") return {
1429
+ node: el,
1430
+ offset: 0
1431
+ };
1432
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
1433
+ let left = point.offset;
1434
+ let last = null;
1435
+ while (walker.nextNode()) {
1436
+ const text = walker.currentNode;
1437
+ last = text;
1438
+ if (left <= text.length) return {
1439
+ node: text,
1440
+ offset: left
1441
+ };
1442
+ left -= text.length;
1443
+ }
1444
+ if (last) return {
1445
+ node: last,
1446
+ offset: last.length
1447
+ };
1448
+ return {
1449
+ node: el,
1450
+ offset: 0
1451
+ };
1452
+ };
1453
+ const pointFromNode = (node, offset) => {
1454
+ const block = (node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement)?.closest("[data-id]");
1455
+ if (!block || !docEl.contains(block)) return null;
1456
+ const blockId = block.dataset["id"] ?? "";
1457
+ if (block.dataset["atomic"] === "true") return {
1458
+ blockId,
1459
+ offset: 0
1460
+ };
1461
+ const range = document.createRange();
1462
+ range.selectNodeContents(block);
1463
+ try {
1464
+ range.setEnd(node, offset);
1465
+ } catch {
1466
+ return {
1467
+ blockId,
1468
+ offset: 0
1469
+ };
1470
+ }
1471
+ return {
1472
+ blockId,
1473
+ offset: range.toString().length
1474
+ };
1475
+ };
1476
+ const readSelection = () => {
1477
+ const sel = document.getSelection();
1478
+ if (!sel || sel.rangeCount === 0) return null;
1479
+ if (!sel.anchorNode || !docEl.contains(sel.anchorNode)) return null;
1480
+ const anchor = pointFromNode(sel.anchorNode, sel.anchorOffset);
1481
+ const headNode = sel.focusNode ?? sel.anchorNode;
1482
+ const head = pointFromNode(headNode, sel.focusOffset);
1483
+ if (!anchor || !head) return null;
1484
+ return {
1485
+ anchor,
1486
+ head
1487
+ };
1488
+ };
1489
+ const samePoint = (a, b) => a.blockId === b.blockId && a.offset === b.offset;
1490
+ const sameSelection = (a, b) => samePoint(a.anchor, b.anchor) && samePoint(a.head, b.head);
1491
+ const applySelection = (selection) => {
1492
+ const anchor = pointToDom(selection.anchor);
1493
+ const head = pointToDom(selection.head);
1494
+ if (!anchor || !head) return;
1495
+ const sel = document.getSelection();
1496
+ if (!sel) return;
1497
+ silent += 1;
1498
+ try {
1499
+ sel.setBaseAndExtent(anchor.node, anchor.offset, head.node, head.offset);
1500
+ } catch {}
1501
+ queueMicrotask(() => {
1502
+ silent = Math.max(0, silent - 1);
1503
+ });
1504
+ };
1505
+ const fillInline = (el, delta) => {
1506
+ el.replaceChildren();
1507
+ if (deltaToText(delta).length === 0) {
1508
+ el.append(document.createElement("br"));
1509
+ return;
1510
+ }
1511
+ for (const op of delta) {
1512
+ let node = document.createTextNode(op.insert);
1513
+ const attrs = op.attributes ?? {};
1514
+ const wrap = (tag, child) => {
1515
+ const wrapped = document.createElement(tag);
1516
+ wrapped.append(child);
1517
+ return wrapped;
1518
+ };
1519
+ if (attrs.code) node = wrap("code", node);
1520
+ if (attrs.strike) node = wrap("s", node);
1521
+ if (attrs.underline) node = wrap("u", node);
1522
+ if (attrs.italic) node = wrap("em", node);
1523
+ if (attrs.bold) node = wrap("strong", node);
1524
+ if (attrs.href) {
1525
+ const href = safeUrl(attrs.href);
1526
+ if (href) {
1527
+ const link = document.createElement("a");
1528
+ link.href = href;
1529
+ link.rel = "noopener noreferrer";
1530
+ link.append(node);
1531
+ node = link;
1532
+ }
1533
+ }
1534
+ el.append(node);
1535
+ }
1536
+ };
1537
+ const bind = (el, block) => {
1538
+ el.dataset["id"] = block.id;
1539
+ el.dataset["type"] = block.type;
1540
+ };
1541
+ const draw = () => {
1542
+ const blocks = readBlocks(session.doc);
1543
+ const fragment = document.createDocumentFragment();
1544
+ let index = 0;
1545
+ while (index < blocks.length) {
1546
+ const block = blocks[index];
1547
+ if (!block) break;
1548
+ if (block.type === "bullet" || block.type === "ordered") {
1549
+ const list = document.createElement(block.type === "bullet" ? "ul" : "ol");
1550
+ while (index < blocks.length && blocks[index]?.type === block.type) {
1551
+ const item = blocks[index];
1552
+ const li = document.createElement("li");
1553
+ bind(li, item);
1554
+ fillInline(li, item.delta);
1555
+ list.append(li);
1556
+ index += 1;
1557
+ }
1558
+ fragment.append(list);
1559
+ continue;
1560
+ }
1561
+ if (block.type === "image") {
1562
+ const figure = document.createElement("figure");
1563
+ bind(figure, block);
1564
+ figure.dataset["atomic"] = "true";
1565
+ figure.contentEditable = "false";
1566
+ const image = document.createElement("img");
1567
+ const src = safeUrl(block.src);
1568
+ if (src) image.src = src;
1569
+ image.alt = block.alt;
1570
+ image.draggable = false;
1571
+ figure.append(image);
1572
+ fragment.append(figure);
1573
+ index += 1;
1574
+ continue;
1575
+ }
1576
+ if (block.type === "rule") {
1577
+ const rule = document.createElement("hr");
1578
+ bind(rule, block);
1579
+ rule.dataset["atomic"] = "true";
1580
+ fragment.append(rule);
1581
+ index += 1;
1582
+ continue;
1583
+ }
1584
+ const tag = block.type === "heading" ? `h${Math.min(3, Math.max(1, block.level || 1))}` : block.type === "blockquote" ? "blockquote" : block.type === "code" ? "pre" : "p";
1585
+ const el = document.createElement(tag);
1586
+ bind(el, block);
1587
+ if (block.type === "code") {
1588
+ const code = document.createElement("code");
1589
+ fillInline(code, block.delta);
1590
+ el.append(code);
1591
+ } else fillInline(el, block.delta);
1592
+ if (block.type === "paragraph" && deltaToText(block.delta).length === 0) el.dataset["placeholder"] = placeholder;
1593
+ fragment.append(el);
1594
+ index += 1;
1595
+ }
1596
+ const empty = blocks.length === 1 && blocks[0]?.type === "paragraph" && deltaToText(blocks[0].delta).length === 0;
1597
+ docEl.dataset["empty"] = empty ? "true" : "false";
1598
+ docEl.replaceChildren(fragment);
1599
+ const head = blockEl(session.selection.head.blockId);
1600
+ if (head?.dataset["atomic"] === "true") head.classList.add("is-selected");
1601
+ if (focused()) applySelection(session.selection);
1602
+ };
1603
+ const slashRect = () => {
1604
+ if (session.snapshot().slash === null) return null;
1605
+ const sel = document.getSelection();
1606
+ if (!sel || sel.rangeCount === 0 || !sel.anchorNode || !docEl.contains(sel.anchorNode)) return null;
1607
+ const rect = sel.getRangeAt(0).getBoundingClientRect();
1608
+ return {
1609
+ top: rect.bottom + 6,
1610
+ left: Math.max(8, rect.left)
1611
+ };
1612
+ };
1613
+ const publish = () => {
1614
+ const snap = session.snapshot();
1615
+ const text = readBlocks(session.doc).map((block) => block.type === "image" ? block.alt || block.src : block.type === "rule" ? "" : deltaToText(block.delta)).join("\n");
1616
+ const words = text.trim() ? text.trim().split(/\s+/).length : 0;
1617
+ options.onChange?.({
1618
+ html: session.html,
1619
+ text,
1620
+ words,
1621
+ characters: text.replace(/\s/g, "").length,
1622
+ slashRect: slashRect(),
1623
+ ...snap
1624
+ });
1625
+ };
1626
+ const redraw = (structural) => {
1627
+ if (destroyed) return;
1628
+ if (composing && structural) {
1629
+ pending = true;
1630
+ publish();
1631
+ return;
1632
+ }
1633
+ if (structural) draw();
1634
+ publish();
1635
+ };
1636
+ const unsession = session.onChange(redraw);
1637
+ const reconcileDom = () => {
1638
+ const elements = [...docEl.querySelectorAll("[data-id]")];
1639
+ const model = readBlocks(session.doc);
1640
+ if (elements.length !== model.length || model.some((block) => !elements.some((el) => el.dataset["id"] === block.id))) {
1641
+ session.replaceHtml(docEl.innerHTML);
1642
+ return;
1643
+ }
1644
+ for (const el of elements) {
1645
+ const id = el.dataset["id"];
1646
+ if (!id || el.dataset["atomic"] === "true") continue;
1647
+ session.reconcileText(id, el.textContent ?? "");
1648
+ }
1649
+ };
1650
+ docEl.addEventListener("beforeinput", (event) => {
1651
+ if (readonly) {
1652
+ event.preventDefault();
1653
+ return;
1654
+ }
1655
+ if (composing) return;
1656
+ const input = event;
1657
+ const type = input.inputType;
1658
+ if (type === "insertText") {
1659
+ event.preventDefault();
1660
+ session.insertText(input.data ?? "");
1661
+ } else if (type === "insertParagraph") {
1662
+ event.preventDefault();
1663
+ session.split("enter");
1664
+ } else if (type === "insertLineBreak") {
1665
+ event.preventDefault();
1666
+ session.split("hard");
1667
+ } else if (type === "deleteContentBackward") {
1668
+ event.preventDefault();
1669
+ session.deleteBackward();
1670
+ } else if (type === "deleteContentForward") {
1671
+ event.preventDefault();
1672
+ session.deleteForward();
1673
+ } else if (type === "insertFromPaste" || type === "insertFromDrop") {
1674
+ event.preventDefault();
1675
+ const html = input.dataTransfer?.getData("text/html") || null;
1676
+ const text = input.dataTransfer?.getData("text/plain") || input.data || null;
1677
+ session.paste(html, text);
1678
+ } else if (type === "historyUndo") {
1679
+ event.preventDefault();
1680
+ session.undo();
1681
+ } else if (type === "historyRedo") {
1682
+ event.preventDefault();
1683
+ session.redo();
1684
+ } else if (type === "formatBold") {
1685
+ event.preventDefault();
1686
+ session.toggleMark("bold");
1687
+ } else if (type === "formatItalic") {
1688
+ event.preventDefault();
1689
+ session.toggleMark("italic");
1690
+ } else if (type === "formatUnderline") {
1691
+ event.preventDefault();
1692
+ session.toggleMark("underline");
1693
+ } else if (type === "formatStrikeThrough") {
1694
+ event.preventDefault();
1695
+ session.toggleMark("strike");
1696
+ } else if (type.startsWith("delete")) {
1697
+ event.preventDefault();
1698
+ session.deleteBackward();
1699
+ }
1700
+ });
1701
+ docEl.addEventListener("input", () => {
1702
+ if (composing || silent > 0) return;
1703
+ reconcileDom();
1704
+ });
1705
+ docEl.addEventListener("compositionstart", () => {
1706
+ composing = true;
1707
+ });
1708
+ docEl.addEventListener("compositionend", () => {
1709
+ composing = false;
1710
+ const sel = readSelection();
1711
+ if (sel) {
1712
+ const el = blockEl(sel.head.blockId);
1713
+ if (el && el.dataset["atomic"] !== "true") session.reconcileText(sel.head.blockId, el.textContent ?? "");
1714
+ }
1715
+ if (pending) {
1716
+ pending = false;
1717
+ draw();
1718
+ }
1719
+ publish();
1720
+ });
1721
+ const onSelectionChange = () => {
1722
+ if (silent > 0 || composing || !focused()) return;
1723
+ const next = readSelection();
1724
+ if (!next || sameSelection(next, session.selection)) return;
1725
+ session.select(next);
1726
+ };
1727
+ document.addEventListener("selectionchange", onSelectionChange);
1728
+ docEl.addEventListener("keydown", (event) => {
1729
+ if (options.interceptKey?.(event)) return;
1730
+ if (readonly) return;
1731
+ const meta = event.metaKey || event.ctrlKey;
1732
+ const key = event.key.toLowerCase();
1733
+ if (meta && key === "b") {
1734
+ event.preventDefault();
1735
+ session.toggleMark("bold");
1736
+ } else if (meta && key === "i") {
1737
+ event.preventDefault();
1738
+ session.toggleMark("italic");
1739
+ } else if (meta && key === "u") {
1740
+ event.preventDefault();
1741
+ session.toggleMark("underline");
1742
+ } else if (meta && key === "e") {
1743
+ event.preventDefault();
1744
+ session.toggleMark("code");
1745
+ } else if (meta && event.shiftKey && (key === "s" || key === "x")) {
1746
+ event.preventDefault();
1747
+ session.toggleMark("strike");
1748
+ } else if (meta && key === "k") {
1749
+ event.preventDefault();
1750
+ options.onLink?.();
1751
+ } else if (meta && key === "z") {
1752
+ event.preventDefault();
1753
+ if (event.shiftKey) session.redo();
1754
+ else session.undo();
1755
+ } else if (meta && key === "y") {
1756
+ event.preventDefault();
1757
+ session.redo();
1758
+ } else if (event.key === "Enter" && meta) {
1759
+ event.preventDefault();
1760
+ session.split("exit");
1761
+ } else if (event.key === "Tab") {
1762
+ event.preventDefault();
1763
+ session.insertText(" ");
1764
+ }
1765
+ });
1766
+ docEl.addEventListener("mousedown", (event) => {
1767
+ const atomic = (event.target instanceof Element ? event.target : null)?.closest("[data-atomic]");
1768
+ if (!atomic || !docEl.contains(atomic)) return;
1769
+ event.preventDefault();
1770
+ const id = atomic.dataset["id"];
1771
+ if (!id) return;
1772
+ docEl.focus();
1773
+ session.focusPoint(id, 0);
1774
+ draw();
1775
+ });
1776
+ docEl.addEventListener("click", (event) => {
1777
+ const link = (event.target instanceof Element ? event.target : null)?.closest("a");
1778
+ if (!link || !docEl.contains(link)) return;
1779
+ if (!event.metaKey && !event.ctrlKey) event.preventDefault();
1780
+ });
1781
+ docEl.addEventListener("cut", (event) => {
1782
+ const sel = document.getSelection();
1783
+ if (!sel || sel.isCollapsed || !sel.anchorNode || !docEl.contains(sel.anchorNode)) return;
1784
+ event.preventDefault();
1785
+ const range = sel.getRangeAt(0);
1786
+ const holder = document.createElement("div");
1787
+ holder.append(range.cloneContents());
1788
+ event.clipboardData?.setData("text/plain", range.toString());
1789
+ event.clipboardData?.setData("text/html", holder.innerHTML);
1790
+ session.deleteBackward();
1791
+ });
1792
+ draw();
1793
+ publish();
1794
+ return {
1795
+ session,
1796
+ destroy() {
1797
+ destroyed = true;
1798
+ document.removeEventListener("selectionchange", onSelectionChange);
1799
+ unsession();
1800
+ frame.replaceChildren();
1801
+ },
1802
+ focus() {
1803
+ docEl.focus();
1804
+ },
1805
+ exec(command) {
1806
+ if (readonly && command.type !== "undo" && command.type !== "redo") return;
1807
+ if (command.type === "mark") session.toggleMark(command.mark);
1808
+ else if (command.type === "link") session.setLink(command.href);
1809
+ else if (command.type === "clear") session.clearMarks();
1810
+ else if (command.type === "block") session.setBlockType(command.block, command.level ?? 1);
1811
+ else if (command.type === "divider") session.insertDivider();
1812
+ else if (command.type === "image") session.insertImage(command.src, command.alt ?? "");
1813
+ else if (command.type === "undo") session.undo();
1814
+ else if (command.type === "redo") session.redo();
1815
+ else if (command.type === "slash") session.applySlash(command.choice);
1816
+ if (!readonly) docEl.focus();
1817
+ },
1818
+ setReadonly(value) {
1819
+ readonly = value;
1820
+ applyEditable();
1821
+ },
1822
+ setPlaceholder(value) {
1823
+ placeholder = value;
1824
+ draw();
1825
+ },
1826
+ setHtml(html) {
1827
+ if (html === session.html) return;
1828
+ session.replaceHtml(html);
1829
+ },
1830
+ getHtml() {
1831
+ return session.html;
1832
+ }
1833
+ };
1834
+ }
1835
+ var WindIcon = class WindIcon {
1836
+ constructor() {
1837
+ this.name = input.required(...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
1838
+ }
1839
+ static {
1840
+ this.ɵfac = i0.ɵɵngDeclareFactory({
1841
+ minVersion: "12.0.0",
1842
+ version: "22.2.3",
1843
+ ngImport: i0,
1844
+ type: WindIcon,
1845
+ deps: [],
1846
+ target: i0.ɵɵFactoryTarget.Component
1847
+ });
1848
+ }
1849
+ static {
1850
+ this.ɵcmp = i0.ɵɵngDeclareComponent({
1851
+ minVersion: "17.0.0",
1852
+ version: "22.2.3",
1853
+ type: WindIcon,
1854
+ isStandalone: true,
1855
+ selector: "wind-icon",
1856
+ inputs: { name: {
1857
+ classPropertyName: "name",
1858
+ publicName: "name",
1859
+ isSignal: true,
1860
+ isRequired: true,
1861
+ transformFunction: null
1862
+ } },
1863
+ ngImport: i0,
1864
+ template: `
1865
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1866
+ @switch (name()) {
1867
+ @case ('bold') { <path d="M6 4h8a4 4 0 0 1 0 8H6z" /><path d="M6 12h9a4 4 0 0 1 0 8H6z" /> }
1868
+ @case ('italic') { <line x1="19" y1="4" x2="10" y2="4" /><line x1="14" y1="20" x2="5" y2="20" /><line x1="15" y1="4" x2="9" y2="20" /> }
1869
+ @case ('underline') { <path d="M6 4v6a6 6 0 0 0 12 0V4" /><line x1="4" y1="20" x2="20" y2="20" /> }
1870
+ @case ('strike') { <path d="M16 5c-.5-1.5-2-2.5-4-2.5-2.5 0-4 1.5-4 3.5 0 4 8 2 8 6 0 2-1.8 3.5-4.2 3.5-2.2 0-3.8-1.2-4.3-3" /><line x1="4" y1="12" x2="20" y2="12" /> }
1871
+ @case ('code') { <path d="m8 8-4 4 4 4" /><path d="m16 8 4 4-4 4" /> }
1872
+ @case ('link') { <path d="M10 13a5 5 0 0 0 7.5.5l2-2a5 5 0 0 0-7.1-7.1l-1.1 1.1" /><path d="M14 11a5 5 0 0 0-7.5-.5l-2 2a5 5 0 0 0 7.1 7.1l1.1-1.1" /> }
1873
+ @case ('list') { <line x1="9" y1="6" x2="20" y2="6" /><line x1="9" y1="12" x2="20" y2="12" /><line x1="9" y1="18" x2="20" y2="18" /><circle cx="5" cy="6" r="1" fill="currentColor" stroke="none" /><circle cx="5" cy="12" r="1" fill="currentColor" stroke="none" /><circle cx="5" cy="18" r="1" fill="currentColor" stroke="none" /> }
1874
+ @case ('ordered') { <line x1="10" y1="6" x2="21" y2="6" /><line x1="10" y1="12" x2="21" y2="12" /><line x1="10" y1="18" x2="21" y2="18" /><path d="M4 6h1v4" /><path d="M4 10h2" /><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" /> }
1875
+ @case ('quote') { <path d="M8 7H5a2 2 0 0 0-2 2v3h5V7z" /><path d="M17 7h-3a2 2 0 0 0-2 2v3h5V7z" /><path d="M8 12v2a3 3 0 0 1-3 3" /><path d="M17 12v2a3 3 0 0 1-3 3" /> }
1876
+ @case ('block') { <rect x="4" y="4" width="16" height="16" rx="2" /><path d="m9 9-2 3 2 3" /><path d="m15 9 2 3-2 3" /> }
1877
+ @case ('rule') { <line x1="4" y1="12" x2="20" y2="12" /> }
1878
+ @case ('image') { <rect x="3" y="5" width="18" height="14" rx="2" /><circle cx="8.5" cy="10" r="1.5" /><path d="m21 16-5-4-9 8" /> }
1879
+ @case ('undo') { <path d="M3 7v6h6" /><path d="M21 17a9 9 0 0 0-15-6.7L3 13" /> }
1880
+ @case ('redo') { <path d="M21 7v6h-6" /><path d="M3 17a9 9 0 0 1 15-6.7L21 13" /> }
1881
+ @case ('clear') { <path d="M4 7h16" /><path d="M9 7V5h6v2" /><path d="m8 7 1 12h6l1-12" /> }
1882
+ @case ('source') { <path d="M8 6 3 12l5 6" /><path d="m16 6 5 6-5 6" /><path d="m14 4-4 16" /> }
1883
+ }
1884
+ </svg>
1885
+ `,
1886
+ isInline: true,
1887
+ styles: [":host{display:inline-flex}svg{width:1rem;height:1rem;display:block}\n"],
1888
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1889
+ });
1890
+ }
1891
+ };
1892
+ i0.ɵɵngDeclareClassMetadata({
1893
+ minVersion: "12.0.0",
1894
+ version: "22.2.3",
1895
+ ngImport: i0,
1896
+ type: WindIcon,
1897
+ decorators: [{
1898
+ type: Component,
1899
+ args: [{
1900
+ selector: "wind-icon",
1901
+ changeDetection: ChangeDetectionStrategy.OnPush,
1902
+ template: `
1903
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1904
+ @switch (name()) {
1905
+ @case ('bold') { <path d="M6 4h8a4 4 0 0 1 0 8H6z" /><path d="M6 12h9a4 4 0 0 1 0 8H6z" /> }
1906
+ @case ('italic') { <line x1="19" y1="4" x2="10" y2="4" /><line x1="14" y1="20" x2="5" y2="20" /><line x1="15" y1="4" x2="9" y2="20" /> }
1907
+ @case ('underline') { <path d="M6 4v6a6 6 0 0 0 12 0V4" /><line x1="4" y1="20" x2="20" y2="20" /> }
1908
+ @case ('strike') { <path d="M16 5c-.5-1.5-2-2.5-4-2.5-2.5 0-4 1.5-4 3.5 0 4 8 2 8 6 0 2-1.8 3.5-4.2 3.5-2.2 0-3.8-1.2-4.3-3" /><line x1="4" y1="12" x2="20" y2="12" /> }
1909
+ @case ('code') { <path d="m8 8-4 4 4 4" /><path d="m16 8 4 4-4 4" /> }
1910
+ @case ('link') { <path d="M10 13a5 5 0 0 0 7.5.5l2-2a5 5 0 0 0-7.1-7.1l-1.1 1.1" /><path d="M14 11a5 5 0 0 0-7.5-.5l-2 2a5 5 0 0 0 7.1 7.1l1.1-1.1" /> }
1911
+ @case ('list') { <line x1="9" y1="6" x2="20" y2="6" /><line x1="9" y1="12" x2="20" y2="12" /><line x1="9" y1="18" x2="20" y2="18" /><circle cx="5" cy="6" r="1" fill="currentColor" stroke="none" /><circle cx="5" cy="12" r="1" fill="currentColor" stroke="none" /><circle cx="5" cy="18" r="1" fill="currentColor" stroke="none" /> }
1912
+ @case ('ordered') { <line x1="10" y1="6" x2="21" y2="6" /><line x1="10" y1="12" x2="21" y2="12" /><line x1="10" y1="18" x2="21" y2="18" /><path d="M4 6h1v4" /><path d="M4 10h2" /><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" /> }
1913
+ @case ('quote') { <path d="M8 7H5a2 2 0 0 0-2 2v3h5V7z" /><path d="M17 7h-3a2 2 0 0 0-2 2v3h5V7z" /><path d="M8 12v2a3 3 0 0 1-3 3" /><path d="M17 12v2a3 3 0 0 1-3 3" /> }
1914
+ @case ('block') { <rect x="4" y="4" width="16" height="16" rx="2" /><path d="m9 9-2 3 2 3" /><path d="m15 9 2 3-2 3" /> }
1915
+ @case ('rule') { <line x1="4" y1="12" x2="20" y2="12" /> }
1916
+ @case ('image') { <rect x="3" y="5" width="18" height="14" rx="2" /><circle cx="8.5" cy="10" r="1.5" /><path d="m21 16-5-4-9 8" /> }
1917
+ @case ('undo') { <path d="M3 7v6h6" /><path d="M21 17a9 9 0 0 0-15-6.7L3 13" /> }
1918
+ @case ('redo') { <path d="M21 7v6h-6" /><path d="M3 17a9 9 0 0 1 15-6.7L21 13" /> }
1919
+ @case ('clear') { <path d="M4 7h16" /><path d="M9 7V5h6v2" /><path d="m8 7 1 12h6l1-12" /> }
1920
+ @case ('source') { <path d="M8 6 3 12l5 6" /><path d="m16 6 5 6-5 6" /><path d="m14 4-4 16" /> }
1921
+ }
1922
+ </svg>
1923
+ `,
1924
+ styles: [":host{display:inline-flex}svg{width:1rem;height:1rem;display:block}\n"]
1925
+ }]
1926
+ }],
1927
+ propDecorators: { name: [{
1928
+ type: i0.Input,
1929
+ args: [{
1930
+ isSignal: true,
1931
+ alias: "name",
1932
+ required: true
1933
+ }]
1934
+ }] }
1935
+ });
1936
+ const TOOLS = [
1937
+ {
1938
+ id: "paragraph",
1939
+ label: "Text",
1940
+ icon: "block"
1941
+ },
1942
+ {
1943
+ id: "h1",
1944
+ label: "Heading 1",
1945
+ icon: "block"
1946
+ },
1947
+ {
1948
+ id: "h2",
1949
+ label: "Heading 2",
1950
+ icon: "block"
1951
+ },
1952
+ {
1953
+ id: "bold",
1954
+ label: "Bold",
1955
+ icon: "bold"
1956
+ },
1957
+ {
1958
+ id: "italic",
1959
+ label: "Italic",
1960
+ icon: "italic"
1961
+ },
1962
+ {
1963
+ id: "underline",
1964
+ label: "Underline",
1965
+ icon: "underline"
1966
+ },
1967
+ {
1968
+ id: "strike",
1969
+ label: "Strikethrough",
1970
+ icon: "strike"
1971
+ },
1972
+ {
1973
+ id: "code",
1974
+ label: "Inline code",
1975
+ icon: "code"
1976
+ },
1977
+ {
1978
+ id: "link",
1979
+ label: "Link",
1980
+ icon: "link"
1981
+ },
1982
+ {
1983
+ id: "bullet",
1984
+ label: "Bullet list",
1985
+ icon: "list"
1986
+ },
1987
+ {
1988
+ id: "ordered",
1989
+ label: "Numbered list",
1990
+ icon: "ordered"
1991
+ },
1992
+ {
1993
+ id: "quote",
1994
+ label: "Quote",
1995
+ icon: "quote"
1996
+ },
1997
+ {
1998
+ id: "codeblock",
1999
+ label: "Code block",
2000
+ icon: "block"
2001
+ },
2002
+ {
2003
+ id: "rule",
2004
+ label: "Divider",
2005
+ icon: "rule"
2006
+ },
2007
+ {
2008
+ id: "image",
2009
+ label: "Image",
2010
+ icon: "image"
2011
+ },
2012
+ {
2013
+ id: "clear",
2014
+ label: "Clear formatting",
2015
+ icon: "clear"
2016
+ },
2017
+ {
2018
+ id: "undo",
2019
+ label: "Undo",
2020
+ icon: "undo"
2021
+ },
2022
+ {
2023
+ id: "redo",
2024
+ label: "Redo",
2025
+ icon: "redo"
2026
+ },
2027
+ {
2028
+ id: "source",
2029
+ label: "HTML",
2030
+ icon: "source"
2031
+ }
2032
+ ];
2033
+ var WindEditor = class WindEditor {
2034
+ constructor() {
2035
+ this.html = model("", ...ngDevMode ? [{ debugName: "html" }] : /* istanbul ignore next */ []);
2036
+ this.placeholder = input("Write something…", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
2037
+ this.readOnly = input(false, {
2038
+ ...ngDevMode ? { debugName: "readOnly" } : /* istanbul ignore next */ {},
2039
+ alias: "readonly"
2040
+ });
2041
+ this.showToolbar = input(true, ...ngDevMode ? [{ debugName: "showToolbar" }] : /* istanbul ignore next */ []);
2042
+ this.toolbar = input("full", ...ngDevMode ? [{ debugName: "toolbar" }] : /* istanbul ignore next */ []);
2043
+ this.tools = computed(() => {
2044
+ const byId = new Map(TOOLS.map((tool) => [tool.id, tool]));
2045
+ return resolveToolbar(this.toolbar()).flatMap((id) => {
2046
+ const tool = byId.get(id);
2047
+ return tool ? [tool] : [];
2048
+ });
2049
+ }, ...ngDevMode ? [{ debugName: "tools" }] : /* istanbul ignore next */ []);
2050
+ this.change = signal(null, ...ngDevMode ? [{ debugName: "change" }] : /* istanbul ignore next */ []);
2051
+ this.linkOpen = signal(false, ...ngDevMode ? [{ debugName: "linkOpen" }] : /* istanbul ignore next */ []);
2052
+ this.linkDraft = signal("", ...ngDevMode ? [{ debugName: "linkDraft" }] : /* istanbul ignore next */ []);
2053
+ this.imageOpen = signal(false, ...ngDevMode ? [{ debugName: "imageOpen" }] : /* istanbul ignore next */ []);
2054
+ this.imageSrc = signal("", ...ngDevMode ? [{ debugName: "imageSrc" }] : /* istanbul ignore next */ []);
2055
+ this.imageAlt = signal("", ...ngDevMode ? [{ debugName: "imageAlt" }] : /* istanbul ignore next */ []);
2056
+ this.sourceOpen = signal(false, ...ngDevMode ? [{ debugName: "sourceOpen" }] : /* istanbul ignore next */ []);
2057
+ this.sourceDraft = signal("", ...ngDevMode ? [{ debugName: "sourceDraft" }] : /* istanbul ignore next */ []);
2058
+ this.slashIndex = signal(0, ...ngDevMode ? [{ debugName: "slashIndex" }] : /* istanbul ignore next */ []);
2059
+ this.slashItems = computed(() => {
2060
+ const query = this.change()?.slash;
2061
+ return query === null || query === void 0 ? [] : filterSlash(query);
2062
+ }, ...ngDevMode ? [{ debugName: "slashItems" }] : /* istanbul ignore next */ []);
2063
+ this.surfaceRef = viewChild("surface", ...ngDevMode ? [{ debugName: "surfaceRef" }] : /* istanbul ignore next */ []);
2064
+ this.destroyRef = inject(DestroyRef);
2065
+ this.handle = null;
2066
+ this.echo = "";
2067
+ afterNextRender(() => this.attach());
2068
+ effect(() => {
2069
+ const value = this.placeholder();
2070
+ this.handle?.setPlaceholder(value);
2071
+ });
2072
+ effect(() => {
2073
+ this.handle?.setReadonly(this.readOnly());
2074
+ });
2075
+ effect(() => {
2076
+ const html = this.html();
2077
+ if (html !== this.echo) this.handle?.setHtml(html);
2078
+ });
2079
+ effect(() => {
2080
+ this.slashItems();
2081
+ this.slashIndex.set(0);
2082
+ });
2083
+ this.destroyRef.onDestroy(() => this.handle?.destroy());
2084
+ }
2085
+ pressed(id) {
2086
+ const snap = this.change();
2087
+ if (!snap) return false;
2088
+ if (id === "bold" || id === "italic" || id === "underline" || id === "strike" || id === "code") return !!snap.marks[id];
2089
+ if (id === "link") return !!snap.href;
2090
+ if (id === "paragraph") return snap.blockType === "paragraph";
2091
+ if (id === "h1") return snap.blockType === "heading" && snap.level === 1;
2092
+ if (id === "h2") return snap.blockType === "heading" && snap.level === 2;
2093
+ if (id === "bullet" || id === "ordered" || id === "quote") {
2094
+ const type = id === "quote" ? "blockquote" : id;
2095
+ return snap.blockType === type;
2096
+ }
2097
+ if (id === "codeblock") return snap.blockType === "code";
2098
+ if (id === "source") return this.sourceOpen();
2099
+ return false;
2100
+ }
2101
+ disabled(id) {
2102
+ if (this.readOnly() && id !== "source") return true;
2103
+ const snap = this.change();
2104
+ if (id === "undo") return !snap?.canUndo;
2105
+ if (id === "redo") return !snap?.canRedo;
2106
+ return false;
2107
+ }
2108
+ run(id) {
2109
+ const handle = this.handle;
2110
+ if (!handle) return;
2111
+ if (id === "bold" || id === "italic" || id === "underline" || id === "strike" || id === "code") handle.exec({
2112
+ type: "mark",
2113
+ mark: id
2114
+ });
2115
+ else if (id === "paragraph") handle.exec({
2116
+ type: "block",
2117
+ block: "paragraph"
2118
+ });
2119
+ else if (id === "h1") handle.exec({
2120
+ type: "block",
2121
+ block: "heading",
2122
+ level: 1
2123
+ });
2124
+ else if (id === "h2") handle.exec({
2125
+ type: "block",
2126
+ block: "heading",
2127
+ level: 2
2128
+ });
2129
+ else if (id === "bullet" || id === "ordered") handle.exec({
2130
+ type: "block",
2131
+ block: id
2132
+ });
2133
+ else if (id === "quote") handle.exec({
2134
+ type: "block",
2135
+ block: "blockquote"
2136
+ });
2137
+ else if (id === "codeblock") handle.exec({
2138
+ type: "block",
2139
+ block: "code"
2140
+ });
2141
+ else if (id === "rule") handle.exec({ type: "divider" });
2142
+ else if (id === "clear") handle.exec({ type: "clear" });
2143
+ else if (id === "undo") handle.exec({ type: "undo" });
2144
+ else if (id === "redo") handle.exec({ type: "redo" });
2145
+ else if (id === "link") {
2146
+ this.linkDraft.set(this.change()?.href ?? "");
2147
+ this.linkOpen.update((open) => !open);
2148
+ } else if (id === "image") this.imageOpen.update((open) => !open);
2149
+ else if (id === "source") {
2150
+ this.sourceDraft.set(this.change()?.html ?? this.html());
2151
+ this.sourceOpen.update((open) => !open);
2152
+ }
2153
+ }
2154
+ pickSlash(item) {
2155
+ if (item.id === "image") {
2156
+ this.handle?.exec({
2157
+ type: "slash",
2158
+ choice: "image"
2159
+ });
2160
+ this.imageOpen.set(true);
2161
+ return;
2162
+ }
2163
+ this.handle?.exec({
2164
+ type: "slash",
2165
+ choice: item.id
2166
+ });
2167
+ }
2168
+ applyLink(event) {
2169
+ event.preventDefault();
2170
+ this.handle?.exec({
2171
+ type: "link",
2172
+ href: this.linkDraft().trim() || null
2173
+ });
2174
+ this.linkOpen.set(false);
2175
+ }
2176
+ clearLink() {
2177
+ this.handle?.exec({
2178
+ type: "link",
2179
+ href: null
2180
+ });
2181
+ this.linkOpen.set(false);
2182
+ }
2183
+ applyImage(event) {
2184
+ event.preventDefault();
2185
+ const src = this.imageSrc().trim();
2186
+ if (!src) return;
2187
+ this.handle?.exec({
2188
+ type: "image",
2189
+ src,
2190
+ alt: this.imageAlt().trim()
2191
+ });
2192
+ this.imageOpen.set(false);
2193
+ this.imageSrc.set("");
2194
+ this.imageAlt.set("");
2195
+ }
2196
+ applySource() {
2197
+ this.echo = this.sourceDraft();
2198
+ this.html.set(this.echo);
2199
+ this.handle?.setHtml(this.echo);
2200
+ this.sourceOpen.set(false);
2201
+ }
2202
+ onDraft(event, field) {
2203
+ const value = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement ? event.target.value : "";
2204
+ if (field === "link") this.linkDraft.set(value);
2205
+ else if (field === "src") this.imageSrc.set(value);
2206
+ else if (field === "alt") this.imageAlt.set(value);
2207
+ else this.sourceDraft.set(value);
2208
+ }
2209
+ attach() {
2210
+ const element = this.surfaceRef()?.nativeElement;
2211
+ if (!element || this.handle) return;
2212
+ this.handle = mountWindSurface(element, {
2213
+ html: this.html() || void 0,
2214
+ placeholder: this.placeholder(),
2215
+ readonly: this.readOnly(),
2216
+ onChange: (change) => {
2217
+ this.change.set(change);
2218
+ if (change.html !== this.html()) {
2219
+ this.echo = change.html;
2220
+ this.html.set(change.html);
2221
+ }
2222
+ },
2223
+ onLink: () => {
2224
+ this.linkDraft.set(this.change()?.href ?? "");
2225
+ this.linkOpen.set(true);
2226
+ },
2227
+ interceptKey: (event) => this.onKey(event)
2228
+ });
2229
+ this.echo = this.handle.getHtml();
2230
+ if (this.echo !== this.html()) this.html.set(this.echo);
2231
+ }
2232
+ onKey(event) {
2233
+ const items = this.slashItems();
2234
+ if (items.length === 0) return false;
2235
+ if (event.key === "ArrowDown") {
2236
+ event.preventDefault();
2237
+ this.slashIndex.update((index) => (index + 1) % items.length);
2238
+ return true;
2239
+ }
2240
+ if (event.key === "ArrowUp") {
2241
+ event.preventDefault();
2242
+ this.slashIndex.update((index) => (index - 1 + items.length) % items.length);
2243
+ return true;
2244
+ }
2245
+ if (event.key === "Enter" || event.key === "Tab") {
2246
+ event.preventDefault();
2247
+ const item = items[this.slashIndex()] ?? items[0];
2248
+ if (item) this.pickSlash(item);
2249
+ return true;
2250
+ }
2251
+ if (event.key === "Escape") {
2252
+ event.preventDefault();
2253
+ this.handle?.session.dismissSlash();
2254
+ return true;
2255
+ }
2256
+ return false;
2257
+ }
2258
+ static {
2259
+ this.ɵfac = i0.ɵɵngDeclareFactory({
2260
+ minVersion: "12.0.0",
2261
+ version: "22.2.3",
2262
+ ngImport: i0,
2263
+ type: WindEditor,
2264
+ deps: [],
2265
+ target: i0.ɵɵFactoryTarget.Component
2266
+ });
2267
+ }
2268
+ static {
2269
+ this.ɵcmp = i0.ɵɵngDeclareComponent({
2270
+ minVersion: "17.0.0",
2271
+ version: "22.2.3",
2272
+ type: WindEditor,
2273
+ isStandalone: true,
2274
+ selector: "wind-editor",
2275
+ inputs: {
2276
+ html: {
2277
+ classPropertyName: "html",
2278
+ publicName: "html",
2279
+ isSignal: true,
2280
+ isRequired: false,
2281
+ transformFunction: null
2282
+ },
2283
+ placeholder: {
2284
+ classPropertyName: "placeholder",
2285
+ publicName: "placeholder",
2286
+ isSignal: true,
2287
+ isRequired: false,
2288
+ transformFunction: null
2289
+ },
2290
+ readOnly: {
2291
+ classPropertyName: "readOnly",
2292
+ publicName: "readonly",
2293
+ isSignal: true,
2294
+ isRequired: false,
2295
+ transformFunction: null
2296
+ },
2297
+ showToolbar: {
2298
+ classPropertyName: "showToolbar",
2299
+ publicName: "showToolbar",
2300
+ isSignal: true,
2301
+ isRequired: false,
2302
+ transformFunction: null
2303
+ },
2304
+ toolbar: {
2305
+ classPropertyName: "toolbar",
2306
+ publicName: "toolbar",
2307
+ isSignal: true,
2308
+ isRequired: false,
2309
+ transformFunction: null
2310
+ }
2311
+ },
2312
+ outputs: { html: "htmlChange" },
2313
+ host: { classAttribute: "wind-editor block min-w-0" },
2314
+ viewQueries: [{
2315
+ propertyName: "surfaceRef",
2316
+ first: true,
2317
+ predicate: ["surface"],
2318
+ descendants: true,
2319
+ isSignal: true
2320
+ }],
2321
+ ngImport: i0,
2322
+ template: `
2323
+ <div class="rounded-xl border border-stone-200 bg-white text-stone-900 shadow-sm dark:border-stone-800 dark:bg-stone-950 dark:text-stone-100">
2324
+ @if (showToolbar() && tools().length > 0) {
2325
+ <div class="flex gap-0.5 overflow-x-auto border-b border-stone-200 px-1.5 py-1.5 dark:border-stone-800" role="toolbar" aria-label="Formatting">
2326
+ @for (tool of tools(); track tool.id) {
2327
+ <button
2328
+ type="button"
2329
+ class="inline-flex h-9 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-sm text-stone-600 hover:bg-stone-100 disabled:opacity-40 aria-pressed:bg-teal-700 aria-pressed:text-white dark:text-stone-300 dark:hover:bg-stone-900 dark:aria-pressed:bg-teal-600"
2330
+ [attr.aria-label]="tool.label"
2331
+ [attr.title]="tool.label"
2332
+ [attr.aria-pressed]="pressed(tool.id)"
2333
+ [disabled]="disabled(tool.id)"
2334
+ (mousedown)="$event.preventDefault()"
2335
+ (click)="run(tool.id)"
2336
+ >
2337
+ @if (tool.id === 'paragraph' || tool.id === 'h1' || tool.id === 'h2') {
2338
+ <span class="px-0.5 text-xs font-semibold tracking-wide">{{ tool.id === 'paragraph' ? 'P' : tool.id.toUpperCase() }}</span>
2339
+ } @else {
2340
+ <wind-icon [name]="tool.icon" />
2341
+ }
2342
+ </button>
2343
+ }
2344
+ </div>
2345
+ }
2346
+
2347
+ @if (linkOpen()) {
2348
+ <form class="flex flex-wrap items-center gap-2 border-b border-stone-200 px-3 py-2 dark:border-stone-800" (submit)="applyLink($event)">
2349
+ <label class="sr-only" for="wind-link">Link URL</label>
2350
+ <input
2351
+ id="wind-link"
2352
+ class="h-9 min-w-0 flex-1 rounded-md border border-stone-300 bg-white px-2 text-sm outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900"
2353
+ [value]="linkDraft()"
2354
+ placeholder="https://"
2355
+ (input)="onDraft($event, 'link')"
2356
+ />
2357
+ <button type="submit" class="h-9 rounded-md bg-teal-700 px-3 text-sm font-medium text-white">Apply</button>
2358
+ <button type="button" class="h-9 rounded-md px-2 text-sm text-stone-600 dark:text-stone-300" (click)="clearLink()">Remove</button>
2359
+ </form>
2360
+ }
2361
+
2362
+ @if (imageOpen()) {
2363
+ <form class="flex flex-wrap items-center gap-2 border-b border-stone-200 px-3 py-2 dark:border-stone-800" (submit)="applyImage($event)">
2364
+ <input class="h-9 min-w-0 flex-1 rounded-md border border-stone-300 bg-white px-2 text-sm outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900" [value]="imageSrc()" placeholder="Image URL" (input)="onDraft($event, 'src')" />
2365
+ <input class="h-9 w-36 rounded-md border border-stone-300 bg-white px-2 text-sm outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900" [value]="imageAlt()" placeholder="Alt text" (input)="onDraft($event, 'alt')" />
2366
+ <button type="submit" class="h-9 rounded-md bg-teal-700 px-3 text-sm font-medium text-white">Insert</button>
2367
+ </form>
2368
+ }
2369
+
2370
+ <div class="relative min-h-48" #surface></div>
2371
+
2372
+ @if (slashItems().length > 0 && change()?.slashRect; as rect) {
2373
+ <ul
2374
+ class="fixed z-30 max-h-64 w-56 overflow-auto rounded-lg border border-stone-200 bg-white py-1 shadow-lg dark:border-stone-700 dark:bg-stone-900"
2375
+ role="listbox"
2376
+ [style.top.px]="rect.top"
2377
+ [style.left.px]="rect.left"
2378
+ >
2379
+ @for (item of slashItems(); track item.id; let index = $index) {
2380
+ <li>
2381
+ <button
2382
+ type="button"
2383
+ class="flex w-full items-baseline justify-between gap-3 px-3 py-1.5 text-left text-sm hover:bg-stone-100 dark:hover:bg-stone-800"
2384
+ [class.bg-teal-700]="index === slashIndex()"
2385
+ [class.text-white]="index === slashIndex()"
2386
+ role="option"
2387
+ [attr.aria-selected]="index === slashIndex()"
2388
+ (mousedown)="$event.preventDefault()"
2389
+ (click)="pickSlash(item)"
2390
+ >
2391
+ <span>{{ item.label }}</span>
2392
+ <span class="text-xs opacity-70">{{ item.hint }}</span>
2393
+ </button>
2394
+ </li>
2395
+ }
2396
+ </ul>
2397
+ }
2398
+
2399
+ <footer class="flex items-center justify-between gap-3 border-t border-stone-200 px-3 py-1.5 text-xs text-stone-500 dark:border-stone-800 dark:text-stone-400">
2400
+ <span>{{ change()?.words ?? 0 }} words</span>
2401
+ <span class="text-stone-400">/ for blocks</span>
2402
+ </footer>
2403
+
2404
+ @if (sourceOpen()) {
2405
+ <div class="border-t border-stone-200 p-3 dark:border-stone-800">
2406
+ <textarea
2407
+ class="h-40 w-full resize-y rounded-md border border-stone-300 bg-stone-50 p-2 font-mono text-xs outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900"
2408
+ [value]="sourceDraft()"
2409
+ spellcheck="false"
2410
+ aria-label="HTML source"
2411
+ (input)="onDraft($event, 'source')"
2412
+ ></textarea>
2413
+ <button type="button" class="mt-2 h-9 rounded-md bg-stone-900 px-3 text-sm text-white dark:bg-stone-100 dark:text-stone-900" (click)="applySource()">Apply HTML</button>
2414
+ </div>
2415
+ }
2416
+ </div>
2417
+ `,
2418
+ isInline: true,
2419
+ styles: [":host{display:block;min-width:0}.wind-frame{position:relative;min-width:0}.wind-doc{min-height:12rem;padding:1rem 1.15rem 1.5rem;outline:none;color:var(--color-stone-900, #1c1917);font-family:var(--font-serif, Georgia, \"Iowan Old Style\", \"Palatino Linotype\", serif);font-size:1.0625rem;line-height:1.65;caret-color:var(--color-teal-700, #0f766e);overflow-wrap:anywhere}.wind-doc>*+*{margin-top:.75rem}.wind-doc p,.wind-doc h1,.wind-doc h2,.wind-doc h3,.wind-doc blockquote,.wind-doc pre,.wind-doc li{margin:0}.wind-doc h1,.wind-doc h2,.wind-doc h3{font-family:var(--font-sans, ui-sans-serif, system-ui, sans-serif);line-height:1.2;letter-spacing:-.03em;color:var(--color-stone-950, #0c0a09)}.wind-doc h1{font-size:1.875rem;font-weight:600}.wind-doc h2{font-size:1.5rem;font-weight:600}.wind-doc h3{font-size:1.25rem;font-weight:600}.wind-doc blockquote{border-left:2px solid var(--color-teal-700, #0f766e);padding-left:.9rem;color:var(--color-stone-600, #57534e)}.wind-doc ul,.wind-doc ol{margin:0;padding-left:1.25rem}.wind-doc li+li{margin-top:.25rem}.wind-doc pre{overflow-x:auto;border-radius:.6rem;background:color-mix(in oklab,var(--color-stone-900, #1c1917) 6%,transparent);padding:.8rem .9rem;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.875rem;line-height:1.55;white-space:pre-wrap}.wind-doc pre code{font:inherit;background:none;padding:0}.wind-doc a{color:var(--color-teal-800, #115e59);text-decoration:underline;text-underline-offset:2px}.wind-doc code{border-radius:.3rem;background:color-mix(in oklab,var(--color-stone-900, #1c1917) 8%,transparent);padding:.05em .3em;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.88em}.wind-doc img{display:block;max-width:100%;border-radius:.6rem}.wind-doc figure{margin:0}.wind-doc hr{border:0;border-top:1px solid var(--color-stone-300, #d6d3d1);margin:.25rem 0}.wind-doc [data-atomic].is-selected{outline:2px solid var(--color-teal-700, #0f766e);outline-offset:3px;border-radius:.4rem}.wind-doc[data-empty=true]>p[data-placeholder]:before{content:attr(data-placeholder);float:left;height:0;pointer-events:none;color:var(--color-stone-400, #a8a29e)}.wind-carets{position:absolute;inset:0;pointer-events:none;overflow:hidden}.wind-caret,.wind-remote{position:absolute;pointer-events:none}.wind-caret{width:2px;background:var(--peer);border-radius:1px}.wind-caret i{position:absolute;top:-1.15rem;left:-1px;border-radius:.3rem;background:var(--peer);color:#fff;padding:.05rem .3rem;font-family:var(--font-sans, ui-sans-serif, system-ui, sans-serif);font-size:.65rem;font-style:normal;line-height:1.3;letter-spacing:.01em;white-space:nowrap}.wind-remote{background:color-mix(in oklab,var(--peer) 32%,transparent);border-radius:.1rem}.wind-frame.is-readonly .wind-doc{caret-color:transparent}.dark .wind-doc{color:var(--color-stone-100, #f5f5f4);caret-color:var(--color-teal-300, #5eead4)}.dark .wind-doc h1,.dark .wind-doc h2,.dark .wind-doc h3{color:var(--color-stone-50, #fafaf9)}.dark .wind-doc blockquote{color:var(--color-stone-400, #a8a29e)}.dark .wind-doc a{color:var(--color-teal-300, #5eead4)}.dark .wind-doc hr{border-top-color:var(--color-stone-700, #44403c)}.dark .wind-doc pre,.dark .wind-doc code{background:color-mix(in oklab,white 8%,transparent)}\n"],
2420
+ dependencies: [{
2421
+ kind: "component",
2422
+ type: WindIcon,
2423
+ selector: "wind-icon",
2424
+ inputs: ["name"]
2425
+ }],
2426
+ changeDetection: i0.ChangeDetectionStrategy.OnPush,
2427
+ encapsulation: i0.ViewEncapsulation.None
2428
+ });
2429
+ }
2430
+ };
2431
+ i0.ɵɵngDeclareClassMetadata({
2432
+ minVersion: "12.0.0",
2433
+ version: "22.2.3",
2434
+ ngImport: i0,
2435
+ type: WindEditor,
2436
+ decorators: [{
2437
+ type: Component,
2438
+ args: [{
2439
+ selector: "wind-editor",
2440
+ changeDetection: ChangeDetectionStrategy.OnPush,
2441
+ encapsulation: ViewEncapsulation.None,
2442
+ imports: [WindIcon],
2443
+ host: { class: "wind-editor block min-w-0" },
2444
+ template: `
2445
+ <div class="rounded-xl border border-stone-200 bg-white text-stone-900 shadow-sm dark:border-stone-800 dark:bg-stone-950 dark:text-stone-100">
2446
+ @if (showToolbar() && tools().length > 0) {
2447
+ <div class="flex gap-0.5 overflow-x-auto border-b border-stone-200 px-1.5 py-1.5 dark:border-stone-800" role="toolbar" aria-label="Formatting">
2448
+ @for (tool of tools(); track tool.id) {
2449
+ <button
2450
+ type="button"
2451
+ class="inline-flex h-9 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-sm text-stone-600 hover:bg-stone-100 disabled:opacity-40 aria-pressed:bg-teal-700 aria-pressed:text-white dark:text-stone-300 dark:hover:bg-stone-900 dark:aria-pressed:bg-teal-600"
2452
+ [attr.aria-label]="tool.label"
2453
+ [attr.title]="tool.label"
2454
+ [attr.aria-pressed]="pressed(tool.id)"
2455
+ [disabled]="disabled(tool.id)"
2456
+ (mousedown)="$event.preventDefault()"
2457
+ (click)="run(tool.id)"
2458
+ >
2459
+ @if (tool.id === 'paragraph' || tool.id === 'h1' || tool.id === 'h2') {
2460
+ <span class="px-0.5 text-xs font-semibold tracking-wide">{{ tool.id === 'paragraph' ? 'P' : tool.id.toUpperCase() }}</span>
2461
+ } @else {
2462
+ <wind-icon [name]="tool.icon" />
2463
+ }
2464
+ </button>
2465
+ }
2466
+ </div>
2467
+ }
2468
+
2469
+ @if (linkOpen()) {
2470
+ <form class="flex flex-wrap items-center gap-2 border-b border-stone-200 px-3 py-2 dark:border-stone-800" (submit)="applyLink($event)">
2471
+ <label class="sr-only" for="wind-link">Link URL</label>
2472
+ <input
2473
+ id="wind-link"
2474
+ class="h-9 min-w-0 flex-1 rounded-md border border-stone-300 bg-white px-2 text-sm outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900"
2475
+ [value]="linkDraft()"
2476
+ placeholder="https://"
2477
+ (input)="onDraft($event, 'link')"
2478
+ />
2479
+ <button type="submit" class="h-9 rounded-md bg-teal-700 px-3 text-sm font-medium text-white">Apply</button>
2480
+ <button type="button" class="h-9 rounded-md px-2 text-sm text-stone-600 dark:text-stone-300" (click)="clearLink()">Remove</button>
2481
+ </form>
2482
+ }
2483
+
2484
+ @if (imageOpen()) {
2485
+ <form class="flex flex-wrap items-center gap-2 border-b border-stone-200 px-3 py-2 dark:border-stone-800" (submit)="applyImage($event)">
2486
+ <input class="h-9 min-w-0 flex-1 rounded-md border border-stone-300 bg-white px-2 text-sm outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900" [value]="imageSrc()" placeholder="Image URL" (input)="onDraft($event, 'src')" />
2487
+ <input class="h-9 w-36 rounded-md border border-stone-300 bg-white px-2 text-sm outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900" [value]="imageAlt()" placeholder="Alt text" (input)="onDraft($event, 'alt')" />
2488
+ <button type="submit" class="h-9 rounded-md bg-teal-700 px-3 text-sm font-medium text-white">Insert</button>
2489
+ </form>
2490
+ }
2491
+
2492
+ <div class="relative min-h-48" #surface></div>
2493
+
2494
+ @if (slashItems().length > 0 && change()?.slashRect; as rect) {
2495
+ <ul
2496
+ class="fixed z-30 max-h-64 w-56 overflow-auto rounded-lg border border-stone-200 bg-white py-1 shadow-lg dark:border-stone-700 dark:bg-stone-900"
2497
+ role="listbox"
2498
+ [style.top.px]="rect.top"
2499
+ [style.left.px]="rect.left"
2500
+ >
2501
+ @for (item of slashItems(); track item.id; let index = $index) {
2502
+ <li>
2503
+ <button
2504
+ type="button"
2505
+ class="flex w-full items-baseline justify-between gap-3 px-3 py-1.5 text-left text-sm hover:bg-stone-100 dark:hover:bg-stone-800"
2506
+ [class.bg-teal-700]="index === slashIndex()"
2507
+ [class.text-white]="index === slashIndex()"
2508
+ role="option"
2509
+ [attr.aria-selected]="index === slashIndex()"
2510
+ (mousedown)="$event.preventDefault()"
2511
+ (click)="pickSlash(item)"
2512
+ >
2513
+ <span>{{ item.label }}</span>
2514
+ <span class="text-xs opacity-70">{{ item.hint }}</span>
2515
+ </button>
2516
+ </li>
2517
+ }
2518
+ </ul>
2519
+ }
2520
+
2521
+ <footer class="flex items-center justify-between gap-3 border-t border-stone-200 px-3 py-1.5 text-xs text-stone-500 dark:border-stone-800 dark:text-stone-400">
2522
+ <span>{{ change()?.words ?? 0 }} words</span>
2523
+ <span class="text-stone-400">/ for blocks</span>
2524
+ </footer>
2525
+
2526
+ @if (sourceOpen()) {
2527
+ <div class="border-t border-stone-200 p-3 dark:border-stone-800">
2528
+ <textarea
2529
+ class="h-40 w-full resize-y rounded-md border border-stone-300 bg-stone-50 p-2 font-mono text-xs outline-none focus:border-teal-700 dark:border-stone-700 dark:bg-stone-900"
2530
+ [value]="sourceDraft()"
2531
+ spellcheck="false"
2532
+ aria-label="HTML source"
2533
+ (input)="onDraft($event, 'source')"
2534
+ ></textarea>
2535
+ <button type="button" class="mt-2 h-9 rounded-md bg-stone-900 px-3 text-sm text-white dark:bg-stone-100 dark:text-stone-900" (click)="applySource()">Apply HTML</button>
2536
+ </div>
2537
+ }
2538
+ </div>
2539
+ `,
2540
+ styles: [":host{display:block;min-width:0}.wind-frame{position:relative;min-width:0}.wind-doc{min-height:12rem;padding:1rem 1.15rem 1.5rem;outline:none;color:var(--color-stone-900, #1c1917);font-family:var(--font-serif, Georgia, \"Iowan Old Style\", \"Palatino Linotype\", serif);font-size:1.0625rem;line-height:1.65;caret-color:var(--color-teal-700, #0f766e);overflow-wrap:anywhere}.wind-doc>*+*{margin-top:.75rem}.wind-doc p,.wind-doc h1,.wind-doc h2,.wind-doc h3,.wind-doc blockquote,.wind-doc pre,.wind-doc li{margin:0}.wind-doc h1,.wind-doc h2,.wind-doc h3{font-family:var(--font-sans, ui-sans-serif, system-ui, sans-serif);line-height:1.2;letter-spacing:-.03em;color:var(--color-stone-950, #0c0a09)}.wind-doc h1{font-size:1.875rem;font-weight:600}.wind-doc h2{font-size:1.5rem;font-weight:600}.wind-doc h3{font-size:1.25rem;font-weight:600}.wind-doc blockquote{border-left:2px solid var(--color-teal-700, #0f766e);padding-left:.9rem;color:var(--color-stone-600, #57534e)}.wind-doc ul,.wind-doc ol{margin:0;padding-left:1.25rem}.wind-doc li+li{margin-top:.25rem}.wind-doc pre{overflow-x:auto;border-radius:.6rem;background:color-mix(in oklab,var(--color-stone-900, #1c1917) 6%,transparent);padding:.8rem .9rem;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.875rem;line-height:1.55;white-space:pre-wrap}.wind-doc pre code{font:inherit;background:none;padding:0}.wind-doc a{color:var(--color-teal-800, #115e59);text-decoration:underline;text-underline-offset:2px}.wind-doc code{border-radius:.3rem;background:color-mix(in oklab,var(--color-stone-900, #1c1917) 8%,transparent);padding:.05em .3em;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.88em}.wind-doc img{display:block;max-width:100%;border-radius:.6rem}.wind-doc figure{margin:0}.wind-doc hr{border:0;border-top:1px solid var(--color-stone-300, #d6d3d1);margin:.25rem 0}.wind-doc [data-atomic].is-selected{outline:2px solid var(--color-teal-700, #0f766e);outline-offset:3px;border-radius:.4rem}.wind-doc[data-empty=true]>p[data-placeholder]:before{content:attr(data-placeholder);float:left;height:0;pointer-events:none;color:var(--color-stone-400, #a8a29e)}.wind-carets{position:absolute;inset:0;pointer-events:none;overflow:hidden}.wind-caret,.wind-remote{position:absolute;pointer-events:none}.wind-caret{width:2px;background:var(--peer);border-radius:1px}.wind-caret i{position:absolute;top:-1.15rem;left:-1px;border-radius:.3rem;background:var(--peer);color:#fff;padding:.05rem .3rem;font-family:var(--font-sans, ui-sans-serif, system-ui, sans-serif);font-size:.65rem;font-style:normal;line-height:1.3;letter-spacing:.01em;white-space:nowrap}.wind-remote{background:color-mix(in oklab,var(--peer) 32%,transparent);border-radius:.1rem}.wind-frame.is-readonly .wind-doc{caret-color:transparent}.dark .wind-doc{color:var(--color-stone-100, #f5f5f4);caret-color:var(--color-teal-300, #5eead4)}.dark .wind-doc h1,.dark .wind-doc h2,.dark .wind-doc h3{color:var(--color-stone-50, #fafaf9)}.dark .wind-doc blockquote{color:var(--color-stone-400, #a8a29e)}.dark .wind-doc a{color:var(--color-teal-300, #5eead4)}.dark .wind-doc hr{border-top-color:var(--color-stone-700, #44403c)}.dark .wind-doc pre,.dark .wind-doc code{background:color-mix(in oklab,white 8%,transparent)}\n"]
2541
+ }]
2542
+ }],
2543
+ ctorParameters: () => [],
2544
+ propDecorators: {
2545
+ html: [{
2546
+ type: i0.Input,
2547
+ args: [{
2548
+ isSignal: true,
2549
+ alias: "html",
2550
+ required: false
2551
+ }]
2552
+ }, {
2553
+ type: i0.Output,
2554
+ args: ["htmlChange"]
2555
+ }],
2556
+ placeholder: [{
2557
+ type: i0.Input,
2558
+ args: [{
2559
+ isSignal: true,
2560
+ alias: "placeholder",
2561
+ required: false
2562
+ }]
2563
+ }],
2564
+ readOnly: [{
2565
+ type: i0.Input,
2566
+ args: [{
2567
+ isSignal: true,
2568
+ alias: "readonly",
2569
+ required: false
2570
+ }]
2571
+ }],
2572
+ showToolbar: [{
2573
+ type: i0.Input,
2574
+ args: [{
2575
+ isSignal: true,
2576
+ alias: "showToolbar",
2577
+ required: false
2578
+ }]
2579
+ }],
2580
+ toolbar: [{
2581
+ type: i0.Input,
2582
+ args: [{
2583
+ isSignal: true,
2584
+ alias: "toolbar",
2585
+ required: false
2586
+ }]
2587
+ }],
2588
+ surfaceRef: [{
2589
+ type: i0.ViewChild,
2590
+ args: ["surface", { isSignal: true }]
2591
+ }]
2592
+ }
2593
+ });
2594
+ export { SLASH_ITEMS, WIND_TOOLBAR_PRESETS, WIND_TOOLS, WindEditor, WindSession, filterSlash, isToolbarPreset, isWindTool, loadHtml, readBlocks, resolveToolbar };
2595
+
2596
+ //# sourceMappingURL=codeauradev-ngx-wind-editor.mjs.map