@os-team-11/editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1358 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BidEditor: () => BidEditor2,
34
+ BidEditorCore: () => BidEditor,
35
+ bidSchema: () => bidSchema,
36
+ bidToEditorElements: () => bidToEditorElements,
37
+ editorElementsToSections: () => editorElementsToSections,
38
+ printBid: () => printBid,
39
+ validateBid: () => validateBid
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+
43
+ // src/schema/validate.ts
44
+ var import_zod = require("zod");
45
+ var bidSchema = import_zod.z.object({
46
+ meta: import_zod.z.object({
47
+ title: import_zod.z.string().min(1),
48
+ docNo: import_zod.z.string().min(1),
49
+ tenderer: import_zod.z.string().min(1),
50
+ version: import_zod.z.string().min(1),
51
+ generatedAt: import_zod.z.string().min(1),
52
+ generator: import_zod.z.string().optional()
53
+ }),
54
+ page: import_zod.z.object({
55
+ size: import_zod.z.union([
56
+ import_zod.z.literal("A4"),
57
+ import_zod.z.literal("Letter"),
58
+ import_zod.z.object({
59
+ width: import_zod.z.number().positive(),
60
+ height: import_zod.z.number().positive()
61
+ })
62
+ ]),
63
+ margins: import_zod.z.object({
64
+ top: import_zod.z.number().nonnegative(),
65
+ right: import_zod.z.number().nonnegative(),
66
+ bottom: import_zod.z.number().nonnegative(),
67
+ left: import_zod.z.number().nonnegative()
68
+ }),
69
+ header: import_zod.z.object({
70
+ template: import_zod.z.string(),
71
+ showOnFirstPage: import_zod.z.boolean()
72
+ }).optional(),
73
+ footer: import_zod.z.object({ template: import_zod.z.string() }).optional(),
74
+ watermark: import_zod.z.object({
75
+ text: import_zod.z.string(),
76
+ opacity: import_zod.z.number().min(0).max(1)
77
+ }).optional()
78
+ }),
79
+ sections: import_zod.z.array(
80
+ import_zod.z.object({
81
+ id: import_zod.z.string().min(1),
82
+ title: import_zod.z.string().min(1),
83
+ level: import_zod.z.union([import_zod.z.literal(1), import_zod.z.literal(2), import_zod.z.literal(3)]),
84
+ blocks: import_zod.z.array(
85
+ import_zod.z.discriminatedUnion("type", [
86
+ import_zod.z.object({
87
+ type: import_zod.z.literal("heading"),
88
+ level: import_zod.z.union([import_zod.z.literal(2), import_zod.z.literal(3), import_zod.z.literal(4)]),
89
+ text: import_zod.z.string().min(1)
90
+ }),
91
+ import_zod.z.object({
92
+ type: import_zod.z.literal("paragraph"),
93
+ text: import_zod.z.string()
94
+ }),
95
+ import_zod.z.object({
96
+ type: import_zod.z.literal("list"),
97
+ ordered: import_zod.z.boolean(),
98
+ items: import_zod.z.array(import_zod.z.string())
99
+ }),
100
+ import_zod.z.object({
101
+ type: import_zod.z.literal("table"),
102
+ rows: import_zod.z.array(import_zod.z.array(import_zod.z.string())),
103
+ caption: import_zod.z.string().optional()
104
+ }),
105
+ import_zod.z.object({
106
+ type: import_zod.z.literal("image"),
107
+ src: import_zod.z.string().min(1),
108
+ alt: import_zod.z.string().optional(),
109
+ caption: import_zod.z.string().optional()
110
+ })
111
+ ])
112
+ )
113
+ })
114
+ ).min(1)
115
+ });
116
+ function validateBid(input) {
117
+ return bidSchema.safeParse(input);
118
+ }
119
+
120
+ // src/adapter/toEditor.ts
121
+ function convertBlock(block, nextListId) {
122
+ switch (block.type) {
123
+ case "paragraph":
124
+ return [{ value: block.text + "\n" }];
125
+ case "heading":
126
+ return [
127
+ {
128
+ value: block.text,
129
+ type: "title",
130
+ level: block.level
131
+ }
132
+ ];
133
+ case "list": {
134
+ const listId = nextListId();
135
+ const listType = block.ordered ? "ol" : "ul";
136
+ return block.items.map((item) => ({
137
+ value: item + "\n",
138
+ listType,
139
+ listId,
140
+ valueList: [{ value: item }]
141
+ }));
142
+ }
143
+ case "table": {
144
+ const out = [];
145
+ if (block.caption) {
146
+ out.push({ value: block.caption + "\n" });
147
+ }
148
+ for (const row of block.rows) {
149
+ out.push({ value: row.join(" ") + "\n" });
150
+ }
151
+ return out;
152
+ }
153
+ case "image": {
154
+ const out = [];
155
+ if (block.caption) {
156
+ out.push({ value: block.caption + "\n" });
157
+ }
158
+ out.push({
159
+ value: block.src,
160
+ type: "image",
161
+ alt: block.alt
162
+ });
163
+ return out;
164
+ }
165
+ }
166
+ }
167
+ function bidToEditorElements(sections) {
168
+ const result = [];
169
+ let listCounter = 0;
170
+ const nextListId = () => `bid-list-${++listCounter}`;
171
+ for (const section of sections) {
172
+ result.push({
173
+ value: section.title,
174
+ type: "title",
175
+ level: section.level
176
+ });
177
+ for (const block of section.blocks) {
178
+ result.push(...convertBlock(block, nextListId));
179
+ }
180
+ }
181
+ return result;
182
+ }
183
+
184
+ // src/adapter/toBid.ts
185
+ function isTabRow(value) {
186
+ return value.includes(" ") && value.endsWith("\n");
187
+ }
188
+ function editorElementsToSections(elements) {
189
+ const sections = [];
190
+ let current = null;
191
+ let pendingList = null;
192
+ let pendingTableRows = [];
193
+ function flushList() {
194
+ if (current && pendingList && pendingList.items.length > 0) {
195
+ current.blocks.push({
196
+ type: "list",
197
+ ordered: pendingList.ordered,
198
+ items: pendingList.items
199
+ });
200
+ }
201
+ pendingList = null;
202
+ }
203
+ function flushTable() {
204
+ if (current && pendingTableRows.length > 0) {
205
+ current.blocks.push({
206
+ type: "table",
207
+ rows: pendingTableRows
208
+ });
209
+ }
210
+ pendingTableRows = [];
211
+ }
212
+ for (const el of elements) {
213
+ if (el.type === "title" && el.level === 1) {
214
+ flushList();
215
+ flushTable();
216
+ current = {
217
+ id: `sec-${sections.length + 1}`,
218
+ title: el.value,
219
+ level: 1,
220
+ blocks: []
221
+ };
222
+ sections.push(current);
223
+ continue;
224
+ }
225
+ if (!current) {
226
+ continue;
227
+ }
228
+ if (el.type === "title" && typeof el.level === "number" && el.level >= 2) {
229
+ flushList();
230
+ flushTable();
231
+ current.blocks.push({
232
+ type: "heading",
233
+ level: el.level,
234
+ text: el.value
235
+ });
236
+ continue;
237
+ }
238
+ if (el.listType && el.listId) {
239
+ flushTable();
240
+ if (!pendingList || pendingList.listId !== el.listId) {
241
+ flushList();
242
+ pendingList = {
243
+ ordered: el.listType === "ol",
244
+ items: [],
245
+ listId: el.listId
246
+ };
247
+ }
248
+ pendingList.items.push(el.value.replace(/\n$/, ""));
249
+ continue;
250
+ }
251
+ if (el.type === "image") {
252
+ flushList();
253
+ flushTable();
254
+ current.blocks.push({
255
+ type: "image",
256
+ src: el.value,
257
+ alt: el.alt
258
+ });
259
+ continue;
260
+ }
261
+ if (isTabRow(el.value)) {
262
+ flushList();
263
+ const cells = el.value.slice(0, -1).split(" ");
264
+ pendingTableRows.push(cells);
265
+ continue;
266
+ }
267
+ flushList();
268
+ flushTable();
269
+ const text = el.value.replace(/\n$/, "");
270
+ if (text.length > 0) {
271
+ current.blocks.push({ type: "paragraph", text });
272
+ }
273
+ }
274
+ flushList();
275
+ flushTable();
276
+ return sections;
277
+ }
278
+
279
+ // src/editor/BidEditor.ts
280
+ var import_canvas_editor = __toESM(require("@hufe921/canvas-editor"), 1);
281
+ var BidEditor = class {
282
+ constructor(container, bid) {
283
+ this.container = container;
284
+ this.meta = bid.meta;
285
+ this.page = bid.page;
286
+ const elements = bidToEditorElements(bid.sections);
287
+ this.editor = new import_canvas_editor.default(
288
+ container,
289
+ { main: elements }
290
+ );
291
+ }
292
+ load(bid) {
293
+ this.meta = bid.meta;
294
+ this.page = bid.page;
295
+ const elements = bidToEditorElements(bid.sections);
296
+ const cmd = this.editor.command;
297
+ if (typeof cmd.executeSelectAll === "function" && typeof cmd.executeBackspace === "function") {
298
+ cmd.executeSelectAll();
299
+ cmd.executeBackspace();
300
+ }
301
+ cmd.executeInsertElementList(elements);
302
+ }
303
+ getBid() {
304
+ const value = this.editor.command.getValue();
305
+ const sections = editorElementsToSections(value.data.main || []);
306
+ return {
307
+ meta: this.meta,
308
+ page: this.page,
309
+ sections
310
+ };
311
+ }
312
+ getCommand() {
313
+ return this.editor.command;
314
+ }
315
+ /** 直接暴露 canvas-editor 的 command,便于 hooks 模式访问 */
316
+ get command() {
317
+ return this.editor.command;
318
+ }
319
+ destroy() {
320
+ if (typeof this.editor.destroy === "function") {
321
+ ;
322
+ this.editor.destroy();
323
+ }
324
+ this.container.innerHTML = "";
325
+ }
326
+ };
327
+
328
+ // src/export/pdf.ts
329
+ function printBid() {
330
+ window.print();
331
+ }
332
+
333
+ // src/react/BidEditor.tsx
334
+ var import_react10 = require("react");
335
+
336
+ // src/react/hooks/useBidEditor.ts
337
+ var import_react = require("react");
338
+ function useBidEditor({ containerRef, bid, onChange }) {
339
+ const editorRef = (0, import_react.useRef)(null);
340
+ const [, force] = (0, import_react.useState)(0);
341
+ const onChangeRef = (0, import_react.useRef)(onChange);
342
+ onChangeRef.current = onChange;
343
+ (0, import_react.useEffect)(() => {
344
+ const container = containerRef.current;
345
+ if (!container) return;
346
+ editorRef.current = new BidEditor(container, bid);
347
+ force((x) => x + 1);
348
+ return () => {
349
+ editorRef.current?.destroy();
350
+ editorRef.current = null;
351
+ };
352
+ }, [containerRef, bid]);
353
+ const handle = {
354
+ getBid: (0, import_react.useCallback)(() => {
355
+ const e = editorRef.current;
356
+ if (!e) return bid;
357
+ const next = e.getBid();
358
+ onChangeRef.current?.(next);
359
+ return next;
360
+ }, [bid]),
361
+ getCommand: (0, import_react.useCallback)(() => editorRef.current?.getCommand(), []),
362
+ print: (0, import_react.useCallback)(() => printBid(), []),
363
+ focus: (0, import_react.useCallback)(() => {
364
+ }, [])
365
+ };
366
+ return { handle, editorRef };
367
+ }
368
+
369
+ // src/react/hooks/useFormatState.ts
370
+ var import_react2 = require("react");
371
+ var EMPTY = {
372
+ bold: false,
373
+ italic: false,
374
+ underline: false,
375
+ strikeout: false,
376
+ superscript: false,
377
+ subscript: false,
378
+ align: null,
379
+ level: null,
380
+ listType: null,
381
+ color: null,
382
+ highlight: null
383
+ };
384
+ function useFormatState(editorRef) {
385
+ const [state, setState] = (0, import_react2.useState)(EMPTY);
386
+ (0, import_react2.useEffect)(() => {
387
+ let readyTimer;
388
+ let pollTimer;
389
+ let attempts = 0;
390
+ const read = () => {
391
+ const editor = editorRef.current;
392
+ const getFormat = editor?.command?.getFormat;
393
+ if (typeof getFormat !== "function") return false;
394
+ const raw = getFormat.call(editor.command);
395
+ setState({
396
+ bold: !!raw.bold,
397
+ italic: !!raw.italic,
398
+ underline: !!raw.underline,
399
+ strikeout: !!raw.strikeout,
400
+ superscript: !!raw.superscript,
401
+ subscript: !!raw.subscript,
402
+ align: raw.rowFlex ?? null,
403
+ level: raw.level ?? null,
404
+ listType: raw.listType ?? null,
405
+ color: raw.color ?? null,
406
+ highlight: raw.highlight ?? null
407
+ });
408
+ return true;
409
+ };
410
+ const startPolling = () => {
411
+ if (read()) {
412
+ clearInterval(readyTimer);
413
+ readyTimer = void 0;
414
+ pollTimer = setInterval(read, 200);
415
+ } else {
416
+ attempts++;
417
+ if (attempts > 100) {
418
+ clearInterval(readyTimer);
419
+ readyTimer = void 0;
420
+ }
421
+ }
422
+ };
423
+ readyTimer = setInterval(startPolling, 100);
424
+ startPolling();
425
+ return () => {
426
+ if (readyTimer) clearInterval(readyTimer);
427
+ if (pollTimer) clearInterval(pollTimer);
428
+ };
429
+ }, [editorRef]);
430
+ return state;
431
+ }
432
+
433
+ // src/react/hooks/useBreakpoint.ts
434
+ var import_react3 = require("react");
435
+ var MOBILE_QUERY = "(max-width: 767px)";
436
+ var TABLET_QUERY = "(min-width: 768px) and (max-width: 1023px)";
437
+ function readBreakpoint() {
438
+ if (typeof window === "undefined" || !window.matchMedia) return "desktop";
439
+ if (window.matchMedia(MOBILE_QUERY).matches) return "mobile";
440
+ if (window.matchMedia(TABLET_QUERY).matches) return "tablet";
441
+ return "desktop";
442
+ }
443
+ function useBreakpoint(forced) {
444
+ const [bp, setBp] = (0, import_react3.useState)(forced ?? readBreakpoint);
445
+ (0, import_react3.useEffect)(() => {
446
+ if (forced) {
447
+ setBp(forced);
448
+ return;
449
+ }
450
+ if (typeof window === "undefined" || !window.matchMedia) return;
451
+ const mobileMql = window.matchMedia(MOBILE_QUERY);
452
+ const tabletMql = window.matchMedia(TABLET_QUERY);
453
+ const handler = () => setBp(readBreakpoint());
454
+ mobileMql.addEventListener("change", handler);
455
+ tabletMql.addEventListener("change", handler);
456
+ return () => {
457
+ mobileMql.removeEventListener("change", handler);
458
+ tabletMql.removeEventListener("change", handler);
459
+ };
460
+ }, [forced]);
461
+ return bp;
462
+ }
463
+
464
+ // src/react/theme/tokens.ts
465
+ var LIGHT_TOKENS = {
466
+ colors: {
467
+ primary: "#2383E2",
468
+ text: "#37352F",
469
+ border: "#EDEDED",
470
+ background: "#FAFAFA",
471
+ hover: "#F1F1F0",
472
+ active: "#E8E8E7"
473
+ },
474
+ radius: { button: 4, container: 8 },
475
+ spacing: { button: 6, group: 4 },
476
+ fontSize: 13,
477
+ fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif"
478
+ };
479
+ var DARK_TOKENS = {
480
+ ...LIGHT_TOKENS,
481
+ dark: true,
482
+ colors: {
483
+ primary: "#4A9BE8",
484
+ text: "#E6E6E6",
485
+ border: "#2A2A2A",
486
+ background: "#1F1F1F",
487
+ hover: "#2D2D2D",
488
+ active: "#3A3A3A"
489
+ }
490
+ };
491
+
492
+ // src/react/theme/mergeTheme.ts
493
+ function deepMerge(base, override) {
494
+ if (!override) return base;
495
+ const result = { ...base };
496
+ for (const key of Object.keys(override)) {
497
+ const ov = override[key];
498
+ if (ov && typeof ov === "object" && !Array.isArray(ov)) {
499
+ result[key] = deepMerge(
500
+ base[key],
501
+ ov
502
+ );
503
+ } else if (ov !== void 0) {
504
+ result[key] = ov;
505
+ }
506
+ }
507
+ return result;
508
+ }
509
+ function mergeTheme(override) {
510
+ const base = override?.dark ? DARK_TOKENS : LIGHT_TOKENS;
511
+ const merged = deepMerge(base, override);
512
+ if (override?.dark !== void 0) {
513
+ merged.dark = override.dark;
514
+ }
515
+ return merged;
516
+ }
517
+
518
+ // src/react/theme/cssVars.ts
519
+ function themeToCssVars(theme) {
520
+ return {
521
+ "--bke-color-primary": theme.colors.primary,
522
+ "--bke-color-text": theme.colors.text,
523
+ "--bke-color-border": theme.colors.border,
524
+ "--bke-color-bg": theme.colors.background,
525
+ "--bke-color-hover": theme.colors.hover,
526
+ "--bke-color-active": theme.colors.active,
527
+ "--bke-radius-button": `${theme.radius.button}px`,
528
+ "--bke-radius-container": `${theme.radius.container}px`,
529
+ "--bke-spacing-button": `${theme.spacing.button}px`,
530
+ "--bke-spacing-group": `${theme.spacing.group}px`,
531
+ "--bke-font-size": `${theme.fontSize}px`,
532
+ "--bke-font-family": theme.fontFamily
533
+ };
534
+ }
535
+
536
+ // src/react/Toolbar.tsx
537
+ var import_react9 = require("react");
538
+
539
+ // src/react/config/buttonCommands.ts
540
+ var DEFAULT_TOOLBAR_GROUPS = [
541
+ {
542
+ id: "font",
543
+ buttons: ["font-family", "font-size"]
544
+ },
545
+ {
546
+ id: "format",
547
+ buttons: ["bold", "italic", "underline", "strikeout", "superscript", "subscript"]
548
+ },
549
+ {
550
+ id: "color",
551
+ buttons: ["color", "highlight"]
552
+ },
553
+ {
554
+ id: "layout",
555
+ buttons: ["align-left", "align-center", "align-right", "align-justify", "line-height"]
556
+ },
557
+ {
558
+ id: "structure",
559
+ buttons: ["h1", "h2", "h3", "h4", "list-ol", "list-ul", "indent", "outdent"]
560
+ },
561
+ {
562
+ id: "history",
563
+ buttons: ["undo", "redo", "format-painter", "clear-format"]
564
+ },
565
+ {
566
+ id: "insert",
567
+ buttons: ["table", "image", "hyperlink", "separator", "page-break", "math", "control", "date"]
568
+ },
569
+ {
570
+ id: "collab",
571
+ buttons: ["comment"]
572
+ }
573
+ ];
574
+ var ALL_TOOLBAR_BUTTON_IDS = DEFAULT_TOOLBAR_GROUPS.flatMap(
575
+ (g) => g.buttons.filter((b) => b !== "|")
576
+ );
577
+ var TABLET_COLLAPSE_GROUP_IDS = ["history", "insert", "collab"];
578
+
579
+ // src/react/config/mergeConfig.ts
580
+ var VALID_BUTTON_SET = new Set(ALL_TOOLBAR_BUTTON_IDS);
581
+ function isValidButton(id) {
582
+ return VALID_BUTTON_SET.has(id);
583
+ }
584
+ function filterValidItems(items) {
585
+ return items.filter((item) => item === "|" || isValidButton(item));
586
+ }
587
+ function applyHide(groups, hide) {
588
+ const hideSet = new Set(hide);
589
+ return groups.map((g) => ({
590
+ ...g,
591
+ buttons: g.buttons.filter((b) => b === "|" || !hideSet.has(b))
592
+ })).filter((g) => g.buttons.length > 0);
593
+ }
594
+ function mergeToolbarConfig(config) {
595
+ if (config === false) return [];
596
+ if (config === true) {
597
+ return DEFAULT_TOOLBAR_GROUPS.map((g) => ({ ...g, buttons: [...g.buttons] }));
598
+ }
599
+ if (Array.isArray(config)) {
600
+ return [{ id: "custom", buttons: filterValidItems(config) }];
601
+ }
602
+ if (config.groups) {
603
+ return config.groups.map((g) => ({
604
+ id: g.id,
605
+ buttons: filterValidItems(g.buttons)
606
+ }));
607
+ }
608
+ if (config.hide) {
609
+ return applyHide(
610
+ DEFAULT_TOOLBAR_GROUPS.map((g) => ({ ...g, buttons: [...g.buttons] })),
611
+ config.hide
612
+ );
613
+ }
614
+ return DEFAULT_TOOLBAR_GROUPS.map((g) => ({ ...g, buttons: [...g.buttons] }));
615
+ }
616
+
617
+ // src/react/hooks/useCommand.ts
618
+ var import_react4 = require("react");
619
+ var METHOD_MAP = {
620
+ bold: "executeBold",
621
+ italic: "executeItalic",
622
+ underline: "executeUnderline",
623
+ strikeout: "executeStrikeout",
624
+ superscript: "executeSuperscript",
625
+ subscript: "executeSubscript",
626
+ color: "executeColor",
627
+ highlight: "executeHighlight",
628
+ "align-left": "executeRowFlex",
629
+ "align-center": "executeRowFlex",
630
+ "align-right": "executeRowFlex",
631
+ "align-justify": "executeRowFlex",
632
+ "line-height": "executeRowMargin",
633
+ h1: "executeTitle",
634
+ h2: "executeTitle",
635
+ h3: "executeTitle",
636
+ h4: "executeTitle",
637
+ "list-ol": "executeList",
638
+ "list-ul": "executeList",
639
+ indent: "executeIndent",
640
+ outdent: "executeOutdent",
641
+ undo: "executeUndo",
642
+ redo: "executeRedo",
643
+ "format-painter": "executePainter",
644
+ "clear-format": "executeFormat",
645
+ table: "executeInsertTable",
646
+ image: "executeImage",
647
+ hyperlink: "executeHyperlink",
648
+ separator: "executeSeparator",
649
+ "page-break": "executePageBreak",
650
+ math: "executeInsertElementList",
651
+ control: "executeInsertControl",
652
+ date: "executeInsertControl",
653
+ comment: "executeInsertGroupAnnotation",
654
+ "font-family": "executeFont",
655
+ "font-size": "executeSize"
656
+ };
657
+ function useCommand(editorRef) {
658
+ return {
659
+ execute: (0, import_react4.useCallback)(
660
+ (buttonId, ...args) => {
661
+ const editor = editorRef.current;
662
+ const method = METHOD_MAP[buttonId];
663
+ if (!editor?.command || !method) return;
664
+ const fn = editor.command[method];
665
+ if (typeof fn !== "function") return;
666
+ if (buttonId === "align-left" || buttonId === "align-center" || buttonId === "align-right" || buttonId === "align-justify") {
667
+ const align = buttonId.replace("align-", "");
668
+ fn.call(editor.command, align);
669
+ return;
670
+ }
671
+ if (buttonId === "h1" || buttonId === "h2" || buttonId === "h3" || buttonId === "h4") {
672
+ const level = Number(buttonId[1]);
673
+ fn.call(editor.command, level);
674
+ return;
675
+ }
676
+ if (buttonId === "list-ol") {
677
+ ;
678
+ fn.call(editor.command, "ol", "decimal");
679
+ return;
680
+ }
681
+ if (buttonId === "list-ul") {
682
+ ;
683
+ fn.call(editor.command, "ul", "disc");
684
+ return;
685
+ }
686
+ if (buttonId === "date") {
687
+ ;
688
+ fn.call(editor.command, {
689
+ type: "control",
690
+ value: "",
691
+ control: { type: "date", code: "bid-date", placeholder: "\u9009\u62E9\u65E5\u671F" }
692
+ });
693
+ return;
694
+ }
695
+ ;
696
+ fn.apply(editor.command, args);
697
+ },
698
+ [editorRef]
699
+ )
700
+ };
701
+ }
702
+
703
+ // src/react/icons.tsx
704
+ var import_jsx_runtime = require("react/jsx-runtime");
705
+ var B = ({ d }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d }) });
706
+ var ICONS = {
707
+ "font-family": /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12 }, children: "Aa" }),
708
+ "font-size": /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12 }, children: "14" }),
709
+ bold: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z" }),
710
+ italic: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M19 4h-9 M14 20H5 M15 4L9 20" }),
711
+ underline: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M6 3v9a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3 M4 21h16" }),
712
+ strikeout: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M16 4H9a3 3 0 0 0-2.83 4 M14 12a3 3 0 0 1 2.83 4H7 M4 12h16" }),
713
+ superscript: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M4 19l6-6 6 6 M14 5l4-2 0 4" }),
714
+ subscript: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M4 5l6 6 6-6 M14 14l4 4 M18 14v4h-4" }),
715
+ color: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: [
716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 4h16v12H4z", fill: "currentColor", opacity: "0.2" }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 4h16v12H4z M4 20h16", stroke: "currentColor", strokeWidth: "2", fill: "none" })
718
+ ] }),
719
+ highlight: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 11l-4 4v4h4l4-4 M14 7l3-3 4 4-3 3-4-4z", fill: "currentColor", stroke: "currentColor", strokeWidth: "2", fillOpacity: "0.3" }) }),
720
+ "align-left": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 6h18 M3 12h12 M3 18h18 M3 24h8" }),
721
+ "align-center": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 6h18 M6 12h12 M3 18h18 M6 24h12" }),
722
+ "align-right": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 6h18 M9 12h12 M3 18h18 M12 24h9" }),
723
+ "align-justify": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 6h18 M3 12h18 M3 18h18 M3 24h18" }),
724
+ "line-height": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M4 12h16 M7 6l-3 6 3 6 M17 6l3 6-3 6" }),
725
+ h1: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 700 }, children: "H1" }),
726
+ h2: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 700 }, children: "H2" }),
727
+ h3: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 700 }, children: "H3" }),
728
+ h4: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontWeight: 700 }, children: "H4" }),
729
+ "list-ol": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M10 6h10 M10 12h10 M10 18h10 M4 6l1-1v4 M4 12h2a1 1 0 0 1 0 2H4l2 2 M4 18h2a1 1 0 0 1 0 2H4l2 2" }),
730
+ "list-ul": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M10 6h10 M10 12h10 M10 18h10 M4 6h.01 M4 12h.01 M4 18h.01" }),
731
+ indent: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 4h18 M3 12h10 M21 12h-8 M3 20h18 M14 9l3 3-3 3" }),
732
+ outdent: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 4h18 M11 12h10 M3 20h18 M7 9l-3 3 3 3" }),
733
+ undo: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 8h11a5 5 0 0 1 0 10h-5 M3 8l4-4 M3 8l4 4" }),
734
+ redo: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M21 8H10a5 5 0 0 0 0 10h5 M21 8l-4-4 M21 8l-4 4" }),
735
+ "format-painter": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 7l4-4h7l4 4-3 3-2-2v6H8V8L6 10z" }),
736
+ "clear-format": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M4 7V4h16v3 M9 20h6 M12 4v16" }),
737
+ table: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 3h18v18H3z M3 9h18 M3 15h18 M9 3v18 M15 3v18" }),
738
+ image: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 3h18v18H3z M3 15l5-5 4 4 3-3 6 6 M8.5 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" }),
739
+ hyperlink: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1 M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 1 0 5.66 5.66l1-1" }),
740
+ separator: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 12h18" }),
741
+ "page-break": /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 7h18 M3 13l3-3 3 3 3-3 3 3 3-3 3 3 M3 19h18" }),
742
+ math: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, fontStyle: "italic" }, children: "\u2211" }),
743
+ control: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 6h18v12H3z M7 10h10" }),
744
+ date: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 5h18v16H3z M3 9h18 M8 3v4 M16 3v4" }),
745
+ comment: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(B, { d: "M3 4h18v12H7l-4 4V4z" })
746
+ };
747
+
748
+ // src/react/ToolbarButton.tsx
749
+ var import_jsx_runtime2 = require("react/jsx-runtime");
750
+ function ToolbarButton({
751
+ id,
752
+ active = false,
753
+ disabled = false,
754
+ onClick,
755
+ renderer: Renderer,
756
+ label,
757
+ shortcut
758
+ }) {
759
+ if (Renderer) {
760
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
761
+ Renderer,
762
+ {
763
+ id,
764
+ active,
765
+ disabled,
766
+ onClick,
767
+ icon: ICONS[id],
768
+ label,
769
+ shortcut
770
+ }
771
+ );
772
+ }
773
+ const className = [
774
+ "bke-btn",
775
+ active ? "bke-btn-active" : "",
776
+ disabled ? "bke-btn-disabled" : ""
777
+ ].filter(Boolean).join(" ");
778
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
779
+ "button",
780
+ {
781
+ type: "button",
782
+ className,
783
+ disabled,
784
+ title: shortcut ?? label,
785
+ onClick,
786
+ "data-bke-id": id,
787
+ children: ICONS[id]
788
+ }
789
+ );
790
+ }
791
+
792
+ // src/react/Dropdown.tsx
793
+ var import_react5 = require("react");
794
+ var import_jsx_runtime3 = require("react/jsx-runtime");
795
+ function Dropdown({ trigger, children, open, onOpenChange, align = "left" }) {
796
+ const [internalOpen, setInternalOpen] = (0, import_react5.useState)(false);
797
+ const isControlled = open !== void 0;
798
+ const isOpen = isControlled ? open : internalOpen;
799
+ const ref = (0, import_react5.useRef)(null);
800
+ const setOpen = (next) => {
801
+ if (!isControlled) setInternalOpen(next);
802
+ onOpenChange?.(next);
803
+ };
804
+ (0, import_react5.useEffect)(() => {
805
+ if (!isOpen) return;
806
+ const onDocClick = (e) => {
807
+ if (ref.current && !ref.current.contains(e.target)) {
808
+ setOpen(false);
809
+ }
810
+ };
811
+ document.addEventListener("mousedown", onDocClick);
812
+ return () => document.removeEventListener("mousedown", onDocClick);
813
+ }, [isOpen]);
814
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bke-dropdown", ref, children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "bke-dropdown-trigger", onClick: () => setOpen(!isOpen), children: trigger }),
816
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `bke-dropdown-panel bke-dropdown-align-${align}`, children })
817
+ ] });
818
+ }
819
+
820
+ // src/react/ColorPicker.tsx
821
+ var import_jsx_runtime4 = require("react/jsx-runtime");
822
+ var DEFAULT_COLOR_PALETTE = [
823
+ "#000000",
824
+ "#666666",
825
+ "#999999",
826
+ "#FFFFFF",
827
+ "#FF0000",
828
+ "#FF6B35",
829
+ "#FFD700",
830
+ "#00C853",
831
+ "#2383E2",
832
+ "#6C5CE7",
833
+ "#E84393",
834
+ "#00B894"
835
+ ];
836
+ function ColorPicker({ palette = DEFAULT_COLOR_PALETTE, onSelect }) {
837
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "bke-color-picker", children: palette.map((color) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
838
+ "button",
839
+ {
840
+ type: "button",
841
+ className: "bke-color-swatch",
842
+ title: color,
843
+ style: { backgroundColor: color },
844
+ onClick: () => onSelect(color)
845
+ },
846
+ color
847
+ )) });
848
+ }
849
+
850
+ // src/react/TableGridPicker.tsx
851
+ var import_react6 = require("react");
852
+ var import_jsx_runtime5 = require("react/jsx-runtime");
853
+ function TableGridPicker({ maxRows = 6, maxCols = 6, onSelect }) {
854
+ const [hoverRow, setHoverRow] = (0, import_react6.useState)(0);
855
+ const [hoverCol, setHoverCol] = (0, import_react6.useState)(0);
856
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "bke-table-grid", children: [
857
+ Array.from({ length: maxRows }).map((_, r) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "bke-grid-row", children: Array.from({ length: maxCols }).map((_2, c) => {
858
+ const active = r < hoverRow && c < hoverCol;
859
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
860
+ "button",
861
+ {
862
+ type: "button",
863
+ className: active ? "bke-grid-cell bke-grid-cell-active" : "bke-grid-cell",
864
+ onMouseEnter: () => {
865
+ setHoverRow(r + 1);
866
+ setHoverCol(c + 1);
867
+ },
868
+ onClick: () => onSelect(r + 1, c + 1)
869
+ },
870
+ c
871
+ );
872
+ }) }, r)),
873
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "bke-grid-label", children: hoverRow > 0 && hoverCol > 0 ? `${hoverRow} \xD7 ${hoverCol}` : "\u9009\u62E9\u884C\u5217" })
874
+ ] });
875
+ }
876
+
877
+ // src/react/HyperlinkDialog.tsx
878
+ var import_react7 = require("react");
879
+ var import_jsx_runtime6 = require("react/jsx-runtime");
880
+ function HyperlinkDialog({
881
+ open,
882
+ initialUrl = "",
883
+ initialText = "",
884
+ onSubmit,
885
+ onCancel
886
+ }) {
887
+ const [url, setUrl] = (0, import_react7.useState)(initialUrl);
888
+ const [text, setText] = (0, import_react7.useState)(initialText);
889
+ (0, import_react7.useEffect)(() => {
890
+ if (open) {
891
+ setUrl(initialUrl);
892
+ setText(initialText);
893
+ }
894
+ }, [open, initialUrl, initialText]);
895
+ if (!open) return null;
896
+ const submit = () => {
897
+ if (!url.trim()) return;
898
+ onSubmit({ url: url.trim(), text: text.trim() });
899
+ };
900
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "bke-dialog-overlay", onClick: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bke-dialog", onClick: (e) => e.stopPropagation(), children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "bke-dialog-title", children: "\u63D2\u5165\u94FE\u63A5" }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "bke-field", children: [
903
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "URL" }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
905
+ "input",
906
+ {
907
+ type: "url",
908
+ placeholder: "https://",
909
+ value: url,
910
+ onChange: (e) => setUrl(e.target.value),
911
+ autoFocus: true
912
+ }
913
+ )
914
+ ] }),
915
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "bke-field", children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "\u6587\u5B57" }),
917
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
918
+ "input",
919
+ {
920
+ type: "text",
921
+ placeholder: "\u94FE\u63A5\u6587\u5B57\uFF08\u53EF\u9009\uFF09",
922
+ value: text,
923
+ onChange: (e) => setText(e.target.value)
924
+ }
925
+ )
926
+ ] }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bke-dialog-actions", children: [
928
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: onCancel, children: "\u53D6\u6D88" }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: submit, disabled: !url.trim(), children: "\u786E\u5B9A" })
930
+ ] })
931
+ ] }) });
932
+ }
933
+
934
+ // src/react/LatexDialog.tsx
935
+ var import_react8 = require("react");
936
+ var import_jsx_runtime7 = require("react/jsx-runtime");
937
+ function LatexDialog({ open, onSubmit, onCancel }) {
938
+ const [latex, setLatex] = (0, import_react8.useState)("");
939
+ (0, import_react8.useEffect)(() => {
940
+ if (open) setLatex("");
941
+ }, [open]);
942
+ if (!open) return null;
943
+ const submit = () => {
944
+ if (!latex.trim()) return;
945
+ onSubmit(latex.trim());
946
+ };
947
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "bke-dialog-overlay", onClick: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "bke-dialog", onClick: (e) => e.stopPropagation(), children: [
948
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "bke-dialog-title", children: "\u63D2\u5165\u516C\u5F0F" }),
949
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "bke-field", children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "LaTeX" }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
952
+ "textarea",
953
+ {
954
+ placeholder: "E = mc^2",
955
+ value: latex,
956
+ onChange: (e) => setLatex(e.target.value),
957
+ rows: 4,
958
+ autoFocus: true
959
+ }
960
+ )
961
+ ] }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "bke-dialog-actions", children: [
963
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: onCancel, children: "\u53D6\u6D88" }),
964
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: submit, disabled: !latex.trim(), children: "\u786E\u5B9A" })
965
+ ] })
966
+ ] }) });
967
+ }
968
+
969
+ // src/react/Toolbar.tsx
970
+ var import_jsx_runtime8 = require("react/jsx-runtime");
971
+ var ACTIVE_MAP = {
972
+ bold: (f) => f.bold,
973
+ italic: (f) => f.italic,
974
+ underline: (f) => f.underline,
975
+ strikeout: (f) => f.strikeout,
976
+ superscript: (f) => f.superscript,
977
+ subscript: (f) => f.subscript,
978
+ "align-left": (f) => f.align === "left",
979
+ "align-center": (f) => f.align === "center",
980
+ "align-right": (f) => f.align === "right",
981
+ "align-justify": (f) => f.align === "justify",
982
+ "list-ol": (f) => f.listType === "ol",
983
+ "list-ul": (f) => f.listType === "ul",
984
+ h1: (f) => f.level === 1,
985
+ h2: (f) => f.level === 2,
986
+ h3: (f) => f.level === 3,
987
+ h4: (f) => f.level === 4
988
+ };
989
+ var MORE_BUTTON_RESERVED_WIDTH = 48;
990
+ function Toolbar({
991
+ editorRef,
992
+ config,
993
+ formatState,
994
+ breakpoint,
995
+ renderers,
996
+ readOnly = false
997
+ }) {
998
+ const { execute } = useCommand(editorRef);
999
+ const [hyperlinkOpen, setHyperlinkOpen] = (0, import_react9.useState)(false);
1000
+ const [latexOpen, setLatexOpen] = (0, import_react9.useState)(false);
1001
+ const [colorOpen, setColorOpen] = (0, import_react9.useState)(false);
1002
+ const [highlightOpen, setHighlightOpen] = (0, import_react9.useState)(false);
1003
+ const [tableOpen, setTableOpen] = (0, import_react9.useState)(false);
1004
+ const [fontOpen, setFontOpen] = (0, import_react9.useState)(false);
1005
+ const [sizeOpen, setSizeOpen] = (0, import_react9.useState)(false);
1006
+ const [lineHeightOpen, setLineHeightOpen] = (0, import_react9.useState)(false);
1007
+ const [overflowIds, setOverflowIds] = (0, import_react9.useState)([]);
1008
+ const disabled = config === false || readOnly || breakpoint === "mobile";
1009
+ const groups = (0, import_react9.useMemo)(
1010
+ () => disabled ? [] : mergeToolbarConfig(config),
1011
+ [disabled, config]
1012
+ );
1013
+ const isTablet = breakpoint === "tablet";
1014
+ const toolbarRef = (0, import_react9.useRef)(null);
1015
+ const measureRef = (0, import_react9.useRef)(null);
1016
+ (0, import_react9.useLayoutEffect)(() => {
1017
+ if (disabled) {
1018
+ setOverflowIds([]);
1019
+ return;
1020
+ }
1021
+ const toolbar = toolbarRef.current;
1022
+ const measure = measureRef.current;
1023
+ if (!toolbar || !measure) return;
1024
+ const compute = () => {
1025
+ const widths = /* @__PURE__ */ new Map();
1026
+ measure.querySelectorAll("[data-group-id]").forEach((el) => {
1027
+ const id = el.dataset.groupId;
1028
+ if (id) widths.set(id, el.offsetWidth);
1029
+ });
1030
+ const available = toolbar.clientWidth - MORE_BUTTON_RESERVED_WIDTH;
1031
+ const result = [];
1032
+ let acc = 0;
1033
+ let crossed = false;
1034
+ for (const g of groups) {
1035
+ const w = widths.get(g.id) ?? 0;
1036
+ if (crossed) {
1037
+ result.push(g.id);
1038
+ continue;
1039
+ }
1040
+ acc += w;
1041
+ if (acc > available) {
1042
+ crossed = true;
1043
+ result.push(g.id);
1044
+ }
1045
+ }
1046
+ setOverflowIds((prev) => {
1047
+ const same = prev.length === result.length && prev.every((v, i) => v === result[i]);
1048
+ return same ? prev : result;
1049
+ });
1050
+ };
1051
+ compute();
1052
+ const ro = new ResizeObserver(compute);
1053
+ ro.observe(toolbar);
1054
+ return () => ro.disconnect();
1055
+ }, [disabled, groups, isTablet]);
1056
+ if (disabled) return null;
1057
+ const renderButtonItem = (id, keySuffix = "") => {
1058
+ const renderer = renderers?.button?.[id];
1059
+ const active = ACTIVE_MAP[id]?.(formatState) ?? false;
1060
+ const key = `${id}${keySuffix}`;
1061
+ if (id === "color" || id === "highlight") {
1062
+ const open = id === "color" ? colorOpen : highlightOpen;
1063
+ const setOpen = id === "color" ? setColorOpen : setHighlightOpen;
1064
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1065
+ Dropdown,
1066
+ {
1067
+ open,
1068
+ onOpenChange: setOpen,
1069
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolbarButton, { id, active, renderer, onClick: () => {
1070
+ } }),
1071
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ColorPicker, { onSelect: (c) => {
1072
+ execute(id, c);
1073
+ setOpen(false);
1074
+ } })
1075
+ },
1076
+ key
1077
+ );
1078
+ }
1079
+ if (id === "table") {
1080
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1081
+ Dropdown,
1082
+ {
1083
+ open: tableOpen,
1084
+ onOpenChange: setTableOpen,
1085
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolbarButton, { id, active, renderer, onClick: () => {
1086
+ } }),
1087
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1088
+ TableGridPicker,
1089
+ {
1090
+ onSelect: (rows, cols) => {
1091
+ execute("table", rows, cols);
1092
+ setTableOpen(false);
1093
+ }
1094
+ }
1095
+ )
1096
+ },
1097
+ key
1098
+ );
1099
+ }
1100
+ if (id === "font-family") {
1101
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1102
+ Dropdown,
1103
+ {
1104
+ open: fontOpen,
1105
+ onOpenChange: setFontOpen,
1106
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolbarButton, { id, label: "\u5B57\u4F53", renderer, onClick: () => {
1107
+ } }),
1108
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "bke-list-menu", children: ["\u5B8B\u4F53", "\u9ED1\u4F53", "\u5FAE\u8F6F\u96C5\u9ED1", "Arial", "Times New Roman"].map((name) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1109
+ "button",
1110
+ {
1111
+ type: "button",
1112
+ style: { fontFamily: name },
1113
+ onClick: () => {
1114
+ execute("font-family", name);
1115
+ setFontOpen(false);
1116
+ },
1117
+ children: name
1118
+ },
1119
+ name
1120
+ )) })
1121
+ },
1122
+ key
1123
+ );
1124
+ }
1125
+ if (id === "font-size") {
1126
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1127
+ Dropdown,
1128
+ {
1129
+ open: sizeOpen,
1130
+ onOpenChange: setSizeOpen,
1131
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolbarButton, { id, label: "\u5B57\u53F7", renderer, onClick: () => {
1132
+ } }),
1133
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "bke-list-menu", children: [12, 14, 16, 18, 24, 32].map((size) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1134
+ "button",
1135
+ {
1136
+ type: "button",
1137
+ onClick: () => {
1138
+ execute("font-size", size);
1139
+ setSizeOpen(false);
1140
+ },
1141
+ children: size
1142
+ },
1143
+ size
1144
+ )) })
1145
+ },
1146
+ key
1147
+ );
1148
+ }
1149
+ if (id === "line-height") {
1150
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1151
+ Dropdown,
1152
+ {
1153
+ open: lineHeightOpen,
1154
+ onOpenChange: setLineHeightOpen,
1155
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolbarButton, { id, label: "\u884C\u8DDD", renderer, onClick: () => {
1156
+ } }),
1157
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "bke-list-menu", children: [1, 1.15, 1.5, 2, 2.5].map((v) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1158
+ "button",
1159
+ {
1160
+ type: "button",
1161
+ onClick: () => {
1162
+ execute("line-height", v);
1163
+ setLineHeightOpen(false);
1164
+ },
1165
+ children: v
1166
+ },
1167
+ v
1168
+ )) })
1169
+ },
1170
+ key
1171
+ );
1172
+ }
1173
+ if (id === "image") {
1174
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1175
+ ToolbarButton,
1176
+ {
1177
+ id,
1178
+ active,
1179
+ renderer,
1180
+ onClick: () => {
1181
+ const input = document.createElement("input");
1182
+ input.type = "file";
1183
+ input.accept = "image/*";
1184
+ input.onchange = () => {
1185
+ const file = input.files?.[0];
1186
+ if (!file) return;
1187
+ const reader = new FileReader();
1188
+ reader.onload = () => {
1189
+ execute("image", { value: reader.result, width: 200, height: 150 });
1190
+ };
1191
+ reader.readAsDataURL(file);
1192
+ };
1193
+ input.click();
1194
+ }
1195
+ },
1196
+ key
1197
+ );
1198
+ }
1199
+ if (id === "hyperlink") {
1200
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1201
+ ToolbarButton,
1202
+ {
1203
+ id,
1204
+ renderer,
1205
+ onClick: () => setHyperlinkOpen(true)
1206
+ },
1207
+ key
1208
+ );
1209
+ }
1210
+ if (id === "math") {
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1212
+ ToolbarButton,
1213
+ {
1214
+ id,
1215
+ renderer,
1216
+ onClick: () => setLatexOpen(true)
1217
+ },
1218
+ key
1219
+ );
1220
+ }
1221
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1222
+ ToolbarButton,
1223
+ {
1224
+ id,
1225
+ active,
1226
+ renderer,
1227
+ onClick: () => execute(id)
1228
+ },
1229
+ key
1230
+ );
1231
+ };
1232
+ const renderGroup = (group, keySuffix = "") => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1233
+ "div",
1234
+ {
1235
+ className: "bke-toolbar-group",
1236
+ "data-group-id": group.id,
1237
+ children: group.buttons.map(
1238
+ (b, i) => b === "|" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "bke-toolbar-divider" }, `d-${i}`) : renderButtonItem(b, keySuffix)
1239
+ )
1240
+ },
1241
+ `${group.id}${keySuffix}`
1242
+ );
1243
+ const forcedOverflow = isTablet ? TABLET_COLLAPSE_GROUP_IDS : [];
1244
+ const finalOverflow = /* @__PURE__ */ new Set([...overflowIds, ...forcedOverflow]);
1245
+ const visibleGroups = groups.filter((g) => !finalOverflow.has(g.id));
1246
+ const hiddenGroups = groups.filter((g) => finalOverflow.has(g.id));
1247
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1248
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "bke-toolbar bke-toolbar-main", ref: toolbarRef, children: [
1249
+ visibleGroups.map((g) => renderGroup(g)),
1250
+ hiddenGroups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1251
+ Dropdown,
1252
+ {
1253
+ align: "right",
1254
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1255
+ "button",
1256
+ {
1257
+ type: "button",
1258
+ className: "bke-btn bke-more-btn",
1259
+ title: "\u66F4\u591A",
1260
+ "aria-label": "\u66F4\u591A",
1261
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "5", cy: "12", r: "1.6" }),
1263
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "1.6" }),
1264
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "19", cy: "12", r: "1.6" })
1265
+ ] })
1266
+ }
1267
+ ),
1268
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "bke-toolbar-overflow", children: hiddenGroups.map((g) => renderGroup(g, "-overflow")) })
1269
+ },
1270
+ "more"
1271
+ )
1272
+ ] }),
1273
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "bke-toolbar-measure", ref: measureRef, "aria-hidden": "true", children: groups.map((g) => renderGroup(g, "-measure")) }),
1274
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1275
+ HyperlinkDialog,
1276
+ {
1277
+ open: hyperlinkOpen,
1278
+ onSubmit: ({ url, text }) => {
1279
+ execute("hyperlink", { url, valueList: text ? [{ value: text }] : void 0 });
1280
+ setHyperlinkOpen(false);
1281
+ },
1282
+ onCancel: () => setHyperlinkOpen(false)
1283
+ }
1284
+ ),
1285
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1286
+ LatexDialog,
1287
+ {
1288
+ open: latexOpen,
1289
+ onSubmit: (latex) => {
1290
+ execute("math", [{ type: "latex", value: latex }]);
1291
+ setLatexOpen(false);
1292
+ },
1293
+ onCancel: () => setLatexOpen(false)
1294
+ }
1295
+ )
1296
+ ] });
1297
+ }
1298
+
1299
+ // src/react/FieldPanel.tsx
1300
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1301
+ function FieldPanel({ visible }) {
1302
+ if (!visible) return null;
1303
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("aside", { className: "bke-field-panel", children: [
1304
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { children: "\u5B57\u6BB5" }),
1305
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 12, opacity: 0.6 }, children: "\u5B57\u6BB5\u9762\u677F\u5C06\u5728 M3 \u5B9E\u73B0" })
1306
+ ] });
1307
+ }
1308
+
1309
+ // src/react/BidEditor.tsx
1310
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1311
+ var BidEditor2 = (0, import_react10.forwardRef)(function BidEditor3(props, ref) {
1312
+ const {
1313
+ bid,
1314
+ onChange,
1315
+ readOnly = false,
1316
+ toolbar = true,
1317
+ theme,
1318
+ renderers,
1319
+ fieldPanel = false,
1320
+ breakpoint: forcedBreakpoint,
1321
+ className,
1322
+ style
1323
+ } = props;
1324
+ const containerRef = (0, import_react10.useRef)(null);
1325
+ const { handle, editorRef } = useBidEditor({ containerRef, bid, onChange, readOnly });
1326
+ const formatState = useFormatState(editorRef);
1327
+ const breakpoint = useBreakpoint(forcedBreakpoint);
1328
+ (0, import_react10.useImperativeHandle)(ref, () => handle, [handle]);
1329
+ const mergedTheme = mergeTheme(theme);
1330
+ const cssVars = themeToCssVars(mergedTheme);
1331
+ const rootClass = ["bke-root", mergedTheme.dark ? "bke-dark" : "", className ?? ""].filter(Boolean).join(" ");
1332
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: rootClass, style: { ...cssVars, ...style }, children: [
1333
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1334
+ Toolbar,
1335
+ {
1336
+ editorRef,
1337
+ config: toolbar,
1338
+ formatState,
1339
+ breakpoint,
1340
+ renderers,
1341
+ readOnly
1342
+ }
1343
+ ),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "bke-editor-host", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "bke-editor-canvas", ref: containerRef }) }),
1345
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldPanel, { visible: fieldPanel })
1346
+ ] });
1347
+ });
1348
+ // Annotate the CommonJS export names for ESM import in node:
1349
+ 0 && (module.exports = {
1350
+ BidEditor,
1351
+ BidEditorCore,
1352
+ bidSchema,
1353
+ bidToEditorElements,
1354
+ editorElementsToSections,
1355
+ printBid,
1356
+ validateBid
1357
+ });
1358
+ //# sourceMappingURL=index.cjs.map