@lumir-company/editor 0.4.13 → 0.4.14

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.mjs CHANGED
@@ -3231,6 +3231,235 @@ function patchBlocks(blocks, tableVAMap) {
3231
3231
  });
3232
3232
  }
3233
3233
 
3234
+ // src/utils/excel-paste.ts
3235
+ var NAMED_COLORS = {
3236
+ black: "#000000",
3237
+ white: "#ffffff",
3238
+ red: "#ff0000",
3239
+ green: "#008000",
3240
+ blue: "#0000ff",
3241
+ yellow: "#ffff00",
3242
+ orange: "#ffa500",
3243
+ purple: "#800080",
3244
+ gray: "#808080",
3245
+ grey: "#808080"
3246
+ };
3247
+ function parseCssColorToRgb(input) {
3248
+ if (!input) return null;
3249
+ const s = input.trim().toLowerCase();
3250
+ if (!s || s === "transparent" || s === "none" || s === "inherit") return null;
3251
+ let m = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
3252
+ if (m) {
3253
+ let h = m[1];
3254
+ if (h.length === 3)
3255
+ h = h.split("").map((c) => c + c).join("");
3256
+ return [
3257
+ parseInt(h.slice(0, 2), 16),
3258
+ parseInt(h.slice(2, 4), 16),
3259
+ parseInt(h.slice(4, 6), 16)
3260
+ ];
3261
+ }
3262
+ m = s.match(/^rgba?\(([^)]+)\)$/);
3263
+ if (m) {
3264
+ const parts = m[1].split(",").map((x) => parseFloat(x.trim()));
3265
+ if (parts.length >= 3 && parts.slice(0, 3).every((n) => !isNaN(n))) {
3266
+ return [parts[0], parts[1], parts[2]];
3267
+ }
3268
+ return null;
3269
+ }
3270
+ if (NAMED_COLORS[s]) return parseCssColorToRgb(NAMED_COLORS[s]);
3271
+ return null;
3272
+ }
3273
+ function rgbToHsl([r, g, b]) {
3274
+ r /= 255;
3275
+ g /= 255;
3276
+ b /= 255;
3277
+ const max = Math.max(r, g, b);
3278
+ const min = Math.min(r, g, b);
3279
+ const l = (max + min) / 2;
3280
+ let h = 0;
3281
+ let s = 0;
3282
+ const d = max - min;
3283
+ if (d !== 0) {
3284
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3285
+ switch (max) {
3286
+ case r:
3287
+ h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
3288
+ break;
3289
+ case g:
3290
+ h = ((b - r) / d + 2) * 60;
3291
+ break;
3292
+ default:
3293
+ h = ((r - g) / d + 4) * 60;
3294
+ break;
3295
+ }
3296
+ }
3297
+ return [h, s, l];
3298
+ }
3299
+ var HUE_REFERENCE = [
3300
+ { value: "red", hue: 0 },
3301
+ { value: "brown", hue: 17 },
3302
+ { value: "orange", hue: 30 },
3303
+ { value: "yellow", hue: 46 },
3304
+ { value: "green", hue: 150 },
3305
+ { value: "blue", hue: 197 },
3306
+ { value: "purple", hue: 262 },
3307
+ { value: "pink", hue: 324 }
3308
+ ];
3309
+ function hueDist(a, b) {
3310
+ const d = Math.abs(a - b) % 360;
3311
+ return d > 180 ? 360 - d : d;
3312
+ }
3313
+ function paletteValueFromRgb(rgb) {
3314
+ const [h, s, l] = rgbToHsl(rgb);
3315
+ if (s < 0.15) {
3316
+ if (l < 0.35) return "default";
3317
+ if (l > 0.85) return "default";
3318
+ return "gray";
3319
+ }
3320
+ let best = "gray";
3321
+ let bestDist = Infinity;
3322
+ for (const ref of HUE_REFERENCE) {
3323
+ const d = hueDist(h, ref.hue);
3324
+ if (d < bestDist) {
3325
+ bestDist = d;
3326
+ best = ref.value;
3327
+ }
3328
+ }
3329
+ return best;
3330
+ }
3331
+ function nearestTextColorValue(rgb) {
3332
+ return paletteValueFromRgb(rgb);
3333
+ }
3334
+ function nearestBackgroundColorValue(rgb) {
3335
+ const [, s, l] = rgbToHsl(rgb);
3336
+ if (s < 0.12 && l > 0.85) return "default";
3337
+ return paletteValueFromRgb(rgb);
3338
+ }
3339
+ function parseStyle(style) {
3340
+ const out = {};
3341
+ style.split(";").forEach((decl) => {
3342
+ const idx = decl.indexOf(":");
3343
+ if (idx === -1) return;
3344
+ const k = decl.slice(0, idx).trim().toLowerCase();
3345
+ const v = decl.slice(idx + 1).trim();
3346
+ if (k) out[k] = v;
3347
+ });
3348
+ return out;
3349
+ }
3350
+ function colorFromBackgroundShorthand(value) {
3351
+ if (!value) return null;
3352
+ const direct = parseCssColorToRgb(value);
3353
+ if (direct) return direct;
3354
+ for (const token of value.split(/\s+/)) {
3355
+ const rgb = parseCssColorToRgb(token);
3356
+ if (rgb) return rgb;
3357
+ }
3358
+ return null;
3359
+ }
3360
+ function isTransparentColor(css) {
3361
+ if (!css) return true;
3362
+ const s = css.trim().toLowerCase();
3363
+ if (s === "transparent" || s === "none") return true;
3364
+ const m = s.match(/^rgba?\(([^)]+)\)$/);
3365
+ if (m) {
3366
+ const p = m[1].split(",").map((x) => parseFloat(x.trim()));
3367
+ if (p.length >= 4 && p[3] === 0) return true;
3368
+ }
3369
+ return false;
3370
+ }
3371
+ function normalizeAlign(ta) {
3372
+ const v = (ta || "").trim().toLowerCase();
3373
+ if (v === "right" || v === "end") return "right";
3374
+ if (v === "center") return "center";
3375
+ if (v === "justify") return "justify";
3376
+ return "";
3377
+ }
3378
+ function applyCellFormatting(el, fmt) {
3379
+ if (fmt.bgRgb && !fmt.bgTransparent) {
3380
+ const v = nearestBackgroundColorValue(fmt.bgRgb);
3381
+ if (v !== "default") el.setAttribute("data-background-color", v);
3382
+ }
3383
+ if (fmt.colorRgb) {
3384
+ const v = nearestTextColorValue(fmt.colorRgb);
3385
+ if (v !== "default") el.setAttribute("data-text-color", v);
3386
+ }
3387
+ if (["right", "center", "justify"].includes(fmt.align)) {
3388
+ el.setAttribute("data-text-alignment", fmt.align);
3389
+ }
3390
+ if ((fmt.bold || fmt.italic || fmt.underline) && el.innerHTML.trim()) {
3391
+ let inner = el.innerHTML;
3392
+ if (fmt.underline) inner = `<u>${inner}</u>`;
3393
+ if (fmt.italic) inner = `<em>${inner}</em>`;
3394
+ if (fmt.bold) inner = `<strong>${inner}</strong>`;
3395
+ el.innerHTML = inner;
3396
+ }
3397
+ }
3398
+ function readComputedFormat(el) {
3399
+ const cs = getComputedStyle(el);
3400
+ const fw = cs.fontWeight;
3401
+ const fwNum = parseInt(fw, 10);
3402
+ const decoration = cs.textDecorationLine || cs.textDecoration || "";
3403
+ return {
3404
+ bgRgb: parseCssColorToRgb(cs.backgroundColor),
3405
+ bgTransparent: isTransparentColor(cs.backgroundColor),
3406
+ colorRgb: parseCssColorToRgb(cs.color),
3407
+ align: normalizeAlign(cs.textAlign),
3408
+ bold: fw === "bold" || fw === "bolder" || !isNaN(fwNum) && fwNum >= 600,
3409
+ italic: (cs.fontStyle || "").toLowerCase().includes("italic"),
3410
+ underline: decoration.toLowerCase().includes("underline")
3411
+ };
3412
+ }
3413
+ function readInlineFormat(el) {
3414
+ const sm = parseStyle(el.getAttribute("style") || "");
3415
+ const bgRaw = sm["background-color"] || sm["background"] || "";
3416
+ const bgRgb = colorFromBackgroundShorthand(bgRaw) || parseCssColorToRgb(el.getAttribute("bgcolor"));
3417
+ const fw = (sm["font-weight"] || "").toLowerCase();
3418
+ const decoration = sm["text-decoration"] || sm["text-decoration-line"] || "";
3419
+ return {
3420
+ bgRgb,
3421
+ bgTransparent: !bgRaw && !el.getAttribute("bgcolor"),
3422
+ colorRgb: parseCssColorToRgb(sm["color"]),
3423
+ align: normalizeAlign(sm["text-align"] || el.getAttribute("align")),
3424
+ bold: fw === "bold" || fw === "bolder" || parseInt(fw, 10) >= 600,
3425
+ italic: (sm["font-style"] || "").toLowerCase().includes("italic"),
3426
+ underline: decoration.toLowerCase().includes("underline")
3427
+ };
3428
+ }
3429
+ function normalizeExcelTableHtml(html) {
3430
+ if (!html || typeof DOMParser === "undefined") return html;
3431
+ try {
3432
+ const doc = new DOMParser().parseFromString(html, "text/html");
3433
+ doc.querySelectorAll("script").forEach((s) => s.remove());
3434
+ if (!doc.querySelector("table")) return html;
3435
+ if (typeof document !== "undefined" && document.body && typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.attachShadow === "function") {
3436
+ let host = null;
3437
+ try {
3438
+ host = document.createElement("div");
3439
+ host.setAttribute("aria-hidden", "true");
3440
+ host.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none";
3441
+ const shadow = host.attachShadow({ mode: "open" });
3442
+ const styles = Array.from(doc.querySelectorAll("style")).map((s) => s.outerHTML).join("");
3443
+ shadow.innerHTML = styles + doc.body.innerHTML;
3444
+ document.body.appendChild(host);
3445
+ shadow.querySelectorAll("td, th").forEach((node) => {
3446
+ applyCellFormatting(node, readComputedFormat(node));
3447
+ });
3448
+ const out = Array.from(shadow.querySelectorAll("table")).map((t) => t.outerHTML).join("");
3449
+ return out || html;
3450
+ } finally {
3451
+ if (host && host.parentNode) host.parentNode.removeChild(host);
3452
+ }
3453
+ }
3454
+ doc.querySelectorAll("td, th").forEach((node) => {
3455
+ applyCellFormatting(node, readInlineFormat(node));
3456
+ });
3457
+ return Array.from(doc.querySelectorAll("table")).map((t) => t.outerHTML).join("") || html;
3458
+ } catch {
3459
+ return html;
3460
+ }
3461
+ }
3462
+
3234
3463
  // src/constants/limits.ts
3235
3464
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
3236
3465
  var MAX_VIDEO_FILE_SIZE = 100 * 1024 * 1024;
@@ -3749,6 +3978,16 @@ function LumirEditor({
3749
3978
  return true;
3750
3979
  }
3751
3980
  }
3981
+ const pastedHtml = event?.clipboardData?.getData?.("text/html") || "";
3982
+ if (/<table[\s>]/i.test(pastedHtml)) {
3983
+ DEBUG_LOG("paste:step0:table", "table HTML detected, using pasteHTML", {
3984
+ htmlLen: pastedHtml.length,
3985
+ hasFiles: !!event?.clipboardData?.files?.length
3986
+ });
3987
+ event.preventDefault();
3988
+ editor2.pasteHTML(normalizeExcelTableHtml(pastedHtml));
3989
+ return true;
3990
+ }
3752
3991
  const fileList = event?.clipboardData?.files ?? null;
3753
3992
  const files = fileList ? Array.from(fileList) : [];
3754
3993
  const acceptedFiles = files.filter(