@mocanvas/mocanvas 1.0.0 → 4.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,1117 @@
1
+ import { T, ImageShapeCrop, ARROWHEAD_KINDS, ARROW_SHAPE_KINDS, LINE_SPLINE_KINDS, assetIdValidator, createBuiltInShapePropsMigrationIds, createBindingPropsMigrationSequence, createBuiltInBindingPropsMigrationIds, createShapePropsMigrationSequence, DefaultFontStyle, DefaultSizeStyle, DefaultDashStyle, DefaultFillStyle, DefaultLabelColorStyle, DefaultColorStyle, DefaultVerticalAlignStyle, DefaultHorizontalAlignStyle, GeoShapeGeoStyle, richTextToPlainText, createShapeId, BaseBoxShapeUtil, Rectangle2d, getDefaultDisplayValues } from '@mocanvas/editor';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/text/tiptap-extensions.ts
5
+ var ZERO_WIDTH_SPACE = "\u200B";
6
+ function level(node) {
7
+ const value = node.attrs?.["level"];
8
+ return typeof value === "number" && value >= 1 && value <= 6 ? Math.floor(value) : 1;
9
+ }
10
+ var RICH_TEXT_NODES = [
11
+ { name: "paragraph", kind: "node", tag: "p", emptyHtml: ZERO_WIDTH_SPACE },
12
+ { name: "heading", kind: "node", tag: "h1", tagFor: (node) => `h${level(node)}`, emptyHtml: ZERO_WIDTH_SPACE },
13
+ { name: "bulletList", kind: "node", tag: "ul" },
14
+ { name: "orderedList", kind: "node", tag: "ol" },
15
+ { name: "listItem", kind: "node", tag: "li", emptyHtml: ZERO_WIDTH_SPACE },
16
+ { name: "blockquote", kind: "node", tag: "blockquote" },
17
+ { name: "codeBlock", kind: "node", tag: "pre" },
18
+ { name: "hardBreak", kind: "node", tag: "br", void: true },
19
+ { name: "horizontalRule", kind: "node", tag: "hr", void: true }
20
+ ];
21
+ var RICH_TEXT_MARKS = [
22
+ { name: "bold", kind: "mark", tag: "strong" },
23
+ { name: "italic", kind: "mark", tag: "em" },
24
+ { name: "underline", kind: "mark", tag: "u" },
25
+ { name: "strike", kind: "mark", tag: "s" },
26
+ { name: "code", kind: "mark", tag: "code" },
27
+ { name: "highlight", kind: "mark", tag: "mark" },
28
+ {
29
+ name: "link",
30
+ kind: "mark",
31
+ tag: "a",
32
+ attributesFor: (mark) => {
33
+ const href = mark.attrs?.["href"];
34
+ if (typeof href !== "string") return null;
35
+ const trimmed = href.trim();
36
+ return /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i.test(trimmed) ? { href: trimmed, rel: "noopener noreferrer" } : null;
37
+ }
38
+ }
39
+ ];
40
+ var tipTapDefaultExtensions = [...RICH_TEXT_NODES, ...RICH_TEXT_MARKS];
41
+ var editorFactory = null;
42
+ function registerRichTextEditorFactory(factory) {
43
+ editorFactory = factory;
44
+ return () => {
45
+ if (editorFactory === factory) editorFactory = null;
46
+ };
47
+ }
48
+ function getRichTextEditorFactory() {
49
+ return editorFactory;
50
+ }
51
+ async function loadTipTapStarterExtensions() {
52
+ const moduleId = "@tiptap/starter-kit";
53
+ let loaded;
54
+ try {
55
+ loaded = await import(
56
+ /* @vite-ignore */
57
+ /* webpackIgnore: true */
58
+ moduleId
59
+ );
60
+ } catch (cause) {
61
+ throw new Error("mocanvas: `@tiptap/starter-kit` is an optional peer dependency and is not installed. Run `npm install @tiptap/starter-kit@^3 @tiptap/core@^3 @tiptap/pm@^3`, or use `tipTapDefaultExtensions` instead.", { cause });
62
+ }
63
+ const starterKit = loaded.default ?? loaded;
64
+ return [starterKit];
65
+ }
66
+ function toRichText(text) {
67
+ const source = typeof text === "string" ? text : "";
68
+ return {
69
+ type: "doc",
70
+ content: source.split("\n").map((line) => line.length === 0 ? { type: "paragraph" } : { type: "paragraph", content: [{ type: "text", text: line }] })
71
+ };
72
+ }
73
+ function isRichText(value) {
74
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
75
+ const doc = value;
76
+ return doc.type === "doc" && (doc.content === void 0 || Array.isArray(doc.content));
77
+ }
78
+ function richTextToText(source) {
79
+ if (source === null || source === void 0) return "";
80
+ if (typeof source === "string") return source;
81
+ return richTextToPlainText(source);
82
+ }
83
+ function asRichText(source) {
84
+ if (isRichText(source)) return source;
85
+ return toRichText(typeof source === "string" ? source : "");
86
+ }
87
+ function richTextEquals(a, b) {
88
+ if (a === b) return true;
89
+ return JSON.stringify(asRichText(a)) === JSON.stringify(asRichText(b));
90
+ }
91
+ function applyPlainTextToRichText(previous, nextText) {
92
+ const doc = asRichText(previous);
93
+ if (richTextToText(doc) === nextText) return doc;
94
+ return toRichText(nextText);
95
+ }
96
+ var SAFE_URL = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
97
+ var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
98
+ function escapeHtml(value) {
99
+ return value.replace(/[&<>"']/g, (c) => ESCAPES[c]);
100
+ }
101
+ function resolve(extensions) {
102
+ const nodes = /* @__PURE__ */ new Map();
103
+ const marks = /* @__PURE__ */ new Map();
104
+ for (const extension of extensions ?? [...RICH_TEXT_NODES, ...RICH_TEXT_MARKS]) {
105
+ const kind = extension.kind;
106
+ if (kind === "node") nodes.set(extension.name, extension);
107
+ else if (kind === "mark") marks.set(extension.name, extension);
108
+ }
109
+ if (nodes.size === 0) for (const node of RICH_TEXT_NODES) nodes.set(node.name, node);
110
+ if (marks.size === 0) for (const mark of RICH_TEXT_MARKS) marks.set(mark.name, mark);
111
+ return { nodes, marks };
112
+ }
113
+ function richTextToHtml(source, options = {}) {
114
+ const { nodes, marks } = resolve(options.extensions);
115
+ const doc = asRichText(source);
116
+ return (doc.content ?? []).map((node) => nodeToHtml(node, nodes, marks, options.unknownTypes)).join("");
117
+ }
118
+ function nodeToHtml(node, nodes, marks, unknown) {
119
+ if (typeof node !== "object" || node === null) return "";
120
+ if (node.type === "text") return textNodeToHtml(node, marks, unknown);
121
+ const extension = nodes.get(node.type);
122
+ if (!extension) {
123
+ unknown?.add(node.type);
124
+ return (node.content ?? []).map((child) => nodeToHtml(child, nodes, marks, unknown)).join("");
125
+ }
126
+ const tag = extension.tagFor ? extension.tagFor(node) : extension.tag;
127
+ if (extension.void) return tag === null ? "" : `<${tag}>`;
128
+ const inner = (node.content ?? []).map((child) => nodeToHtml(child, nodes, marks, unknown)).join("");
129
+ if (tag === null) return inner;
130
+ return `<${tag}>${inner.length === 0 && extension.emptyHtml !== void 0 ? extension.emptyHtml : inner}</${tag}>`;
131
+ }
132
+ function textNodeToHtml(node, marks, unknown) {
133
+ let html = escapeHtml(node.text ?? "");
134
+ for (const mark of node.marks ?? []) {
135
+ const extension = marks.get(mark.type);
136
+ if (!extension) {
137
+ unknown?.add(mark.type);
138
+ continue;
139
+ }
140
+ const attributes = extension.attributesFor?.(mark) ?? null;
141
+ if (attributes === null && extension.attributesFor) continue;
142
+ const rendered = attributes === null ? "" : Object.entries(attributes).map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join("");
143
+ html = `<${extension.tag}${rendered}>${html}</${extension.tag}>`;
144
+ }
145
+ return html;
146
+ }
147
+ function safeHref(value) {
148
+ if (typeof value !== "string") return null;
149
+ const trimmed = value.trim();
150
+ return SAFE_URL.test(trimmed) ? trimmed : null;
151
+ }
152
+ var PLAIN_STYLE = { bold: false, italic: false, underline: false, strike: false, code: false };
153
+ var BOLD_MARKS = /* @__PURE__ */ new Set(["bold", "strong"]);
154
+ var ITALIC_MARKS = /* @__PURE__ */ new Set(["italic", "em"]);
155
+ var UNDERLINE_MARKS = /* @__PURE__ */ new Set(["underline"]);
156
+ var STRIKE_MARKS = /* @__PURE__ */ new Set(["strike", "strikethrough", "s", "del"]);
157
+ var CODE_MARKS = /* @__PURE__ */ new Set(["code"]);
158
+ function styleOf(marks) {
159
+ const style = { ...PLAIN_STYLE };
160
+ for (const mark of marks ?? []) {
161
+ if (BOLD_MARKS.has(mark.type)) style.bold = true;
162
+ else if (ITALIC_MARKS.has(mark.type)) style.italic = true;
163
+ else if (UNDERLINE_MARKS.has(mark.type)) style.underline = true;
164
+ else if (STRIKE_MARKS.has(mark.type)) style.strike = true;
165
+ else if (CODE_MARKS.has(mark.type)) style.code = true;
166
+ else if (mark.type === "link") {
167
+ const href = safeHref(mark.attrs?.["href"]);
168
+ if (href !== null) style.href = href;
169
+ }
170
+ }
171
+ return style;
172
+ }
173
+ function richTextToBlocks(source) {
174
+ if (typeof source === "string") return source.split("\n").map((text) => ({ runs: text.length === 0 ? [] : [{ text, ...PLAIN_STYLE }] }));
175
+ const doc = asRichText(source);
176
+ const blocks = [];
177
+ for (const node of doc.content ?? []) collectBlocks(node, blocks);
178
+ if (blocks.length === 0) blocks.push({ runs: [] });
179
+ return blocks;
180
+ }
181
+ function collectBlocks(node, out) {
182
+ if (typeof node !== "object" || node === null) return;
183
+ if (node.type === "text" || node.type === "hardBreak") {
184
+ appendInline(node, out);
185
+ return;
186
+ }
187
+ const children = node.content ?? [];
188
+ if (children.some((child) => isBlockNode(child))) {
189
+ for (const child of children) collectBlocks(child, out);
190
+ return;
191
+ }
192
+ out.push({ runs: [] });
193
+ for (const child of children) appendInline(child, out);
194
+ }
195
+ var BLOCK_NODE_TYPES = /* @__PURE__ */ new Set(["paragraph", "heading", "bulletList", "orderedList", "listItem", "blockquote", "codeBlock", "horizontalRule", "taskList", "taskItem", "table", "tableRow", "tableCell", "tableHeader"]);
196
+ function isBlockNode(node) {
197
+ if (typeof node !== "object" || node === null) return false;
198
+ if (node.type === "text" || node.type === "hardBreak") return false;
199
+ if (Array.isArray(node.marks) && node.marks.length > 0) return false;
200
+ return BLOCK_NODE_TYPES.has(node.type) || Array.isArray(node.content);
201
+ }
202
+ function appendInline(node, out, inherited) {
203
+ if (typeof node !== "object" || node === null) return;
204
+ if (out.length === 0) out.push({ runs: [] });
205
+ if (node.type === "hardBreak") {
206
+ out.push({ runs: [] });
207
+ return;
208
+ }
209
+ const style = mergeStyle(inherited, styleOf(node.marks));
210
+ if (node.type === "text") {
211
+ const text = node.text ?? "";
212
+ if (text.length > 0) out[out.length - 1].runs.push({ text, ...style });
213
+ return;
214
+ }
215
+ for (const child of node.content ?? []) appendInline(child, out, style);
216
+ }
217
+ function mergeStyle(base, next) {
218
+ if (!base) return next;
219
+ return {
220
+ bold: base.bold || next.bold,
221
+ italic: base.italic || next.italic,
222
+ underline: base.underline || next.underline,
223
+ strike: base.strike || next.strike,
224
+ code: base.code || next.code,
225
+ ...next.href ?? base.href ? { href: next.href ?? base.href } : {}
226
+ };
227
+ }
228
+ function propsOf(shape) {
229
+ const props = shape.props;
230
+ return typeof props === "object" && props !== null && !Array.isArray(props) ? props : {};
231
+ }
232
+ function readString(props, key, fallback) {
233
+ const value = props?.[key];
234
+ return typeof value === "string" ? value : fallback;
235
+ }
236
+ function readEnum(props, key, allowed, fallback) {
237
+ const value = props?.[key];
238
+ return typeof value === "string" && allowed.includes(value) ? value : fallback;
239
+ }
240
+ function readStyle(props, key, style, fallback = style.defaultValue) {
241
+ return readEnum(props, key, style.values, fallback);
242
+ }
243
+ function readNumber(props, key, fallback) {
244
+ const value = props?.[key];
245
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
246
+ }
247
+ function readBoolean(props, key, fallback) {
248
+ const value = props?.[key];
249
+ return typeof value === "boolean" ? value : fallback;
250
+ }
251
+ function readText(props, key = "text") {
252
+ if (key !== "text") return readString(props, key, "");
253
+ return richTextToPlainText(readRichText(props));
254
+ }
255
+ function readRichText(props, key = "richText") {
256
+ const value = props?.[key];
257
+ const stored = isRichText(value) ? value : null;
258
+ if (stored !== null && richTextToPlainText(stored).length > 0) return stored;
259
+ const text = props?.["text"];
260
+ if (typeof text === "string" && text.length > 0) return toRichText(text);
261
+ return stored ?? toRichText("");
262
+ }
263
+ function readPoint(props, key, fallback) {
264
+ const value = props?.[key];
265
+ if (typeof value !== "object" || value === null) return fallback;
266
+ return { x: readNumber(value, "x", fallback.x), y: readNumber(value, "y", fallback.y) };
267
+ }
268
+ function readArray(props, key) {
269
+ const value = props?.[key];
270
+ return Array.isArray(value) ? value : [];
271
+ }
272
+ function readRecord(props, key) {
273
+ const value = props?.[key];
274
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
275
+ }
276
+
277
+ // src/shapes/indicator-paths.ts
278
+ function canBuildPath() {
279
+ return typeof Path2D !== "undefined";
280
+ }
281
+ function rectPath(w, h, radius = 0) {
282
+ const path = new Path2D();
283
+ if (radius > 0 && typeof path.roundRect === "function") {
284
+ path.roundRect(0, 0, w, h, radius);
285
+ } else {
286
+ path.rect(0, 0, w, h);
287
+ }
288
+ return path;
289
+ }
290
+ function boxPath(box) {
291
+ const path = new Path2D();
292
+ path.rect(box.x, box.y, box.w, box.h);
293
+ return path;
294
+ }
295
+ function svgPath(d) {
296
+ return new Path2D(d);
297
+ }
298
+ function polylinePath(points, close = false) {
299
+ const path = new Path2D();
300
+ const first = points[0];
301
+ if (!first) return path;
302
+ path.moveTo(first.x, first.y);
303
+ for (let i = 1; i < points.length; i++) {
304
+ const p = points[i];
305
+ path.lineTo(p.x, p.y);
306
+ }
307
+ if (close) path.closePath();
308
+ return path;
309
+ }
310
+ var richTextValidator = T.jsonValue.refine((value) => {
311
+ if (!isRichText(value)) throw new Error(`Expected a rich text document, got ${JSON.stringify(value)}`);
312
+ return { type: "doc", content: value.content ?? [] };
313
+ });
314
+ function styleValue(style) {
315
+ return T.unknown.refine((value) => style.validate(value));
316
+ }
317
+ var imageCropValidator = ImageShapeCrop.refine(
318
+ (crop) => crop.isCircle === void 0 ? { topLeft: crop.topLeft, bottomRight: crop.bottomRight } : { topLeft: crop.topLeft, bottomRight: crop.bottomRight, isCircle: crop.isCircle }
319
+ );
320
+ var pointValidator = T.object({ x: T.number, y: T.number });
321
+ var drawPointValidator = T.object({ x: T.number, y: T.number, z: T.number.optional() });
322
+ var drawSegmentValidator = T.object({
323
+ type: T.literalEnum("free", "straight"),
324
+ points: T.arrayOf(drawPointValidator)
325
+ });
326
+ var derivedTextValidator = T.string.optional();
327
+ var geoShapeProps = {
328
+ geo: GeoShapeGeoStyle,
329
+ w: T.nonZeroNumber,
330
+ h: T.nonZeroNumber,
331
+ color: DefaultColorStyle,
332
+ labelColor: DefaultLabelColorStyle,
333
+ fill: DefaultFillStyle,
334
+ dash: DefaultDashStyle,
335
+ size: DefaultSizeStyle,
336
+ font: DefaultFontStyle,
337
+ align: DefaultHorizontalAlignStyle,
338
+ verticalAlign: DefaultVerticalAlignStyle,
339
+ growY: T.positiveNumber,
340
+ url: T.linkUrl,
341
+ richText: richTextValidator,
342
+ text: derivedTextValidator,
343
+ scale: T.nonZeroNumber,
344
+ flipX: T.boolean,
345
+ flipY: T.boolean
346
+ };
347
+ var arrowShapeProps = {
348
+ kind: T.literalEnum(...ARROW_SHAPE_KINDS),
349
+ start: pointValidator,
350
+ end: pointValidator,
351
+ bend: T.number,
352
+ elbowMidPoint: T.number,
353
+ color: DefaultColorStyle,
354
+ labelColor: DefaultLabelColorStyle,
355
+ fill: DefaultFillStyle,
356
+ dash: DefaultDashStyle,
357
+ size: DefaultSizeStyle,
358
+ arrowheadStart: T.literalEnum(...ARROWHEAD_KINDS),
359
+ arrowheadEnd: T.literalEnum(...ARROWHEAD_KINDS),
360
+ font: DefaultFontStyle,
361
+ richText: richTextValidator,
362
+ text: derivedTextValidator,
363
+ labelPosition: T.number,
364
+ scale: T.nonZeroNumber
365
+ };
366
+ var drawShapeProps = {
367
+ segments: T.arrayOf(drawSegmentValidator),
368
+ color: DefaultColorStyle,
369
+ fill: DefaultFillStyle,
370
+ dash: DefaultDashStyle,
371
+ size: DefaultSizeStyle,
372
+ isComplete: T.boolean,
373
+ isClosed: T.boolean,
374
+ isPen: T.boolean,
375
+ scale: T.nonZeroNumber
376
+ };
377
+ var highlightShapeProps = {
378
+ segments: T.arrayOf(drawSegmentValidator),
379
+ color: DefaultColorStyle,
380
+ size: DefaultSizeStyle,
381
+ isComplete: T.boolean,
382
+ isPen: T.boolean,
383
+ scale: T.nonZeroNumber
384
+ };
385
+ var lineShapeProps = {
386
+ color: DefaultColorStyle,
387
+ dash: DefaultDashStyle,
388
+ size: DefaultSizeStyle,
389
+ spline: T.literalEnum(...LINE_SPLINE_KINDS),
390
+ points: T.dict(T.string, T.object({ id: T.string, index: T.string, x: T.number, y: T.number })),
391
+ scale: T.nonZeroNumber
392
+ };
393
+ var textShapeProps = {
394
+ color: DefaultColorStyle,
395
+ size: DefaultSizeStyle,
396
+ font: DefaultFontStyle,
397
+ textAlign: DefaultHorizontalAlignStyle,
398
+ w: T.nonZeroNumber,
399
+ richText: richTextValidator,
400
+ text: derivedTextValidator,
401
+ scale: T.nonZeroNumber,
402
+ autoSize: T.boolean
403
+ };
404
+ var noteShapeProps = {
405
+ color: DefaultColorStyle,
406
+ labelColor: DefaultLabelColorStyle,
407
+ size: DefaultSizeStyle,
408
+ font: DefaultFontStyle,
409
+ fontSizeAdjustment: T.positiveNumber,
410
+ align: DefaultHorizontalAlignStyle,
411
+ verticalAlign: DefaultVerticalAlignStyle,
412
+ growY: T.positiveNumber,
413
+ url: T.linkUrl,
414
+ richText: richTextValidator,
415
+ text: derivedTextValidator,
416
+ // Who first edited the label by hand, or `null` while nobody has. Absent on
417
+ // every note written before attribution existed, so optional as well.
418
+ textFirstEditedBy: T.string.nullable().optional(),
419
+ scale: T.nonZeroNumber
420
+ };
421
+ var frameShapeProps = {
422
+ w: T.nonZeroNumber,
423
+ h: T.nonZeroNumber,
424
+ name: T.string,
425
+ // A frame's colour is per frame: a frame is chrome around other people's
426
+ // shapes, and recolouring a mixed selection should not repaint the frames it
427
+ // happens to contain. Hence a style *value*, not a style.
428
+ color: styleValue(DefaultColorStyle).optional()
429
+ };
430
+ var groupShapeProps = {};
431
+ var imageShapeProps = {
432
+ w: T.nonZeroNumber,
433
+ h: T.nonZeroNumber,
434
+ assetId: assetIdValidator.nullable(),
435
+ playing: T.boolean,
436
+ url: T.linkUrl,
437
+ crop: imageCropValidator.nullable(),
438
+ flipX: T.boolean,
439
+ flipY: T.boolean,
440
+ altText: T.string
441
+ };
442
+ var videoShapeProps = {
443
+ w: T.nonZeroNumber,
444
+ h: T.nonZeroNumber,
445
+ assetId: assetIdValidator.nullable(),
446
+ time: T.number,
447
+ playing: T.boolean,
448
+ url: T.linkUrl,
449
+ altText: T.string
450
+ };
451
+ var bookmarkShapeProps = {
452
+ w: T.nonZeroNumber,
453
+ h: T.nonZeroNumber,
454
+ assetId: assetIdValidator.nullable(),
455
+ url: T.linkUrl
456
+ };
457
+ var embedShapeProps = {
458
+ w: T.nonZeroNumber,
459
+ h: T.nonZeroNumber,
460
+ url: T.linkUrl
461
+ };
462
+ var arrowBindingProps = {
463
+ terminal: T.literalEnum("start", "end"),
464
+ normalizedAnchor: pointValidator,
465
+ isExact: T.boolean,
466
+ isPrecise: T.boolean
467
+ };
468
+ function backfillMigration(type, defaults, options = {}) {
469
+ const versions = createBuiltInShapePropsMigrationIds(type, { BackfillMissingProps: 1 });
470
+ return createShapePropsMigrationSequence({
471
+ sequence: [
472
+ {
473
+ id: versions.BackfillMissingProps,
474
+ up(props) {
475
+ if (options.richTextFromText && props["richText"] === void 0) {
476
+ props["richText"] = toRichText(typeof props["text"] === "string" ? props["text"] : "");
477
+ }
478
+ for (const [key, value] of Object.entries(defaults)) {
479
+ if (props[key] === void 0) props[key] = structuredClone(value);
480
+ }
481
+ },
482
+ down() {
483
+ }
484
+ }
485
+ ]
486
+ });
487
+ }
488
+ var arrowShapeVersions = createBuiltInShapePropsMigrationIds("arrow", {
489
+ BackfillMissingProps: 1
490
+ });
491
+ var arrowShapeMigrations = backfillMigration(
492
+ "arrow",
493
+ { kind: "arc", elbowMidPoint: 0.5, labelPosition: 0.5, scale: 1 },
494
+ { richTextFromText: true }
495
+ );
496
+ var drawShapeMigrations = backfillMigration("draw", {
497
+ isComplete: true,
498
+ isClosed: false,
499
+ isPen: false,
500
+ scale: 1
501
+ });
502
+ var highlightShapeMigrations = backfillMigration("highlight", {
503
+ isComplete: true,
504
+ isPen: false,
505
+ scale: 1
506
+ });
507
+ var lineShapeMigrations = backfillMigration("line", { spline: "line", scale: 1 });
508
+ var textShapeMigrations = backfillMigration("text", { autoSize: true, scale: 1 }, { richTextFromText: true });
509
+ var noteShapeMigrations = backfillMigration(
510
+ "note",
511
+ { fontSizeAdjustment: 0, growY: 0, url: "", scale: 1 },
512
+ { richTextFromText: true }
513
+ );
514
+ var frameShapeMigrations = backfillMigration("frame", { name: "" });
515
+ var groupShapeMigrations = backfillMigration("group", {});
516
+ var imageShapeMigrations = backfillMigration("image", {
517
+ assetId: null,
518
+ playing: true,
519
+ url: "",
520
+ crop: null,
521
+ flipX: false,
522
+ flipY: false,
523
+ altText: ""
524
+ });
525
+ var videoShapeMigrations = backfillMigration("video", {
526
+ assetId: null,
527
+ time: 0,
528
+ playing: true,
529
+ url: "",
530
+ altText: ""
531
+ });
532
+ var bookmarkShapeMigrations = backfillMigration("bookmark", { assetId: null, url: "" });
533
+ var embedShapeMigrations = backfillMigration("embed", { url: "" });
534
+ var arrowBindingMigrations = createBindingPropsMigrationSequence({
535
+ sequence: [
536
+ {
537
+ id: createBuiltInBindingPropsMigrationIds("arrow", { Initial: 1 }).Initial,
538
+ up() {
539
+ },
540
+ down() {
541
+ }
542
+ }
543
+ ]
544
+ });
545
+ var BOOKMARK_WIDTH = 300;
546
+ var BOOKMARK_HEIGHT = 320;
547
+ var BOOKMARK_FILL = "#ffffff";
548
+ var BOOKMARK_STROKE = "#e8e9ea";
549
+ var BOOKMARK_STROKE_WIDTH = 1;
550
+ var BOOKMARK_RADIUS = 8;
551
+ var BOOKMARK_BANNER_HEIGHT = 160;
552
+ var BOOKMARK_BANNER_FILL = "#eceff3";
553
+ var BOOKMARK_PADDING = 12;
554
+ var BOOKMARK_GAP = 6;
555
+ var BOOKMARK_TITLE_HEIGHT = 38;
556
+ var BOOKMARK_META_HEIGHT = 16;
557
+ var BOOKMARK_FAVICON_SIZE = 14;
558
+ var BOOKMARK_TITLE_FONT_SIZE = 14;
559
+ var BOOKMARK_TEXT_FONT_SIZE = 12;
560
+ var BOOKMARK_META_FONT_SIZE = 11;
561
+ var BOOKMARK_TITLE_COLOR = "#1d1d1d";
562
+ var BOOKMARK_TEXT_COLOR = "#666666";
563
+ var BOOKMARK_META_COLOR = "#8f8f8f";
564
+ var BOOKMARK_MIN_BODY_HEIGHT = 80;
565
+ function getBookmarkLayout(w, h) {
566
+ const pad = BOOKMARK_PADDING;
567
+ const innerW = Math.max(0, w - pad * 2);
568
+ const bannerH = Math.max(0, Math.min(BOOKMARK_BANNER_HEIGHT, h - BOOKMARK_MIN_BODY_HEIGHT));
569
+ const metaY = Math.max(bannerH, h - pad - BOOKMARK_META_HEIGHT);
570
+ const titleY = bannerH + pad;
571
+ const titleH = Math.max(0, Math.min(BOOKMARK_TITLE_HEIGHT, metaY - BOOKMARK_GAP - titleY));
572
+ const descY = titleY + titleH + BOOKMARK_GAP;
573
+ const clamp = (r) => {
574
+ const x = Math.max(0, Math.min(r.x, w));
575
+ const y = Math.max(0, Math.min(r.y, h));
576
+ return { x, y, w: Math.max(0, Math.min(r.w, w - x)), h: Math.max(0, Math.min(r.h, h - y)) };
577
+ };
578
+ return {
579
+ banner: clamp({ x: 0, y: 0, w, h: bannerH }),
580
+ title: clamp({ x: pad, y: titleY, w: innerW, h: titleH }),
581
+ description: clamp({ x: pad, y: descY, w: innerW, h: metaY - BOOKMARK_GAP - descY }),
582
+ favicon: clamp({
583
+ x: pad,
584
+ y: metaY + (BOOKMARK_META_HEIGHT - BOOKMARK_FAVICON_SIZE) / 2,
585
+ w: BOOKMARK_FAVICON_SIZE,
586
+ h: BOOKMARK_FAVICON_SIZE
587
+ }),
588
+ hostname: clamp({
589
+ x: pad + BOOKMARK_FAVICON_SIZE + BOOKMARK_GAP,
590
+ y: metaY,
591
+ w: innerW - BOOKMARK_FAVICON_SIZE - BOOKMARK_GAP,
592
+ h: BOOKMARK_META_HEIGHT
593
+ })
594
+ };
595
+ }
596
+ function getBookmarkHostname(url) {
597
+ if (!url) return "";
598
+ try {
599
+ const host = new URL(url).hostname.toLowerCase();
600
+ return host.startsWith("www.") ? host.slice(4) : host;
601
+ } catch {
602
+ return "";
603
+ }
604
+ }
605
+ function getBookmarkAsset(editor, shape) {
606
+ const assetId = readString(propsOf(shape), "assetId", "");
607
+ if (!assetId) return null;
608
+ const asset = editor.getAsset(assetId);
609
+ return asset && asset.type === "bookmark" ? asset : null;
610
+ }
611
+ function getBookmarkCard(editor, shape) {
612
+ const url = readString(propsOf(shape), "url", "");
613
+ const asset = getBookmarkAsset(editor, shape);
614
+ const props = asset ? asset.props : void 0;
615
+ return {
616
+ title: readString(props, "title", ""),
617
+ description: readString(props, "description", ""),
618
+ image: readString(props, "image", ""),
619
+ favicon: readString(props, "favicon", ""),
620
+ hostname: getBookmarkHostname(url) || getBookmarkHostname(readString(props, "src", "") ?? ""),
621
+ url,
622
+ hasAsset: asset !== null
623
+ };
624
+ }
625
+ function readBookmarkBox(shape) {
626
+ const p = propsOf(shape);
627
+ return { w: readNumber(p, "w", BOOKMARK_WIDTH), h: readNumber(p, "h", BOOKMARK_HEIGHT) };
628
+ }
629
+ var ELLIPSIS = { overflow: "hidden", textOverflow: "ellipsis" };
630
+ function createEmptyBookmarkShape(editor, url, point) {
631
+ const centre = point ?? editor.getViewportPageBounds().center;
632
+ const id = createShapeId();
633
+ editor.run(() => {
634
+ editor.markHistoryStoppingPoint("insert bookmark");
635
+ editor.createShape({
636
+ id,
637
+ type: "bookmark",
638
+ x: centre.x - BOOKMARK_WIDTH / 2,
639
+ y: centre.y - BOOKMARK_HEIGHT / 2,
640
+ props: { w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, url, assetId: null }
641
+ });
642
+ editor.setSelectedShapes([id]);
643
+ });
644
+ return editor.getShape(id);
645
+ }
646
+ var BookmarkShapeUtil = class extends BaseBoxShapeUtil {
647
+ static type = "bookmark";
648
+ static props = bookmarkShapeProps;
649
+ static migrations = bookmarkShapeMigrations;
650
+ getDefaultProps() {
651
+ return { w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, assetId: null, url: "" };
652
+ }
653
+ getGeometry(shape) {
654
+ const { w, h } = readBookmarkBox(shape);
655
+ return new Rectangle2d({ width: Math.max(1, w), height: Math.max(1, h), isFilled: true });
656
+ }
657
+ /**
658
+ * The card is html — a banner image over wrapped, ellipsised text — which no
659
+ * single textured quad can stand in for, so the shape always renders through
660
+ * the DOM overlay.
661
+ */
662
+ getRenderStyle(_shape) {
663
+ return null;
664
+ }
665
+ component(shape) {
666
+ const { w, h } = readBookmarkBox(shape);
667
+ const card = getBookmarkCard(this.editor, shape);
668
+ const layout = getBookmarkLayout(w, h);
669
+ const isEditing = this.editor.getEditingShapeId() === shape.id;
670
+ const frame = {
671
+ position: "absolute",
672
+ left: 0,
673
+ top: 0,
674
+ width: w,
675
+ height: h,
676
+ boxSizing: "border-box",
677
+ overflow: "hidden",
678
+ borderRadius: BOOKMARK_RADIUS,
679
+ background: BOOKMARK_FILL,
680
+ border: `${BOOKMARK_STROKE_WIDTH}px solid ${BOOKMARK_STROKE}`,
681
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
682
+ /*
683
+ * The canvas owns the pointer: a card is a shape you drag and select,
684
+ * not a link you click. The url is still in the DOM as an `<a>` so
685
+ * assistive tech can read and follow it, but it only takes the pointer
686
+ * while the shape is being edited.
687
+ */
688
+ pointerEvents: isEditing ? "auto" : "none",
689
+ userSelect: "none"
690
+ };
691
+ return /* @__PURE__ */ jsx(
692
+ "a",
693
+ {
694
+ href: card.url || void 0,
695
+ target: "_blank",
696
+ rel: "noreferrer noopener",
697
+ draggable: false,
698
+ "aria-label": card.title || card.hostname || card.url || "bookmark",
699
+ tabIndex: isEditing ? 0 : -1,
700
+ style: { ...frame, display: "block", color: "inherit", textDecoration: "none" },
701
+ children: card.hasAsset ? this.renderCard(card, layout) : this.renderPlaceholder(card, layout)
702
+ }
703
+ );
704
+ }
705
+ /** The scraped card: banner, title, description, favicon and host. */
706
+ renderCard(card, layout) {
707
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
708
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", ...layout.banner, background: BOOKMARK_BANNER_FILL, overflow: "hidden" }, children: card.image ? /* @__PURE__ */ jsx(
709
+ "img",
710
+ {
711
+ src: card.image,
712
+ alt: "",
713
+ draggable: false,
714
+ style: { width: "100%", height: "100%", objectFit: "cover", pointerEvents: "none" }
715
+ }
716
+ ) : null }),
717
+ /* @__PURE__ */ jsx(
718
+ "div",
719
+ {
720
+ style: {
721
+ position: "absolute",
722
+ ...layout.title,
723
+ ...ELLIPSIS,
724
+ fontSize: BOOKMARK_TITLE_FONT_SIZE,
725
+ fontWeight: 600,
726
+ lineHeight: 1.3,
727
+ color: BOOKMARK_TITLE_COLOR
728
+ },
729
+ children: card.title
730
+ }
731
+ ),
732
+ /* @__PURE__ */ jsx(
733
+ "div",
734
+ {
735
+ style: {
736
+ position: "absolute",
737
+ ...layout.description,
738
+ ...ELLIPSIS,
739
+ fontSize: BOOKMARK_TEXT_FONT_SIZE,
740
+ lineHeight: 1.4,
741
+ color: BOOKMARK_TEXT_COLOR
742
+ },
743
+ children: card.description
744
+ }
745
+ ),
746
+ card.favicon ? /* @__PURE__ */ jsx(
747
+ "img",
748
+ {
749
+ src: card.favicon,
750
+ alt: "",
751
+ draggable: false,
752
+ style: { position: "absolute", ...layout.favicon, objectFit: "contain", pointerEvents: "none" }
753
+ }
754
+ ) : null,
755
+ /* @__PURE__ */ jsx(
756
+ "div",
757
+ {
758
+ style: {
759
+ position: "absolute",
760
+ ...layout.hostname,
761
+ ...ELLIPSIS,
762
+ whiteSpace: "nowrap",
763
+ fontSize: BOOKMARK_META_FONT_SIZE,
764
+ lineHeight: `${BOOKMARK_META_HEIGHT}px`,
765
+ color: BOOKMARK_META_COLOR
766
+ },
767
+ children: card.hostname
768
+ }
769
+ )
770
+ ] });
771
+ }
772
+ /** No asset: an empty banner and the host, so the shape still reads as a link to somewhere. */
773
+ renderPlaceholder(card, layout) {
774
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
775
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", ...layout.banner, background: BOOKMARK_BANNER_FILL } }),
776
+ /* @__PURE__ */ jsx(
777
+ "div",
778
+ {
779
+ style: {
780
+ position: "absolute",
781
+ ...layout.title,
782
+ ...ELLIPSIS,
783
+ fontSize: BOOKMARK_TITLE_FONT_SIZE,
784
+ lineHeight: 1.3,
785
+ color: BOOKMARK_META_COLOR
786
+ },
787
+ children: card.hostname || card.url
788
+ }
789
+ )
790
+ ] });
791
+ }
792
+ getIndicatorPath(shape) {
793
+ const { w, h } = readBookmarkBox(shape);
794
+ return rectPath(w, h, BOOKMARK_RADIUS);
795
+ }
796
+ };
797
+ var EMBED_WIDTH = 720;
798
+ var EMBED_HEIGHT = 500;
799
+ var EMBED_PLACEHOLDER_FILL = "#f5f6f8";
800
+ var EMBED_PLACEHOLDER_STROKE = "#9fa8b2";
801
+ var EMBED_PLACEHOLDER_TEXT = "#5f6670";
802
+ var EMBED_PLACEHOLDER_FONT_SIZE = 13;
803
+ var EMBED_PLACEHOLDER_PADDING = 16;
804
+ var EMBED_RADIUS = 6;
805
+ var EMBED_SANDBOX = "allow-scripts allow-same-origin allow-popups";
806
+ function httpsUrl(host, url, opts = {}) {
807
+ return `https://${host}${url.pathname}${url.search}${opts.hash ? url.hash : ""}`;
808
+ }
809
+ function youTubeId(candidate) {
810
+ return candidate && /^[A-Za-z0-9_-]{6,20}$/.test(candidate) ? candidate : null;
811
+ }
812
+ var youtube = {
813
+ type: "youtube",
814
+ title: "YouTube",
815
+ hostnames: ["youtube.com", "m.youtube.com", "youtube-nocookie.com", "youtu.be"],
816
+ toEmbedUrl(url) {
817
+ const segments = url.pathname.split("/").filter(Boolean);
818
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
819
+ const id = host === "youtu.be" ? youTubeId(segments[0]) : youTubeId(url.searchParams.get("v")) ?? (segments[0] === "embed" || segments[0] === "shorts" || segments[0] === "live" ? youTubeId(segments[1]) : null);
820
+ return id ? `https://www.youtube-nocookie.com/embed/${id}` : null;
821
+ }
822
+ };
823
+ var vimeo = {
824
+ type: "vimeo",
825
+ title: "Vimeo",
826
+ hostnames: ["vimeo.com", "player.vimeo.com"],
827
+ toEmbedUrl(url) {
828
+ const id = url.pathname.split("/").filter(Boolean).find((s) => /^\d{6,12}$/.test(s));
829
+ return id ? `https://player.vimeo.com/video/${id}` : null;
830
+ }
831
+ };
832
+ var codesandbox = {
833
+ type: "codesandbox",
834
+ title: "CodeSandbox",
835
+ hostnames: ["codesandbox.io"],
836
+ toEmbedUrl(url) {
837
+ const segments = url.pathname.split("/").filter(Boolean);
838
+ const id = segments[0] === "p" && segments[1] === "sandbox" ? segments[2] : segments[0] === "s" || segments[0] === "embed" ? segments[1] : null;
839
+ return id && /^[A-Za-z0-9_-]{3,64}$/.test(id) ? `https://codesandbox.io/embed/${id}` : null;
840
+ }
841
+ };
842
+ var figma = {
843
+ type: "figma",
844
+ title: "Figma",
845
+ hostnames: ["figma.com"],
846
+ toEmbedUrl(url) {
847
+ if (url.pathname === "/embed") return null;
848
+ const target = `https://www.figma.com${url.pathname}${url.search}`;
849
+ return `https://www.figma.com/embed?embed_host=mocanvas&url=${encodeURIComponent(target)}`;
850
+ },
851
+ // The one built-in whose embed form is not also a valid input to its own
852
+ // `toEmbedUrl`: wrapping an already-wrapped url would nest it twice.
853
+ fromEmbedUrl(url) {
854
+ if (url.pathname !== "/embed") return null;
855
+ const target = url.searchParams.get("url");
856
+ if (!target) return null;
857
+ try {
858
+ const parsed = new URL(target);
859
+ return parsed.protocol === "https:" && normalizeHost(parsed.hostname) === "figma.com" ? parsed.href : null;
860
+ } catch {
861
+ return null;
862
+ }
863
+ }
864
+ };
865
+ var googleMaps = {
866
+ type: "google-maps",
867
+ title: "Google Maps",
868
+ hostnames: ["google.com", "maps.google.com"],
869
+ toEmbedUrl(url) {
870
+ if (!url.pathname.startsWith("/maps")) return null;
871
+ if (url.pathname.startsWith("/maps/embed")) return httpsUrl("www.google.com", url);
872
+ const params = new URLSearchParams(url.search);
873
+ params.set("output", "embed");
874
+ return `https://www.google.com/maps?${params.toString()}`;
875
+ }
876
+ };
877
+ var excalidraw = {
878
+ type: "excalidraw",
879
+ title: "Excalidraw",
880
+ hostnames: ["excalidraw.com"],
881
+ // The scene lives in the fragment (`#json=`, `#room=`), so it has to survive.
882
+ toEmbedUrl: (url) => httpsUrl("excalidraw.com", url, { hash: true })
883
+ };
884
+ var DEFAULT_EMBED_DEFINITIONS = [youtube, vimeo, codesandbox, figma, googleMaps, excalidraw];
885
+ var embedDefinitions = [...DEFAULT_EMBED_DEFINITIONS];
886
+ var EMPTY_EMBED_CONFIG = Object.freeze({});
887
+ function normalizeHost(hostname) {
888
+ const host = hostname.toLowerCase();
889
+ return host.startsWith("www.") ? host.slice(4) : host;
890
+ }
891
+ function getEmbedDefinition(url, definitions = embedDefinitions, config = EMPTY_EMBED_CONFIG) {
892
+ if (typeof url !== "string" || url.length === 0) return null;
893
+ let parsed;
894
+ try {
895
+ parsed = new URL(url);
896
+ } catch {
897
+ return null;
898
+ }
899
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
900
+ const host = normalizeHost(parsed.hostname);
901
+ for (const definition of definitions) {
902
+ if (!definition.hostnames.some((h) => normalizeHost(h) === host)) continue;
903
+ const embedUrl = definition.toEmbedUrl(parsed, config[definition.type]);
904
+ if (embedUrl && embedUrl.startsWith("https://")) return { definition, embedUrl };
905
+ return null;
906
+ }
907
+ return null;
908
+ }
909
+ function getEmbedInfo(definitions, url, config = EMPTY_EMBED_CONFIG) {
910
+ const match = getEmbedDefinition(url, definitions, config);
911
+ if (match !== null) return { definition: match.definition, url, embedUrl: match.embedUrl };
912
+ if (typeof url !== "string" || url.length === 0) return void 0;
913
+ let parsed;
914
+ try {
915
+ parsed = new URL(url);
916
+ } catch {
917
+ return void 0;
918
+ }
919
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
920
+ const host = normalizeHost(parsed.hostname);
921
+ for (const definition of definitions) {
922
+ if (definition.fromEmbedUrl === void 0) continue;
923
+ if (!definition.hostnames.some((h) => normalizeHost(h) === host)) continue;
924
+ let pageUrl;
925
+ try {
926
+ pageUrl = definition.fromEmbedUrl(parsed);
927
+ } catch {
928
+ pageUrl = null;
929
+ }
930
+ if (!pageUrl) continue;
931
+ return { definition, url: pageUrl, embedUrl: parsed.href };
932
+ }
933
+ return void 0;
934
+ }
935
+ function readEmbedBox(shape) {
936
+ const p = propsOf(shape);
937
+ return { w: readNumber(p, "w", EMBED_WIDTH), h: readNumber(p, "h", EMBED_HEIGHT) };
938
+ }
939
+ function getEmbedDisplayValues(editor, shape, theme, colorMode) {
940
+ return {
941
+ ...getDefaultDisplayValues(editor, shape, theme, colorMode),
942
+ placeholderFill: EMBED_PLACEHOLDER_FILL,
943
+ placeholderStroke: EMBED_PLACEHOLDER_STROKE,
944
+ placeholderTextColor: EMBED_PLACEHOLDER_TEXT,
945
+ cornerRadius: EMBED_RADIUS
946
+ };
947
+ }
948
+ var EMBED_SHAPE_PERMISSION_NAMES = [
949
+ "accelerometer",
950
+ "autoplay",
951
+ "camera",
952
+ "clipboard-write",
953
+ "encrypted-media",
954
+ "fullscreen",
955
+ "geolocation",
956
+ "gyroscope",
957
+ "microphone",
958
+ "picture-in-picture"
959
+ ];
960
+ var unknownEmbedShapePermissionOverrides = {};
961
+ function embedShapePermissionsToAllow(permissions) {
962
+ const granted = EMBED_SHAPE_PERMISSION_NAMES.filter((name) => permissions[name] === true);
963
+ return granted.length === 0 ? void 0 : granted.join("; ");
964
+ }
965
+ var EmbedShapeUtil = class extends BaseBoxShapeUtil {
966
+ static type = "embed";
967
+ static props = embedShapeProps;
968
+ static migrations = embedShapeMigrations;
969
+ static options = { getDefaultDisplayValues: getEmbedDisplayValues };
970
+ getDefaultProps() {
971
+ return { w: EMBED_WIDTH, h: EMBED_HEIGHT, url: "" };
972
+ }
973
+ /**
974
+ * The per-service settings this util was configured with, keyed by
975
+ * {@link EmbedDefinition.type}.
976
+ *
977
+ * Always an object, so a definition can index it without a guard. This is
978
+ * where an api key belongs: a definition is handed its own entry and never
979
+ * reaches for `process.env` itself, which would put the secret in every
980
+ * bundle that imported the definition and make it the same for every editor
981
+ * on the page.
982
+ */
983
+ get embedConfig() {
984
+ return this.options.embedConfig ?? EMPTY_EMBED_CONFIG;
985
+ }
986
+ /** The permit list in force for this util; see {@link EmbedShapeOptions.embedDefinitions}. */
987
+ getEmbedDefinitions() {
988
+ return this.options.embedDefinitions ?? embedDefinitions;
989
+ }
990
+ /** This shape's permit-list entry and iframe url, or `null` when it has none. */
991
+ getEmbedMatch(shape) {
992
+ return getEmbedDefinition(readString(propsOf(shape), "url", ""), this.getEmbedDefinitions(), this.embedConfig);
993
+ }
994
+ /**
995
+ * The width ÷ height this embed's content wants, or `undefined` when nothing
996
+ * has an opinion about it.
997
+ *
998
+ * Public because the app needs the same answer the util uses: a "fit to
999
+ * content" command, a paste handler placing a new embed, a layout engine
1000
+ * reserving a slot. It comes from the matching definition's
1001
+ * {@link EmbedDefinition.sizeToContentAspectRatio}; a url off the permit
1002
+ * list, or a service with no fixed shape, answers `undefined`.
1003
+ */
1004
+ resolveAspectRatio(shape) {
1005
+ const ratio = this.getEmbedMatch(shape)?.definition.sizeToContentAspectRatio;
1006
+ return typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0 ? ratio : void 0;
1007
+ }
1008
+ getGeometry(shape) {
1009
+ const { w, h } = readEmbedBox(shape);
1010
+ return new Rectangle2d({ width: Math.max(1, w), height: Math.max(1, h), isFilled: true });
1011
+ }
1012
+ /** An iframe is not a quad: embeds always render through the DOM overlay. */
1013
+ getRenderStyle(_shape) {
1014
+ return null;
1015
+ }
1016
+ component(shape) {
1017
+ const { w, h } = readEmbedBox(shape);
1018
+ const url = readString(propsOf(shape), "url", "");
1019
+ const match = this.getEmbedMatch(shape);
1020
+ const box = {
1021
+ position: "absolute",
1022
+ left: 0,
1023
+ top: 0,
1024
+ width: w,
1025
+ height: h,
1026
+ boxSizing: "border-box",
1027
+ overflow: "hidden",
1028
+ borderRadius: EMBED_RADIUS
1029
+ };
1030
+ if (!match) {
1031
+ return /* @__PURE__ */ jsx(
1032
+ "div",
1033
+ {
1034
+ style: {
1035
+ ...box,
1036
+ background: EMBED_PLACEHOLDER_FILL,
1037
+ border: `1px dashed ${EMBED_PLACEHOLDER_STROKE}`,
1038
+ padding: EMBED_PLACEHOLDER_PADDING,
1039
+ display: "flex",
1040
+ alignItems: "center",
1041
+ justifyContent: "center",
1042
+ textAlign: "center",
1043
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
1044
+ fontSize: EMBED_PLACEHOLDER_FONT_SIZE,
1045
+ color: EMBED_PLACEHOLDER_TEXT,
1046
+ wordBreak: "break-all",
1047
+ pointerEvents: "none",
1048
+ userSelect: "none"
1049
+ },
1050
+ "aria-label": url ? `embed: ${url}` : "embed",
1051
+ children: url || "No embed url"
1052
+ }
1053
+ );
1054
+ }
1055
+ const isEditing = this.editor.getEditingShapeId() === shape.id;
1056
+ return /* @__PURE__ */ jsx("div", { style: box, children: /* @__PURE__ */ jsx(
1057
+ "iframe",
1058
+ {
1059
+ src: match.embedUrl,
1060
+ title: `${match.definition.title} embed`,
1061
+ width: w,
1062
+ height: h,
1063
+ sandbox: EMBED_SANDBOX,
1064
+ referrerPolicy: "strict-origin-when-cross-origin",
1065
+ loading: "lazy",
1066
+ allowFullScreen: true,
1067
+ style: { display: "block", width: "100%", height: "100%", border: 0, pointerEvents: isEditing ? "auto" : "none" }
1068
+ }
1069
+ ) });
1070
+ }
1071
+ getIndicatorPath(shape) {
1072
+ const { w, h } = readEmbedBox(shape);
1073
+ return rectPath(w, h, EMBED_RADIUS);
1074
+ }
1075
+ /**
1076
+ * "Editing" an embed is not typing into it — the shape has no text of its own.
1077
+ * It is the state in which the embedded page takes the pointer, and it is the
1078
+ * state the iframe above waits for.
1079
+ *
1080
+ * Saying no here left that state unreachable, so the frame stayed inert for
1081
+ * good and the embed was a picture of a page rather than a page: a video that
1082
+ * would not play, a map that would not pan. Saying yes gives an embed the same
1083
+ * two-step every canvas uses for something that wants the pointer for itself —
1084
+ * one click selects the shape and its handles, a double click hands the
1085
+ * pointer over, and clicking away takes it back.
1086
+ */
1087
+ canEdit(_shape) {
1088
+ return true;
1089
+ }
1090
+ /**
1091
+ * An embed whose service declared a fixed content shape resizes
1092
+ * proportionally; anything else is a free box.
1093
+ */
1094
+ // SEMANTICS-ASSUMED: tying the lock to `sizeToContentAspectRatio` is the
1095
+ // reading that makes the two features one feature — the ratio is declared
1096
+ // once and both sizing paths honour it — and it is a no-op for every
1097
+ // built-in, none of which declares one.
1098
+ isAspectRatioLocked(shape) {
1099
+ return this.resolveAspectRatio(shape) !== void 0;
1100
+ }
1101
+ /**
1102
+ * A new embed is created at its service's content ratio, keeping the width
1103
+ * it was asked for. Nothing happens for a service with no declared ratio.
1104
+ */
1105
+ onBeforeCreate(next) {
1106
+ const ratio = this.resolveAspectRatio(next);
1107
+ if (ratio === void 0) return;
1108
+ const { w } = readEmbedBox(next);
1109
+ const h = Math.max(1, w / ratio);
1110
+ if (h === readEmbedBox(next).h) return;
1111
+ return { ...next, props: { ...next.props, h } };
1112
+ }
1113
+ };
1114
+
1115
+ export { BOOKMARK_BANNER_FILL, BOOKMARK_BANNER_HEIGHT, BOOKMARK_FAVICON_SIZE, BOOKMARK_FILL, BOOKMARK_GAP, BOOKMARK_HEIGHT, BOOKMARK_META_COLOR, BOOKMARK_META_FONT_SIZE, BOOKMARK_META_HEIGHT, BOOKMARK_MIN_BODY_HEIGHT, BOOKMARK_PADDING, BOOKMARK_RADIUS, BOOKMARK_STROKE, BOOKMARK_STROKE_WIDTH, BOOKMARK_TEXT_COLOR, BOOKMARK_TEXT_FONT_SIZE, BOOKMARK_TITLE_COLOR, BOOKMARK_TITLE_FONT_SIZE, BOOKMARK_TITLE_HEIGHT, BOOKMARK_WIDTH, BookmarkShapeUtil, DEFAULT_EMBED_DEFINITIONS, EMBED_HEIGHT, EMBED_PLACEHOLDER_FILL, EMBED_PLACEHOLDER_FONT_SIZE, EMBED_PLACEHOLDER_PADDING, EMBED_PLACEHOLDER_STROKE, EMBED_PLACEHOLDER_TEXT, EMBED_RADIUS, EMBED_SANDBOX, EMBED_SHAPE_PERMISSION_NAMES, EMBED_WIDTH, EmbedShapeUtil, RICH_TEXT_MARKS, RICH_TEXT_NODES, applyPlainTextToRichText, arrowBindingMigrations, arrowBindingProps, arrowShapeMigrations, arrowShapeProps, arrowShapeVersions, asRichText, bookmarkShapeMigrations, bookmarkShapeProps, boxPath, canBuildPath, createEmptyBookmarkShape, drawShapeMigrations, drawShapeProps, embedDefinitions, embedShapeMigrations, embedShapePermissionsToAllow, embedShapeProps, escapeHtml, frameShapeMigrations, frameShapeProps, geoShapeProps, getBookmarkAsset, getBookmarkCard, getBookmarkHostname, getBookmarkLayout, getEmbedDefinition, getEmbedDisplayValues, getEmbedInfo, getRichTextEditorFactory, groupShapeMigrations, groupShapeProps, highlightShapeMigrations, highlightShapeProps, imageShapeMigrations, imageShapeProps, isRichText, lineShapeMigrations, lineShapeProps, loadTipTapStarterExtensions, noteShapeMigrations, noteShapeProps, polylinePath, propsOf, readArray, readBoolean, readEnum, readNumber, readPoint, readRecord, readRichText, readString, readStyle, readText, rectPath, registerRichTextEditorFactory, richTextEquals, richTextToBlocks, richTextToHtml, richTextToText, richTextValidator, safeHref, svgPath, textShapeMigrations, textShapeProps, tipTapDefaultExtensions, toRichText, unknownEmbedShapePermissionOverrides, videoShapeMigrations, videoShapeProps };
1116
+ //# sourceMappingURL=chunk-OCYJAMXT.js.map
1117
+ //# sourceMappingURL=chunk-OCYJAMXT.js.map