@ai-markdown/core 3.3.0 → 3.4.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.
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ let _ai_markdown_code_language_detector = require("@ai-markdown/code-language-detector");
3
4
  function extractCode(node) {
4
5
  if (node?.tagName !== "pre" || node.children.length !== 1 || Object.keys(node.properties).length) return null;
5
6
  const child = node.children[0];
@@ -45,19 +46,26 @@ function text(node) {
45
46
  if (node.tagName === "img" || node.tagName === "cross-chunk-image") return String(node.properties.alt ?? "");
46
47
  return node.children.map(text).join("");
47
48
  }
49
+ var ExportRefused = class extends Error {
50
+ reason;
51
+ constructor(reason) {
52
+ super(reason);
53
+ this.reason = reason;
54
+ }
55
+ };
48
56
  function projectTable(node) {
49
57
  try {
50
- if (node?.tagName !== "table") throw new Error("Table source is unavailable");
58
+ if (node?.tagName !== "table") throw new ExportRefused("unavailable");
51
59
  const rows = [];
52
60
  const walk = (parent) => {
53
61
  for (const child of parent.children) {
54
62
  if (child.type === "text" && !child.value.trim()) continue;
55
- if (child.type !== "element") throw new Error("Unsupported table content");
63
+ if (child.type !== "element") throw new ExportRefused("unsupportedContent");
56
64
  if (child.tagName === "tr") {
57
65
  const cells = [];
58
66
  for (const cell of child.children) {
59
67
  if (cell.type === "text" && !cell.value.trim()) continue;
60
- if (cell.type !== "element" || !["th", "td"].includes(cell.tagName) || Number(cell.properties.colSpan ?? 1) !== 1 || Number(cell.properties.rowSpan ?? 1) !== 1) throw new Error("Only tables without merged cells can be exported");
68
+ if (cell.type !== "element" || !["th", "td"].includes(cell.tagName) || Number(cell.properties.colSpan ?? 1) !== 1 || Number(cell.properties.rowSpan ?? 1) !== 1) throw new ExportRefused("mergedCells");
61
69
  cells.push(text(cell));
62
70
  }
63
71
  rows.push(cells);
@@ -66,14 +74,14 @@ function projectTable(node) {
66
74
  "tbody",
67
75
  "tfoot"
68
76
  ].includes(child.tagName) && parent === node) walk(child);
69
- else throw new Error("Unsupported table structure");
77
+ else throw new ExportRefused("unsupportedStructure");
70
78
  }
71
79
  };
72
80
  walk(node);
73
- if (!rows.length || !rows[0].length || rows.some((r) => r.length !== rows[0].length)) throw new Error("Only rectangular tables can be exported");
81
+ if (!rows.length || !rows[0].length || rows.some((r) => r.length !== rows[0].length)) throw new ExportRefused("notRectangular");
74
82
  return { rows };
75
83
  } catch (error) {
76
- return { reason: error instanceof Error ? error.message : "Table cannot be exported" };
84
+ return { reason: error instanceof ExportRefused ? error.reason : "unavailable" };
77
85
  }
78
86
  }
79
87
  const formulaPrefix = /^[\s\u0000-\u001f]*[=+@-]/;
@@ -83,6 +91,57 @@ function serializeTable(rows, delimiter = ",") {
83
91
  return /["\r\n\t,]/.test(value) ? `"${value.replaceAll("\"", "\"\"")}"` : value;
84
92
  }).join(delimiter)).join("\r\n");
85
93
  }
94
+ function codeBody(code) {
95
+ return code.endsWith("\n") ? code.slice(0, -1) : code;
96
+ }
97
+ function startsWithText(text, prefix) {
98
+ return text.length >= prefix.length && text.slice(0, prefix.length) === prefix;
99
+ }
100
+ function extendsCode(next, previous) {
101
+ return startsWithText(next, codeBody(previous));
102
+ }
103
+ function createCodeLanguageTracker(onSettle) {
104
+ const detector = new _ai_markdown_code_language_detector.StreamingLanguageDetector();
105
+ let followed;
106
+ let settled = null;
107
+ let timer;
108
+ const cancel = () => {
109
+ if (timer !== void 0) clearTimeout(timer);
110
+ timer = void 0;
111
+ };
112
+ const judge = (from, text, streaming) => (streaming && settled !== text ? from.update(text) : from.finalize(text)).language;
113
+ return {
114
+ read(code, streaming) {
115
+ if (followed !== void 0 && extendsCode(code, followed)) return detector.current.language;
116
+ return judge(new _ai_markdown_code_language_detector.StreamingLanguageDetector(), codeBody(code), streaming);
117
+ },
118
+ observe(code, streaming) {
119
+ cancel();
120
+ const text = codeBody(code);
121
+ const starting = followed === void 0 || !extendsCode(code, followed);
122
+ if (starting) detector.reset();
123
+ const before = detector.current.language;
124
+ followed = code;
125
+ const after = judge(detector, text, streaming);
126
+ if (streaming && settled !== text) timer = setTimeout(() => {
127
+ timer = void 0;
128
+ settled = text;
129
+ detector.finalize(text);
130
+ onSettle();
131
+ }, 300);
132
+ return !starting && before !== after;
133
+ },
134
+ dispose: cancel
135
+ };
136
+ }
137
+ function nextCodeFrame(previous, code, language) {
138
+ if (previous?.code === code && previous.language === language) return previous;
139
+ return {
140
+ code,
141
+ language,
142
+ generation: previous && language === previous.language && extendsCode(code, previous.code) ? previous.generation : {}
143
+ };
144
+ }
86
145
  function createCodeFrame(initial, publish) {
87
146
  let shown = initial;
88
147
  let latest = initial;
@@ -100,7 +159,7 @@ function createCodeFrame(initial, publish) {
100
159
  };
101
160
  return {
102
161
  update(next, streaming, interval) {
103
- const replaced = next.language !== latest.language || !next.code.startsWith(latest.code);
162
+ const replaced = next.generation !== latest.generation;
104
163
  latest = next;
105
164
  if (delay !== interval) cancel();
106
165
  delay = interval;
@@ -162,16 +221,17 @@ function createJsonCompletenessScanner() {
162
221
  let invalid = false;
163
222
  let last = "";
164
223
  return (text) => {
224
+ const body = codeBody(text);
165
225
  let from = previous.length;
166
- if (!text.startsWith(previous)) {
226
+ if (!startsWithText(body, previous)) {
167
227
  from = 0;
168
228
  depth = 0;
169
229
  quoted = escaped = invalid = false;
170
230
  last = "";
171
231
  }
172
- previous = text;
173
- for (let i = from; i < text.length; i++) {
174
- const c = text[i];
232
+ previous = body;
233
+ for (let i = from; i < body.length; i++) {
234
+ const c = body[i];
175
235
  if (!/\s/.test(c)) last = c;
176
236
  if (quoted) {
177
237
  if (escaped) escaped = false;
@@ -190,6 +250,89 @@ function createJsonCompletenessScanner() {
190
250
  function jsonLooksComplete(text) {
191
251
  return createJsonCompletenessScanner()(text);
192
252
  }
253
+ function createCodeBlockSession(onChange) {
254
+ let committed;
255
+ let shown;
256
+ let frames;
257
+ let token;
258
+ let committing = false;
259
+ const tracker = createCodeLanguageTracker(() => onChange());
260
+ const scanJson = createJsonCompletenessScanner();
261
+ let formatted;
262
+ const generationOf = (input) => {
263
+ if (!committed) return 0;
264
+ return input.identity === committed.identity && input.language === committed.language && extendsCode(input.code, committed.code) ? committed.generation : committed.generation + 1;
265
+ };
266
+ const languageOf = (input) => input.language || (input.autoDetect ? tracker.read(input.code, input.streaming) ?? "" : "");
267
+ const pretty = (source, nested) => {
268
+ if (formatted?.source !== source || formatted.nested !== nested) formatted = {
269
+ source,
270
+ nested,
271
+ text: prettyPrintJson(source, nested)
272
+ };
273
+ return formatted.text;
274
+ };
275
+ return {
276
+ read(input) {
277
+ const generation = generationOf(input);
278
+ const language = languageOf(input);
279
+ const displayed = !shown || !input.streaming || input.highlightIntervalMs === 0 || shown.generation !== generation || shown.language !== language ? input.code : shown.code;
280
+ return {
281
+ text: input.formatJson && displayed && language === "json" && (!input.streaming || scanJson(displayed)) ? pretty(displayed, input.expandNestedJson) : displayed,
282
+ language,
283
+ generation
284
+ };
285
+ },
286
+ commit(input) {
287
+ const generation = generationOf(input);
288
+ const moved = input.autoDetect && !input.language ? tracker.observe(input.code, input.streaming) : (tracker.dispose(), false);
289
+ const language = languageOf(input);
290
+ committed = {
291
+ code: input.code,
292
+ language: input.language,
293
+ identity: input.identity,
294
+ generation
295
+ };
296
+ const key = `${generation}:${language}`;
297
+ if (token?.key !== key) token = {
298
+ key,
299
+ value: {}
300
+ };
301
+ const frame = {
302
+ code: input.code,
303
+ language,
304
+ generation: token.value
305
+ };
306
+ committing = true;
307
+ try {
308
+ if (!frames) {
309
+ shown = {
310
+ code: input.code,
311
+ language,
312
+ generation
313
+ };
314
+ frames = createCodeFrame(frame, (next) => {
315
+ shown = {
316
+ code: next.code,
317
+ language: next.language,
318
+ generation: committed.generation
319
+ };
320
+ if (!committing) onChange();
321
+ });
322
+ }
323
+ frames.update(frame, input.streaming, input.highlightIntervalMs);
324
+ if (shown.code === input.code && shown.language === language) shown.generation = generation;
325
+ } finally {
326
+ committing = false;
327
+ }
328
+ if (moved) onChange();
329
+ },
330
+ dispose() {
331
+ frames?.dispose();
332
+ tracker.dispose();
333
+ }
334
+ };
335
+ }
193
336
  function createRenderQueue() {
194
337
  const pending = /* @__PURE__ */ new Map();
195
338
  let running = false;
@@ -231,9 +374,6 @@ function createRenderQueue() {
231
374
  const mermaidRenderQueue = createRenderQueue();
232
375
  const SAFE_SECURITY_LEVELS = /* @__PURE__ */ new Set(["strict", "sandbox"]);
233
376
  let ownedThemes = /* @__PURE__ */ new WeakMap();
234
- const resetMermaidInitializationForTests = () => {
235
- ownedThemes = /* @__PURE__ */ new WeakMap();
236
- };
237
377
  const readSiteConfig = (mermaid) => {
238
378
  const api = mermaid.mermaidAPI;
239
379
  return api?.getSiteConfig?.() ?? api?.getConfig?.() ?? {};
@@ -271,94 +411,265 @@ const ensureMermaidInitialized = (mermaid, isDark) => {
271
411
  }
272
412
  mermaid.initialize(next);
273
413
  };
274
- let nextId = 0;
414
+ function diagramErrorText(error, labels) {
415
+ if (error.reason === "tooLarge") return labels.tooLarge(error.length, error.limit);
416
+ return error.message || labels.renderFailed;
417
+ }
418
+ const ERROR_MESSAGE_MAX_CHARS = 500;
419
+ const LOAD_RETRIES = 3;
420
+ const LOAD_RETRY_MS = 1500;
421
+ function engineMessage(error) {
422
+ const text = (error instanceof Error ? error.message : typeof error === "string" ? error : "").replace(/\r\n?/g, "\n").trim();
423
+ return text.length > ERROR_MESSAGE_MAX_CHARS ? `${text.slice(0, ERROR_MESSAGE_MAX_CHARS)}…` : text;
424
+ }
425
+ var DiagramFailure = class {
426
+ view;
427
+ constructor(view) {
428
+ this.view = view;
429
+ }
430
+ };
431
+ const diagramId = () => `mermaid-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
275
432
  function createDiagramController(load, publish) {
276
433
  const owner = {};
434
+ let request;
435
+ let current;
436
+ let shown;
437
+ let frames;
438
+ let updating = false;
439
+ let view = { kind: "source" };
440
+ let loadFailed = false;
441
+ let previousStreaming = false;
442
+ let needsCorrective = false;
443
+ let lastSuccess;
444
+ let loadFailures = 0;
445
+ let nonce = 0;
446
+ let ran;
277
447
  let epoch = 0;
278
- let previous;
279
- let svg = "";
280
- let bind;
281
- let timer;
448
+ let retryTimer;
449
+ const sameError = (a, b) => a.reason === "tooLarge" ? b.reason === "tooLarge" && a.length === b.length && a.limit === b.limit : b.reason === a.reason && a.message === b.message;
450
+ const emit = () => publish({
451
+ view,
452
+ loadFailed
453
+ });
454
+ const show = (next) => {
455
+ if (next.kind === "source" && view.kind === "source") return;
456
+ if (next.kind === "error" && view.kind === "error" && sameError(next, view)) return;
457
+ view = next;
458
+ emit();
459
+ };
282
460
  const cancel = () => {
283
461
  epoch++;
284
- if (timer !== void 0) clearTimeout(timer);
285
- timer = void 0;
286
462
  mermaidRenderQueue.cancel(owner);
463
+ if (retryTimer !== void 0) clearTimeout(retryTimer);
464
+ retryTimer = void 0;
287
465
  };
288
- return {
289
- update(request) {
290
- const replaced = !previous || previous.resetKey !== request.resetKey || previous.dark !== request.dark || !request.code.startsWith(previous.code);
291
- const pending = timer !== void 0;
292
- epoch++;
293
- mermaidRenderQueue.cancel(owner);
294
- if (replaced) {
295
- svg = "";
296
- bind = void 0;
297
- if (timer !== void 0) clearTimeout(timer);
298
- timer = void 0;
299
- }
300
- previous = request;
301
- if (!request.active) {
302
- cancel();
303
- return;
466
+ const attempt = async (code, dark, streaming, generation) => {
467
+ const stale = () => generation !== epoch;
468
+ let engine;
469
+ try {
470
+ engine = await load();
471
+ } catch {
472
+ if (stale()) return;
473
+ if (loadFailures < LOAD_RETRIES) {
474
+ loadFailures += 1;
475
+ retryTimer = setTimeout(() => {
476
+ retryTimer = void 0;
477
+ nonce++;
478
+ run();
479
+ }, LOAD_RETRY_MS);
480
+ } else if (!loadFailed) {
481
+ loadFailed = true;
482
+ emit();
304
483
  }
305
- publish({
306
- status: "loading",
484
+ return;
485
+ }
486
+ loadFailures = 0;
487
+ let reason = "render";
488
+ try {
489
+ if (stale()) return;
490
+ ensureMermaidInitialized(engine, dark);
491
+ const maxTextSize = getMermaidMaxTextSize(engine);
492
+ if (code.length > maxTextSize) throw new DiagramFailure({
493
+ kind: "error",
494
+ reason: "tooLarge",
495
+ length: code.length,
496
+ limit: maxTextSize
497
+ });
498
+ reason = "parse";
499
+ if (!await engine.parse(code)) throw new DiagramFailure({
500
+ kind: "error",
501
+ reason: "parse",
502
+ message: ""
503
+ });
504
+ reason = "render";
505
+ if (stale()) return;
506
+ const { svg, bindFunctions, diagramType } = await engine.render(diagramId(), code);
507
+ if (stale()) return;
508
+ needsCorrective = false;
509
+ lastSuccess = {
510
+ code,
511
+ dark
512
+ };
513
+ loadFailed = false;
514
+ show({
515
+ kind: "diagram",
307
516
  svg,
308
- bind
517
+ chartType: diagramType ?? "",
518
+ bind: bindFunctions
309
519
  });
310
- const attempt = () => {
311
- timer = void 0;
312
- const current = previous;
313
- const generation = epoch;
314
- const valid = () => generation === epoch;
315
- let phase = "load";
316
- load().then((engine) => {
317
- phase = "render";
318
- if (!valid()) return;
319
- return mermaidRenderQueue.enqueue(owner, async () => {
320
- if (!valid()) return;
321
- ensureMermaidInitialized(engine, current.dark);
322
- if (current.code.length > getMermaidMaxTextSize(engine)) throw new Error("Diagram exceeds Mermaid’s maximum text size");
323
- const parsed = await engine.parse(current.code, { suppressErrors: true });
324
- if (!valid()) return;
325
- if (parsed === false) throw new Error("Diagram syntax is incomplete or invalid");
326
- const result = await engine.render(`aimd-diagram-${++nextId}`, current.code);
327
- if (!valid()) return;
328
- svg = result.svg;
329
- bind = result.bindFunctions;
330
- publish({
331
- status: "ready",
332
- svg,
333
- bind
334
- });
335
- });
336
- }).catch((error) => {
337
- if (valid()) publish({
338
- status: current.streaming && phase === "render" ? "loading" : "error",
339
- svg,
340
- bind,
341
- error: (phase === "load" ? "Diagram engine could not load: " : "") + (error instanceof Error ? error.message : String(error)).slice(0, 500)
342
- });
343
- });
520
+ } catch (error) {
521
+ if (stale()) return;
522
+ if (streaming) return;
523
+ const failure = error instanceof DiagramFailure ? error.view : {
524
+ kind: "error",
525
+ reason,
526
+ message: engineMessage(error)
344
527
  };
345
- if (!request.streaming || request.interval <= 0) {
346
- if (timer !== void 0) clearTimeout(timer);
347
- attempt();
348
- } else if (!pending || replaced) timer = setTimeout(attempt, request.interval);
528
+ if (needsCorrective) {
529
+ needsCorrective = false;
530
+ show(failure);
531
+ return;
532
+ }
533
+ if (view.kind === "diagram") return;
534
+ show(failure);
535
+ }
536
+ };
537
+ function run() {
538
+ if (!request || !current || !shown) return;
539
+ const { dark, active, streaming, interval } = request;
540
+ const code = !streaming || interval === 0 || current.generation !== shown.generation ? request.code : shown.code;
541
+ if (ran && ran.code === code && ran.dark === dark && ran.active === active && ran.streaming === streaming && ran.nonce === nonce) return;
542
+ ran = {
543
+ code,
544
+ dark,
545
+ active,
546
+ streaming,
547
+ nonce
548
+ };
549
+ cancel();
550
+ if (streaming && !previousStreaming) {
551
+ needsCorrective = false;
552
+ lastSuccess = void 0;
553
+ loadFailures = 0;
554
+ if (view.kind !== "source" || loadFailed) {
555
+ loadFailed = false;
556
+ view = { kind: "source" };
557
+ emit();
558
+ }
559
+ } else if (!streaming && previousStreaming) needsCorrective = true;
560
+ previousStreaming = streaming;
561
+ if (!code || !active) return;
562
+ if (lastSuccess?.code === code && lastSuccess.dark === dark) {
563
+ needsCorrective = false;
564
+ return;
565
+ }
566
+ const generation = epoch;
567
+ mermaidRenderQueue.enqueue(owner, () => attempt(code, dark, streaming, generation));
568
+ }
569
+ return {
570
+ update(next) {
571
+ request = next;
572
+ updating = true;
573
+ try {
574
+ if (!frames || !current) {
575
+ current = shown = nextCodeFrame(void 0, next.code, "");
576
+ frames = createCodeFrame(current, (frame) => {
577
+ shown = frame;
578
+ if (!updating) run();
579
+ });
580
+ } else current = nextCodeFrame(current, next.code, "");
581
+ frames.update(current, next.streaming, next.interval);
582
+ } finally {
583
+ updating = false;
584
+ }
585
+ run();
349
586
  },
350
- dispose: cancel
587
+ retry() {
588
+ loadFailures = 0;
589
+ if (loadFailed) {
590
+ loadFailed = false;
591
+ emit();
592
+ }
593
+ nonce++;
594
+ run();
595
+ },
596
+ dispose() {
597
+ cancel();
598
+ frames?.dispose();
599
+ ran = void 0;
600
+ }
351
601
  };
352
602
  }
603
+ function resolveLabels(defaults, option) {
604
+ const overrides = typeof option === "function" ? option() : option;
605
+ if (!overrides) return defaults;
606
+ const labels = { ...defaults };
607
+ for (const key of Object.keys(overrides)) {
608
+ const value = overrides[key];
609
+ if (value !== void 0) labels[key] = value;
610
+ }
611
+ return labels;
612
+ }
613
+ const defaultCodeBlockLabels = Object.freeze({
614
+ actions: "Code block actions",
615
+ plainText: "text",
616
+ copy: "Copy code",
617
+ copied: "Copied",
618
+ copyFailed: "Copy failed",
619
+ showSource: "Show source",
620
+ showPreview: "Show preview",
621
+ expand: "Expand",
622
+ collapse: "Collapse",
623
+ previewFailed: "Preview failed. Source is available below."
624
+ });
625
+ const defaultDiagramLabels = Object.freeze({
626
+ waiting: "Waiting for diagram…",
627
+ rendering: "Rendering diagram…",
628
+ loadFailed: "Diagram engine could not load.",
629
+ retry: "Retry diagram",
630
+ renderFailed: "Mermaid could not render this diagram.",
631
+ tooLarge: (length, limit) => `Diagram source is ${length} characters, above mermaid's maxTextSize of ${limit}.`
632
+ });
633
+ const defaultImageLabels = Object.freeze({
634
+ close: "Close preview",
635
+ prev: "Previous image",
636
+ next: "Next image",
637
+ rotateLeft: "Rotate left",
638
+ rotateRight: "Rotate right",
639
+ zoomIn: "Zoom in",
640
+ zoomOut: "Zoom out",
641
+ flipX: "Flip horizontally",
642
+ flipY: "Flip vertically",
643
+ loading: "Loading image…",
644
+ loadFailed: "Image could not be loaded.",
645
+ open: (alt) => `Preview image${alt ? `: ${alt}` : ""}`,
646
+ preview: "Image preview",
647
+ actions: "Image preview actions"
648
+ });
649
+ const defaultTableLabels = Object.freeze({
650
+ actions: "Table actions",
651
+ copy: "Copy table",
652
+ download: "Download CSV",
653
+ copied: "Table copied",
654
+ copyFailed: "Copy failed",
655
+ scrollRegion: "Scrollable table",
656
+ mergedCells: "Only tables without merged cells can be exported",
657
+ notRectangular: "Only rectangular tables can be exported",
658
+ unsupportedStructure: "Unsupported table structure",
659
+ unsupportedContent: "Unsupported table content",
660
+ unavailable: "Table source is unavailable"
661
+ });
353
662
  const locks = /* @__PURE__ */ new WeakMap();
354
- function lockImagePreviewScroll(target) {
663
+ function lockImagePreviewScroll(target, scrollbarWidth = 0) {
355
664
  let lock = locks.get(target);
356
665
  if (!lock) {
357
666
  lock = {
358
667
  count: 0,
359
- overflow: target.style.overflow
668
+ overflow: target.style.overflow,
669
+ paddingRight: target.style.paddingRight
360
670
  };
361
671
  locks.set(target, lock);
672
+ if (scrollbarWidth > 0) target.style.paddingRight = lock.paddingRight ? `calc(${lock.paddingRight} + ${scrollbarWidth}px)` : `${scrollbarWidth}px`;
362
673
  }
363
674
  lock.count++;
364
675
  target.style.overflow = "hidden";
@@ -368,10 +679,374 @@ function lockImagePreviewScroll(target) {
368
679
  released = true;
369
680
  if (--lock.count === 0) {
370
681
  if (target.style.overflow === "hidden") target.style.overflow = lock.overflow;
682
+ target.style.paddingRight = lock.paddingRight;
371
683
  locks.delete(target);
372
684
  }
373
685
  };
374
686
  }
687
+ const initialImageTransform = () => ({
688
+ x: 0,
689
+ y: 0,
690
+ rotate: 0,
691
+ scale: 1,
692
+ flipX: false,
693
+ flipY: false
694
+ });
695
+ function imageTransformStyle(transform) {
696
+ const { x, y, scale, rotate, flipX, flipY } = transform;
697
+ return `translate3d(${x}px, ${y}px, 0) scale3d(${flipX ? "-" : ""}${scale}, ${flipY ? "-" : ""}${scale}, 1) rotate(${rotate}deg)`;
698
+ }
699
+ function zoomImage(transform, ratio, geometry, centerX = geometry.viewportWidth / 2, centerY = geometry.viewportHeight / 2, touch = false) {
700
+ if (!Number.isFinite(ratio) || ratio <= 0) return transform;
701
+ const scale = Math.min(50, touch ? Math.max(.01, transform.scale * ratio) : Math.max(1, transform.scale * ratio));
702
+ const difference = scale / transform.scale - 1;
703
+ let x = transform.x - difference * (centerX - transform.x - geometry.left - geometry.width / 2);
704
+ let y = transform.y - difference * (centerY - transform.y - geometry.top - geometry.height / 2);
705
+ if (ratio < 1 && scale === 1 && geometry.width <= geometry.viewportWidth && geometry.height <= geometry.viewportHeight) x = y = 0;
706
+ return {
707
+ ...transform,
708
+ x,
709
+ y,
710
+ scale
711
+ };
712
+ }
713
+ function imageWheelRatio(delta, mode = 0, pinch = false) {
714
+ if (!Number.isFinite(delta) || delta === 0) return 1;
715
+ if (pinch) return Math.min(1.5, Math.max(1 / 1.5, Math.exp(-delta / 100)));
716
+ const pixels = mode === 1 ? delta * 100 / 3 : mode === 2 ? delta * 100 : delta;
717
+ const ratio = 1 + Math.min(Math.abs(pixels / 100), 1) * .5;
718
+ return pixels > 0 ? 1 / ratio : ratio;
719
+ }
720
+ function reboundImage(transform, geometry, bounds) {
721
+ if (transform.scale < 1) return {
722
+ ...transform,
723
+ x: 0,
724
+ y: 0,
725
+ scale: 1
726
+ };
727
+ const rotated = transform.rotate % 180 !== 0;
728
+ const width = (rotated ? geometry.height : geometry.width) * transform.scale;
729
+ const height = (rotated ? geometry.width : geometry.height) * transform.scale;
730
+ if (width <= geometry.viewportWidth && height <= geometry.viewportHeight) return {
731
+ ...transform,
732
+ x: 0,
733
+ y: 0
734
+ };
735
+ const fix = (position, start, size, viewport) => {
736
+ const offset = (size - viewport) / 2;
737
+ if (size > viewport) {
738
+ if (start > 0) return offset;
739
+ if (start < 0 && start + size < viewport) return -offset;
740
+ } else if (start < 0 || start + size > viewport) return start < 0 ? offset : -offset;
741
+ return position;
742
+ };
743
+ return {
744
+ ...transform,
745
+ x: fix(transform.x, bounds.left, width, geometry.viewportWidth),
746
+ y: fix(transform.y, bounds.top, height, geometry.viewportHeight)
747
+ };
748
+ }
749
+ function imagePinchCenter(oldA, oldB, a, b) {
750
+ const distanceA = Math.hypot(a.x - oldA.x, a.y - oldA.y), distanceB = Math.hypot(b.x - oldB.x, b.y - oldB.y);
751
+ const ratio = distanceA + distanceB ? distanceA / (distanceA + distanceB) : 0;
752
+ return {
753
+ x: oldA.x + ratio * (oldB.x - oldA.x),
754
+ y: oldA.y + ratio * (oldB.y - oldA.y)
755
+ };
756
+ }
757
+ const ZOOM_STEP = 1.5;
758
+ const MAX_SCALE = 50;
759
+ const CLOSE_MS = 300;
760
+ const closedState = Object.freeze({
761
+ phase: "closed",
762
+ items: Object.freeze([]),
763
+ current: -1,
764
+ item: void 0,
765
+ transform: initialImageTransform(),
766
+ status: "loading",
767
+ moving: false,
768
+ canPrevious: false,
769
+ canNext: false,
770
+ canZoomIn: false,
771
+ canZoomOut: false
772
+ });
773
+ function createImagePreviewController(host, publish, options = {}) {
774
+ let phase = "closed";
775
+ let items = [];
776
+ let selected;
777
+ let current = -1;
778
+ let transform = initialImageTransform();
779
+ let status = "loading";
780
+ let moving = false;
781
+ let unsubscribe;
782
+ let cancelPublish;
783
+ let cancelOpening;
784
+ let closeTimer;
785
+ const pointers = /* @__PURE__ */ new Map();
786
+ let grip;
787
+ let displaced = false;
788
+ const snapshot = () => {
789
+ if (phase === "closed") return closedState;
790
+ const ready = status === "ready";
791
+ return {
792
+ phase,
793
+ items,
794
+ current,
795
+ item: items[current],
796
+ transform,
797
+ status,
798
+ moving,
799
+ canPrevious: current > 0,
800
+ canNext: current < items.length - 1,
801
+ canZoomIn: ready && transform.scale < MAX_SCALE,
802
+ canZoomOut: ready && transform.scale > 1
803
+ };
804
+ };
805
+ const emit = () => {
806
+ cancelPublish?.();
807
+ cancelPublish = void 0;
808
+ publish(snapshot());
809
+ };
810
+ const emitNextFrame = () => {
811
+ cancelPublish ??= host.frame(() => {
812
+ cancelPublish = void 0;
813
+ publish(snapshot());
814
+ });
815
+ };
816
+ const endGesture = () => {
817
+ pointers.clear();
818
+ grip = void 0;
819
+ displaced = false;
820
+ moving = false;
821
+ };
822
+ const startImage = () => {
823
+ transform = initialImageTransform();
824
+ status = "loading";
825
+ endGesture();
826
+ };
827
+ const zoom = (ratio, x, y, touch = false) => {
828
+ const box = host.geometry();
829
+ if (box && status === "ready") transform = zoomImage(transform, ratio, box, x, y, touch);
830
+ };
831
+ const rebound = () => {
832
+ if (transform.scale < 1) {
833
+ transform = {
834
+ ...transform,
835
+ x: 0,
836
+ y: 0,
837
+ scale: 1
838
+ };
839
+ return;
840
+ }
841
+ const box = host.geometry();
842
+ if (!box) return;
843
+ const turned = transform.rotate % 180 !== 0;
844
+ const width = (turned ? box.height : box.width) * transform.scale;
845
+ const height = (turned ? box.width : box.height) * transform.scale;
846
+ transform = reboundImage(transform, box, {
847
+ left: box.left + box.width / 2 + transform.x - width / 2,
848
+ top: box.top + box.height / 2 + transform.y - height / 2
849
+ });
850
+ };
851
+ const finishClosing = () => {
852
+ closeTimer = void 0;
853
+ phase = "closed";
854
+ items = [];
855
+ selected = void 0;
856
+ current = -1;
857
+ emit();
858
+ };
859
+ const close = () => {
860
+ if (phase === "closed" || phase === "closing") return;
861
+ cancelOpening?.();
862
+ cancelOpening = void 0;
863
+ unsubscribe?.();
864
+ unsubscribe = void 0;
865
+ endGesture();
866
+ phase = "closing";
867
+ const duration = typeof options.closeDuration === "function" ? options.closeDuration() : options.closeDuration ?? CLOSE_MS;
868
+ if (duration > 0) {
869
+ emit();
870
+ closeTimer = setTimeout(finishClosing, duration);
871
+ } else finishClosing();
872
+ };
873
+ const go = (delta) => {
874
+ const target = current + delta;
875
+ if (phase === "closed" || phase === "closing" || target < 0 || target >= items.length || target === current) return;
876
+ current = target;
877
+ selected = items[target].id;
878
+ startImage();
879
+ emit();
880
+ };
881
+ const follow = (gallery) => {
882
+ const next = gallery.items();
883
+ const index = next.findIndex((item) => item.id === selected);
884
+ if (index < 0) return close();
885
+ if (next[index].src !== items[current]?.src) startImage();
886
+ items = next;
887
+ current = index;
888
+ emit();
889
+ };
890
+ return {
891
+ state: snapshot,
892
+ open(gallery, id) {
893
+ const list = gallery.items();
894
+ const index = list.findIndex((item) => item.id === id);
895
+ if (index < 0) return false;
896
+ if (closeTimer !== void 0) clearTimeout(closeTimer);
897
+ closeTimer = void 0;
898
+ unsubscribe?.();
899
+ cancelOpening?.();
900
+ items = list;
901
+ current = index;
902
+ selected = id;
903
+ startImage();
904
+ unsubscribe = gallery.subscribe(() => follow(gallery));
905
+ if (phase === "open") {
906
+ emit();
907
+ return true;
908
+ }
909
+ cancelOpening = void 0;
910
+ phase = "opening";
911
+ emit();
912
+ cancelOpening = host.frame(() => {
913
+ cancelOpening = host.frame(() => {
914
+ cancelOpening = void 0;
915
+ phase = "open";
916
+ emit();
917
+ });
918
+ });
919
+ return true;
920
+ },
921
+ close,
922
+ go,
923
+ action(name) {
924
+ if (phase === "closed" || phase === "closing") return;
925
+ if (name === "close") return close();
926
+ if (name === "prev") return go(-1);
927
+ if (name === "next") return go(1);
928
+ if (status !== "ready") return;
929
+ if (name === "zoomIn") zoom(ZOOM_STEP);
930
+ else if (name === "zoomOut") zoom(1 / ZOOM_STEP);
931
+ else if (name === "rotateLeft") transform = {
932
+ ...transform,
933
+ rotate: transform.rotate - 90
934
+ };
935
+ else if (name === "rotateRight") transform = {
936
+ ...transform,
937
+ rotate: transform.rotate + 90
938
+ };
939
+ else if (name === "flipX") transform = {
940
+ ...transform,
941
+ flipX: !transform.flipX
942
+ };
943
+ else if (name === "flipY") transform = {
944
+ ...transform,
945
+ flipY: !transform.flipY
946
+ };
947
+ emit();
948
+ },
949
+ loaded() {
950
+ if (phase === "closed" || status === "ready") return;
951
+ status = "ready";
952
+ emit();
953
+ },
954
+ failed() {
955
+ if (phase === "closed" || status === "error") return;
956
+ status = "error";
957
+ endGesture();
958
+ emit();
959
+ },
960
+ pointerDown(pointer) {
961
+ if (phase !== "open" && phase !== "opening" || status !== "ready") return false;
962
+ if (pointer.button !== void 0 && pointer.button !== 0) return false;
963
+ if (pointers.size >= 2 && !pointers.has(pointer.id)) return false;
964
+ pointers.set(pointer.id, {
965
+ x: pointer.x,
966
+ y: pointer.y
967
+ });
968
+ grip = pointers.size === 1 ? {
969
+ x: pointer.x - transform.x,
970
+ y: pointer.y - transform.y
971
+ } : void 0;
972
+ moving = true;
973
+ emit();
974
+ return true;
975
+ },
976
+ pointerMove(pointer) {
977
+ const previous = pointers.get(pointer.id);
978
+ if (!previous || previous.x === pointer.x && previous.y === pointer.y) return;
979
+ const before = [...pointers.values()];
980
+ pointers.set(pointer.id, {
981
+ x: pointer.x,
982
+ y: pointer.y
983
+ });
984
+ if (pointers.size === 2) {
985
+ const [a, b] = [...pointers.values()];
986
+ const span = Math.hypot(before[0].x - before[1].x, before[0].y - before[1].y);
987
+ const center = imagePinchCenter(before[0], before[1], a, b);
988
+ if (span > 0) zoom(Math.hypot(a.x - b.x, a.y - b.y) / span, center.x, center.y, true);
989
+ } else if (grip) transform = {
990
+ ...transform,
991
+ x: pointer.x - grip.x,
992
+ y: pointer.y - grip.y
993
+ };
994
+ displaced = true;
995
+ emitNextFrame();
996
+ },
997
+ pointerUp(id) {
998
+ if (!pointers.delete(id)) return;
999
+ const [remaining] = pointers.values();
1000
+ if (remaining) {
1001
+ grip = {
1002
+ x: remaining.x - transform.x,
1003
+ y: remaining.y - transform.y
1004
+ };
1005
+ return;
1006
+ }
1007
+ const settle = displaced;
1008
+ endGesture();
1009
+ if (settle) rebound();
1010
+ emit();
1011
+ },
1012
+ wheel(input) {
1013
+ if (phase !== "open" || status !== "ready" || !input.deltaY) return false;
1014
+ zoom(imageWheelRatio(input.deltaY, input.mode, input.pinch), input.x, input.y);
1015
+ emitNextFrame();
1016
+ return true;
1017
+ },
1018
+ doubleClick(x, y) {
1019
+ if (phase !== "open" || status !== "ready") return;
1020
+ if (transform.scale !== 1) transform = {
1021
+ ...transform,
1022
+ x: 0,
1023
+ y: 0,
1024
+ scale: 1
1025
+ };
1026
+ else zoom(ZOOM_STEP, x, y);
1027
+ emit();
1028
+ },
1029
+ key(key) {
1030
+ if (phase !== "open" && phase !== "opening" || items.length < 2) return false;
1031
+ if (key !== "ArrowLeft" && key !== "ArrowRight") return false;
1032
+ go(key === "ArrowLeft" ? -1 : 1);
1033
+ return true;
1034
+ },
1035
+ dispose() {
1036
+ if (closeTimer !== void 0) clearTimeout(closeTimer);
1037
+ closeTimer = void 0;
1038
+ cancelPublish?.();
1039
+ cancelOpening?.();
1040
+ unsubscribe?.();
1041
+ cancelPublish = cancelOpening = unsubscribe = void 0;
1042
+ endGesture();
1043
+ phase = "closed";
1044
+ items = [];
1045
+ selected = void 0;
1046
+ current = -1;
1047
+ }
1048
+ };
1049
+ }
375
1050
  const imageIconPaths = {
376
1051
  rotateLeft: ["M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", "M3 3v5h5"],
377
1052
  rotateRight: ["M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8", "M21 3v5h-5"],
@@ -418,16 +1093,20 @@ const imageIconPaths = {
418
1093
  "M11 9a2 2 0 1 1-4 0 2 2 0 0 1 4 0"
419
1094
  ]
420
1095
  };
421
- const imageGroupSelector = "[data-aimd-image-scope], .aim-typography-root, .aimd-vue, .mantine-Typography-root";
422
1096
  const galleries = /* @__PURE__ */ new WeakMap();
423
1097
  function getImageGallery(scope) {
424
1098
  const existing = galleries.get(scope);
425
1099
  if (existing) return existing;
426
1100
  const entries = /* @__PURE__ */ new Map();
427
1101
  const listeners = /* @__PURE__ */ new Set();
428
- const notify = () => listeners.forEach((listener) => listener());
1102
+ let sorted;
1103
+ const notify = () => {
1104
+ sorted = void 0;
1105
+ listeners.forEach((listener) => listener());
1106
+ };
429
1107
  const gallery = {
430
- items: () => [...entries.values()].sort((a, b) => a.precedes(b.id) ? -1 : b.precedes(a.id) ? 1 : 0),
1108
+ items: () => sorted ??= [...entries.values()].sort((a, b) => a.precedes(b.id) ? -1 : b.precedes(a.id) ? 1 : 0),
1109
+ size: () => entries.size,
431
1110
  subscribe(listener) {
432
1111
  listeners.add(listener);
433
1112
  return () => {
@@ -447,95 +1126,34 @@ function getImageGallery(scope) {
447
1126
  galleries.set(scope, gallery);
448
1127
  return gallery;
449
1128
  }
450
- const imageActionLabels = {
451
- close: "Close preview",
452
- prev: "Previous image",
453
- next: "Next image",
454
- rotateLeft: "Rotate left",
455
- rotateRight: "Rotate right",
456
- zoomIn: "Zoom in",
457
- zoomOut: "Zoom out",
458
- flipX: "Flip horizontally",
459
- flipY: "Flip vertically"
460
- };
461
- const initialImageTransform = () => ({
462
- x: 0,
463
- y: 0,
464
- rotate: 0,
465
- scale: 1,
466
- flipX: false,
467
- flipY: false
468
- });
469
- function imageTransformStyle(transform) {
470
- const { x, y, scale, rotate, flipX, flipY } = transform;
471
- return `translate3d(${x}px, ${y}px, 0) scale3d(${flipX ? "-" : ""}${scale}, ${flipY ? "-" : ""}${scale}, 1) rotate(${rotate}deg)`;
472
- }
473
- function zoomImage(transform, ratio, geometry, centerX = geometry.viewportWidth / 2, centerY = geometry.viewportHeight / 2, touch = false) {
474
- if (!Number.isFinite(ratio) || ratio <= 0) return transform;
475
- const scale = Math.min(50, touch ? Math.max(.01, transform.scale * ratio) : Math.max(1, transform.scale * ratio));
476
- const difference = scale / transform.scale - 1;
477
- let x = transform.x - difference * (centerX - transform.x - geometry.left - geometry.width / 2);
478
- let y = transform.y - difference * (centerY - transform.y - geometry.top - geometry.height / 2);
479
- if (ratio < 1 && scale === 1 && geometry.width <= geometry.viewportWidth && geometry.height <= geometry.viewportHeight) x = y = 0;
480
- return {
481
- ...transform,
482
- x,
483
- y,
484
- scale
485
- };
486
- }
487
- function imageWheelRatio(delta) {
488
- const ratio = 1 + Math.min(Math.abs(delta / 100), 1) * .5;
489
- return delta > 0 ? 1 / ratio : ratio;
490
- }
491
- function reboundImage(transform, geometry, bounds) {
492
- if (transform.scale < 1) return {
493
- ...transform,
494
- x: 0,
495
- y: 0,
496
- scale: 1
497
- };
498
- const rotated = transform.rotate % 180 !== 0;
499
- const width = (rotated ? geometry.height : geometry.width) * transform.scale;
500
- const height = (rotated ? geometry.width : geometry.height) * transform.scale;
501
- if (width <= geometry.viewportWidth && height <= geometry.viewportHeight) return {
502
- ...transform,
503
- x: 0,
504
- y: 0
505
- };
506
- const fix = (position, start, size, viewport) => {
507
- const offset = (size - viewport) / 2;
508
- if (size > viewport) {
509
- if (start > 0) return offset;
510
- if (start < 0 && start + size < viewport) return -offset;
511
- } else if (start < 0 || start + size > viewport) return start < 0 ? offset : -offset;
512
- return position;
513
- };
514
- return {
515
- ...transform,
516
- x: fix(transform.x, bounds.left, width, geometry.viewportWidth),
517
- y: fix(transform.y, bounds.top, height, geometry.viewportHeight)
518
- };
519
- }
520
- function imagePinchCenter(oldA, oldB, a, b) {
521
- const distanceA = Math.hypot(a.x - oldA.x, a.y - oldA.y), distanceB = Math.hypot(b.x - oldB.x, b.y - oldB.y);
522
- const ratio = distanceA + distanceB ? distanceA / (distanceA + distanceB) : 0;
523
- return {
524
- x: oldA.x + ratio * (oldB.x - oldA.x),
525
- y: oldA.y + ratio * (oldB.y - oldA.y)
526
- };
527
- }
528
- exports.MERMAID_DEFAULT_MAX_TEXT_SIZE = MERMAID_DEFAULT_MAX_TEXT_SIZE;
1129
+ const imageActionNames = Object.freeze([
1130
+ "close",
1131
+ "prev",
1132
+ "next",
1133
+ "rotateLeft",
1134
+ "rotateRight",
1135
+ "zoomIn",
1136
+ "zoomOut",
1137
+ "flipX",
1138
+ "flipY"
1139
+ ]);
1140
+ exports.createCodeBlockSession = createCodeBlockSession;
529
1141
  exports.createCodeFrame = createCodeFrame;
1142
+ exports.createCodeLanguageTracker = createCodeLanguageTracker;
530
1143
  exports.createDiagramController = createDiagramController;
1144
+ exports.createImagePreviewController = createImagePreviewController;
531
1145
  exports.createJsonCompletenessScanner = createJsonCompletenessScanner;
532
- exports.createRenderQueue = createRenderQueue;
1146
+ exports.defaultCodeBlockLabels = defaultCodeBlockLabels;
1147
+ exports.defaultDiagramLabels = defaultDiagramLabels;
1148
+ exports.defaultImageLabels = defaultImageLabels;
1149
+ exports.defaultTableLabels = defaultTableLabels;
1150
+ exports.diagramErrorText = diagramErrorText;
533
1151
  exports.ensureMermaidInitialized = ensureMermaidInitialized;
1152
+ exports.extendsCode = extendsCode;
534
1153
  exports.extractCode = extractCode;
535
1154
  exports.getImageGallery = getImageGallery;
536
1155
  exports.getMermaidMaxTextSize = getMermaidMaxTextSize;
537
- exports.imageActionLabels = imageActionLabels;
538
- exports.imageGroupSelector = imageGroupSelector;
1156
+ exports.imageActionNames = imageActionNames;
539
1157
  exports.imageIconPaths = imageIconPaths;
540
1158
  exports.imagePinchCenter = imagePinchCenter;
541
1159
  exports.imageTransformStyle = imageTransformStyle;
@@ -544,11 +1162,12 @@ exports.initialImageTransform = initialImageTransform;
544
1162
  exports.jsonLooksComplete = jsonLooksComplete;
545
1163
  exports.lockImagePreviewScroll = lockImagePreviewScroll;
546
1164
  exports.mermaidRenderQueue = mermaidRenderQueue;
1165
+ exports.nextCodeFrame = nextCodeFrame;
547
1166
  exports.normalizeRenderers = normalizeRenderers;
548
1167
  exports.prettyPrintJson = prettyPrintJson;
549
1168
  exports.projectTable = projectTable;
550
1169
  exports.reboundImage = reboundImage;
551
- exports.resetMermaidInitializationForTests = resetMermaidInitializationForTests;
1170
+ exports.resolveLabels = resolveLabels;
552
1171
  exports.serializeTable = serializeTable;
553
1172
  exports.zoomImage = zoomImage;
554
1173