@visactor/vtable-plugins 1.26.1-alpha.0 → 1.26.2-alpha.1

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.
@@ -1214,11 +1214,6 @@
1214
1214
  }
1215
1215
  }
1216
1216
 
1217
- const MENU_CONTAINER_CLASS = 'vtable-context-menu-container';
1218
- const MENU_ITEM_CLASS = 'vtable-context-menu-item';
1219
- const MENU_ITEM_DISABLED_CLASS = 'vtable-context-menu-item-disabled';
1220
- const MENU_ITEM_SEPARATOR_CLASS = 'vtable-context-menu-item-separator';
1221
- const MENU_ITEM_SUBMENU_CLASS = 'vtable-context-menu-item-submenu';
1222
1217
  const MENU_STYLES = {
1223
1218
  menuContainer: {
1224
1219
  position: 'absolute',
@@ -1226,7 +1221,7 @@
1226
1221
  boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
1227
1222
  borderRadius: '4px',
1228
1223
  padding: '5px 0',
1229
- zIndex: 1000,
1224
+ zIndex: '1000',
1230
1225
  minWidth: '180px',
1231
1226
  maxHeight: '300px',
1232
1227
  overflowY: 'auto',
@@ -1245,7 +1240,7 @@
1245
1240
  backgroundColor: '#f5f5f5'
1246
1241
  },
1247
1242
  menuItemDisabled: {
1248
- opacity: 0.5,
1243
+ opacity: '0.5',
1249
1244
  cursor: 'not-allowed'
1250
1245
  },
1251
1246
  menuItemSeparator: {
@@ -1262,7 +1257,7 @@
1262
1257
  justifyContent: 'center'
1263
1258
  },
1264
1259
  menuItemText: {
1265
- flex: 1
1260
+ flex: '1'
1266
1261
  },
1267
1262
  menuItemShortcut: {
1268
1263
  marginLeft: '20px',
@@ -1282,7 +1277,7 @@
1282
1277
  boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
1283
1278
  borderRadius: '4px',
1284
1279
  padding: '5px 0',
1285
- zIndex: 1001,
1280
+ zIndex: '1001',
1286
1281
  minWidth: '180px',
1287
1282
  fontSize: '12px'
1288
1283
  },
@@ -1316,6 +1311,99 @@
1316
1311
  fontSize: '12px'
1317
1312
  }
1318
1313
  };
1314
+ const MENU_CLASSES = {
1315
+ menuContainer: 'vtable-context-menu-container',
1316
+ submenuContainer: 'vtable-context-submenu-container',
1317
+ menuItem: 'vtable-context-menu-item',
1318
+ menuItemSeparator: 'vtable-context-menu-item-separator',
1319
+ menuItemSubmenu: 'vtable-context-menu-item-submenu',
1320
+ menuItemDisabled: 'vtable-context-menu-item-disabled'
1321
+ };
1322
+ const DANGEROUS_SVG_ELEMENTS = new Set([
1323
+ 'script',
1324
+ 'iframe',
1325
+ 'object',
1326
+ 'embed',
1327
+ 'form',
1328
+ 'input',
1329
+ 'textarea',
1330
+ 'select',
1331
+ 'button'
1332
+ ]);
1333
+ const DANGEROUS_ATTR_PREFIXES = ['on'];
1334
+ const DANGEROUS_URL_ATTRS = new Set(['href', 'xlink:href']);
1335
+ function sanitizeSvg(svgString) {
1336
+ const parser = new DOMParser();
1337
+ const doc = parser.parseFromString(svgString, 'image/svg+xml');
1338
+ const errorNode = doc.querySelector('parsererror');
1339
+ if (errorNode) {
1340
+ return '';
1341
+ }
1342
+ const svg = doc.documentElement;
1343
+ if (svg.tagName.toLowerCase() !== 'svg') {
1344
+ return '';
1345
+ }
1346
+ sanitizeNode(svg);
1347
+ const serializer = new XMLSerializer();
1348
+ return serializer.serializeToString(svg);
1349
+ }
1350
+ function sanitizeNode(node) {
1351
+ const children = Array.from(node.children);
1352
+ for (const child of children) {
1353
+ if (DANGEROUS_SVG_ELEMENTS.has(child.tagName.toLowerCase())) {
1354
+ node.removeChild(child);
1355
+ }
1356
+ else {
1357
+ sanitizeNode(child);
1358
+ }
1359
+ }
1360
+ const attrs = Array.from(node.attributes);
1361
+ for (const attr of attrs) {
1362
+ const name = attr.name.toLowerCase();
1363
+ if (DANGEROUS_ATTR_PREFIXES.some(prefix => name.startsWith(prefix))) {
1364
+ node.removeAttribute(attr.name);
1365
+ continue;
1366
+ }
1367
+ if (DANGEROUS_URL_ATTRS.has(name)) {
1368
+ const value = attr.value.trim().toLowerCase();
1369
+ if (value.startsWith('javascript:') || value.startsWith('data:')) {
1370
+ node.removeAttribute(attr.name);
1371
+ }
1372
+ }
1373
+ }
1374
+ }
1375
+ function mergeStyles(styles) {
1376
+ if (!styles) {
1377
+ return { ...MENU_STYLES };
1378
+ }
1379
+ const result = {};
1380
+ for (const _key in MENU_STYLES) {
1381
+ const key = _key;
1382
+ if (styles[key]) {
1383
+ result[key] = { ...MENU_STYLES[key], ...styles[key] };
1384
+ }
1385
+ else {
1386
+ result[key] = { ...MENU_STYLES[key] };
1387
+ }
1388
+ }
1389
+ return result;
1390
+ }
1391
+ function mergeClasses(classes) {
1392
+ if (!classes) {
1393
+ return { ...MENU_CLASSES };
1394
+ }
1395
+ const result = {};
1396
+ for (const _key in MENU_CLASSES) {
1397
+ const key = _key;
1398
+ if (classes[key]) {
1399
+ result[key] = `${MENU_CLASSES[key]} ${normalizeClassName(classes[key]).join(' ')}`;
1400
+ }
1401
+ else {
1402
+ result[key] = MENU_CLASSES[key];
1403
+ }
1404
+ }
1405
+ return result;
1406
+ }
1319
1407
  function createElement$1(tag, className, styles) {
1320
1408
  const element = document.createElement(tag);
1321
1409
  if (className) {
@@ -1327,11 +1415,39 @@
1327
1415
  return element;
1328
1416
  }
1329
1417
  function applyStyles$1(element, styles) {
1330
- Object.entries(styles).forEach(([key, value]) => {
1331
- element.style[key] = value;
1332
- });
1333
- }
1334
- function createIcon(iconName) {
1418
+ for (const key in styles) {
1419
+ const value = styles[key];
1420
+ if (value !== undefined) {
1421
+ element.style[key] = value;
1422
+ }
1423
+ }
1424
+ }
1425
+ function createIcon(icon, menuItem, iconStyles = MENU_STYLES.menuItemIcon) {
1426
+ if (typeof icon === 'function') {
1427
+ const element = icon(menuItem);
1428
+ applyStyles$1(element, iconStyles);
1429
+ return element;
1430
+ }
1431
+ if (typeof icon === 'object' && 'svg' in icon) {
1432
+ const svgConfig = icon;
1433
+ const iconElement = createElement$1('span');
1434
+ const safeSvg = sanitizeSvg(svgConfig.svg);
1435
+ if (safeSvg) {
1436
+ iconElement.innerHTML = safeSvg;
1437
+ const svgEl = iconElement.querySelector('svg');
1438
+ if (svgEl) {
1439
+ if (svgConfig.width !== undefined) {
1440
+ svgEl.setAttribute('width', String(svgConfig.width));
1441
+ }
1442
+ if (svgConfig.height !== undefined) {
1443
+ svgEl.setAttribute('height', String(svgConfig.height));
1444
+ }
1445
+ }
1446
+ }
1447
+ applyStyles$1(iconElement, iconStyles);
1448
+ return iconElement;
1449
+ }
1450
+ const iconName = icon;
1335
1451
  const iconElement = createElement$1('span');
1336
1452
  switch (iconName) {
1337
1453
  case 'copy':
@@ -1376,30 +1492,48 @@
1376
1492
  default:
1377
1493
  iconElement.innerHTML = '•';
1378
1494
  }
1379
- applyStyles$1(iconElement, MENU_STYLES.menuItemIcon);
1495
+ applyStyles$1(iconElement, iconStyles);
1380
1496
  return iconElement;
1381
1497
  }
1382
- function createNumberInputItem(label, defaultValue = 1, iconName, callback) {
1498
+ function createNumberInputItem(label, defaultValue = 1, icon, callback, styles = MENU_STYLES) {
1383
1499
  const container = createElement$1('div');
1384
- applyStyles$1(container, MENU_STYLES.inputContainer);
1385
- if (iconName) {
1386
- const icon = createIcon(iconName);
1387
- container.appendChild(icon);
1500
+ applyStyles$1(container, styles.inputContainer);
1501
+ if (icon) {
1502
+ const iconEl = createIcon(icon, undefined, styles.menuItemIcon);
1503
+ container.appendChild(iconEl);
1388
1504
  }
1389
1505
  const labelElement = createElement$1('label');
1390
1506
  labelElement.textContent = label;
1391
- applyStyles$1(labelElement, MENU_STYLES.inputLabel);
1507
+ applyStyles$1(labelElement, styles.inputLabel);
1392
1508
  container.appendChild(labelElement);
1393
1509
  const input = createElement$1('input');
1394
1510
  input.type = 'number';
1395
1511
  input.min = '1';
1396
1512
  input.value = defaultValue.toString();
1397
- applyStyles$1(input, MENU_STYLES.inputField);
1513
+ applyStyles$1(input, styles.inputField);
1398
1514
  container.appendChild(input);
1399
1515
  const wrapper = createElement$1('div');
1400
1516
  wrapper.appendChild(container);
1401
1517
  return wrapper;
1402
1518
  }
1519
+ function normalizeItemClassNameConfig(classNames) {
1520
+ const normalized = {};
1521
+ if (classNames) {
1522
+ Object.keys(classNames).forEach(item => {
1523
+ const className = classNames[item];
1524
+ if (className) {
1525
+ normalized[item] = normalizeClassName(className);
1526
+ }
1527
+ });
1528
+ }
1529
+ return normalized;
1530
+ }
1531
+ function normalizeClassName(className) {
1532
+ if (!className) {
1533
+ return [];
1534
+ }
1535
+ return isArray$6(className) ? className : className.split(' ').filter(Boolean);
1536
+ }
1403
1537
 
1404
1538
  class MenuManager {
1405
1539
  menuContainer = null;
@@ -1412,12 +1546,19 @@
1412
1546
  submenuShowDelay = 100;
1413
1547
  submenuHideDelay = 500;
1414
1548
  menuInitializationDelay = 200;
1549
+ styles;
1550
+ classes;
1551
+ constructor(styles, classes) {
1552
+ this.styles = styles ?? MENU_STYLES;
1553
+ this.classes = classes ?? MENU_CLASSES;
1554
+ }
1415
1555
  showMenu(menuItems, x, y, context, table) {
1416
1556
  this.context = context;
1417
1557
  this.table = table;
1418
1558
  this.release();
1419
- this.menuContainer = createElement$1('div', MENU_CONTAINER_CLASS);
1420
- applyStyles$1(this.menuContainer, MENU_STYLES.menuContainer);
1559
+ this.menuContainer = createElement$1('div');
1560
+ this.menuContainer.classList.add(...normalizeClassName(this.classes.menuContainer));
1561
+ applyStyles$1(this.menuContainer, this.styles.menuContainer);
1421
1562
  document.body.appendChild(this.menuContainer);
1422
1563
  this.menuContainer.addEventListener('contextmenu', (e) => {
1423
1564
  e.preventDefault();
@@ -1440,36 +1581,55 @@
1440
1581
  createMenuItems(items, container, parentItem) {
1441
1582
  items.forEach(item => {
1442
1583
  if (typeof item === 'string' && item === '---') {
1443
- const separator = createElement$1('div', MENU_ITEM_SEPARATOR_CLASS);
1444
- applyStyles$1(separator, MENU_STYLES.menuItemSeparator);
1584
+ const separator = createElement$1('div');
1585
+ separator.classList.add(...normalizeClassName(this.classes.menuItemSeparator));
1586
+ applyStyles$1(separator, this.styles.menuItemSeparator);
1445
1587
  container.appendChild(separator);
1446
1588
  }
1447
1589
  else if (typeof item === 'object') {
1448
1590
  const menuItem = item;
1449
- const menuItemElement = createElement$1('div', MENU_ITEM_CLASS);
1450
- applyStyles$1(menuItemElement, MENU_STYLES.menuItem);
1591
+ const customClassName = normalizeItemClassNameConfig(item.customClassName);
1592
+ const menuItemElement = createElement$1('div');
1593
+ menuItemElement.classList.add(...normalizeClassName(this.classes.menuItem));
1594
+ if (customClassName.item) {
1595
+ menuItemElement.classList.add(...customClassName.item);
1596
+ }
1597
+ applyStyles$1(menuItemElement, this.styles.menuItem);
1451
1598
  const leftContainer = createElement$1('div');
1599
+ if (customClassName.leftContainer) {
1600
+ leftContainer.classList.add(...customClassName.leftContainer);
1601
+ }
1452
1602
  leftContainer.style.display = 'flex';
1453
1603
  leftContainer.style.alignItems = 'center';
1454
- if (menuItem.iconName) {
1455
- const icon = createIcon(menuItem.iconName);
1604
+ const iconValue = menuItem.customIcon ?? menuItem.iconName;
1605
+ if (iconValue) {
1606
+ const icon = createIcon(iconValue, menuItem, this.styles.menuItemIcon);
1607
+ if (customClassName.icon) {
1608
+ icon.classList.add(...customClassName.icon);
1609
+ }
1456
1610
  leftContainer.appendChild(icon);
1457
1611
  }
1458
1612
  else if (menuItem.iconPlaceholder) {
1459
1613
  const placeholder = createElement$1('span');
1460
- applyStyles$1(placeholder, MENU_STYLES.menuItemIcon);
1614
+ applyStyles$1(placeholder, this.styles.menuItemIcon);
1461
1615
  leftContainer.appendChild(placeholder);
1462
1616
  }
1463
1617
  const text = createElement$1('span');
1618
+ if (customClassName.text) {
1619
+ text.classList.add(...customClassName.text);
1620
+ }
1464
1621
  text.textContent = menuItem.text;
1465
- applyStyles$1(text, MENU_STYLES.menuItemText);
1622
+ applyStyles$1(text, this.styles.menuItemText);
1466
1623
  leftContainer.appendChild(text);
1467
1624
  if (item.inputDefaultValue) {
1468
1625
  const input = createElement$1('input');
1626
+ if (customClassName.input) {
1627
+ input.classList.add(...customClassName.input);
1628
+ }
1469
1629
  input.type = 'number';
1470
1630
  input.min = '1';
1471
1631
  input.value = item.inputDefaultValue.toString();
1472
- applyStyles$1(input, MENU_STYLES.inputField);
1632
+ applyStyles$1(input, this.styles.inputField);
1473
1633
  leftContainer.appendChild(input);
1474
1634
  input.addEventListener('keydown', (e) => {
1475
1635
  if (e.key === 'Enter') {
@@ -1484,25 +1644,37 @@
1484
1644
  }
1485
1645
  menuItemElement.appendChild(leftContainer);
1486
1646
  const rightContainer = createElement$1('div');
1647
+ if (customClassName.rightContainer) {
1648
+ rightContainer.classList.add(...customClassName.rightContainer);
1649
+ }
1487
1650
  rightContainer.style.display = 'flex';
1488
1651
  rightContainer.style.alignItems = 'center';
1489
1652
  if (menuItem.shortcut) {
1490
1653
  const shortcut = createElement$1('span');
1654
+ if (customClassName.shortcut) {
1655
+ shortcut.classList.add(...customClassName.shortcut);
1656
+ }
1491
1657
  shortcut.textContent = menuItem.shortcut;
1492
- applyStyles$1(shortcut, MENU_STYLES.menuItemShortcut);
1658
+ applyStyles$1(shortcut, this.styles.menuItemShortcut);
1493
1659
  rightContainer.appendChild(shortcut);
1494
1660
  }
1495
1661
  if (menuItem.children && menuItem.children.length > 0) {
1496
- menuItemElement.classList.add(MENU_ITEM_SUBMENU_CLASS);
1662
+ menuItemElement.classList.add(...normalizeClassName(this.classes.menuItemSubmenu));
1497
1663
  const arrow = createElement$1('span');
1664
+ if (customClassName.arrow) {
1665
+ arrow.classList.add(...customClassName.arrow);
1666
+ }
1498
1667
  arrow.textContent = '▶';
1499
- applyStyles$1(arrow, MENU_STYLES.submenuArrow);
1668
+ applyStyles$1(arrow, this.styles.submenuArrow);
1500
1669
  rightContainer.appendChild(arrow);
1501
1670
  }
1502
1671
  menuItemElement.appendChild(rightContainer);
1503
1672
  if (menuItem.disabled) {
1504
- menuItemElement.classList.add(MENU_ITEM_DISABLED_CLASS);
1505
- applyStyles$1(menuItemElement, MENU_STYLES.menuItemDisabled);
1673
+ menuItemElement.classList.add(...normalizeClassName(this.classes.menuItemDisabled));
1674
+ if (customClassName.itemDisabled) {
1675
+ menuItemElement.classList.add(...customClassName.itemDisabled);
1676
+ }
1677
+ applyStyles$1(menuItemElement, this.styles.menuItemDisabled);
1506
1678
  }
1507
1679
  if (!menuItem.disabled) {
1508
1680
  if (!menuItem.children || menuItem.children.length === 0) {
@@ -1518,7 +1690,7 @@
1518
1690
  });
1519
1691
  }
1520
1692
  menuItemElement.addEventListener('mouseenter', () => {
1521
- applyStyles$1(menuItemElement, MENU_STYLES.menuItemHover);
1693
+ applyStyles$1(menuItemElement, this.styles.menuItemHover);
1522
1694
  if (this.hideTimeout !== null) {
1523
1695
  clearTimeout(this.hideTimeout);
1524
1696
  this.hideTimeout = null;
@@ -1540,7 +1712,7 @@
1540
1712
  }
1541
1713
  });
1542
1714
  menuItemElement.addEventListener('mouseleave', () => {
1543
- Object.keys(MENU_STYLES.menuItemHover).forEach(key => {
1715
+ Object.keys(this.styles.menuItemHover).forEach(key => {
1544
1716
  menuItemElement.style[key] = '';
1545
1717
  });
1546
1718
  if (menuItem.children && menuItem.children.length > 0) {
@@ -1556,8 +1728,9 @@
1556
1728
  }
1557
1729
  showSubmenu(items, parentElement, parentItem) {
1558
1730
  const parentRect = parentElement.getBoundingClientRect();
1559
- const submenu = createElement$1('div', MENU_CONTAINER_CLASS);
1560
- applyStyles$1(submenu, MENU_STYLES.submenuContainer);
1731
+ const submenu = createElement$1('div');
1732
+ submenu.classList.add(...normalizeClassName(this.classes.menuContainer), ...normalizeClassName(this.classes.submenuContainer));
1733
+ applyStyles$1(submenu, this.styles.submenuContainer);
1561
1734
  this.createMenuItems(items, submenu, parentItem);
1562
1735
  document.body.appendChild(submenu);
1563
1736
  const submenuRect = submenu.getBoundingClientRect();
@@ -10231,7 +10404,7 @@ ${recordsStr}
10231
10404
  constructor(pluginOptions = {}) {
10232
10405
  this.id = pluginOptions.id ?? this.id;
10233
10406
  this.pluginOptions = pluginOptions;
10234
- this.menuManager = new MenuManager();
10407
+ this.menuManager = new MenuManager(mergeStyles(pluginOptions.customMenuAttributions?.style), mergeClasses(pluginOptions.customMenuAttributions?.class));
10235
10408
  this.menuHandler = new MenuHandler();
10236
10409
  this.initDefaultMenuItems();
10237
10410
  }
@@ -27048,11 +27221,7 @@ ${recordsStr}
27048
27221
  exports.HighlightHeaderWhenSelectCellPlugin = HighlightHeaderWhenSelectCellPlugin;
27049
27222
  exports.HistoryPlugin = HistoryPlugin;
27050
27223
  exports.InvertHighlightPlugin = InvertHighlightPlugin;
27051
- exports.MENU_CONTAINER_CLASS = MENU_CONTAINER_CLASS;
27052
- exports.MENU_ITEM_CLASS = MENU_ITEM_CLASS;
27053
- exports.MENU_ITEM_DISABLED_CLASS = MENU_ITEM_DISABLED_CLASS;
27054
- exports.MENU_ITEM_SEPARATOR_CLASS = MENU_ITEM_SEPARATOR_CLASS;
27055
- exports.MENU_ITEM_SUBMENU_CLASS = MENU_ITEM_SUBMENU_CLASS;
27224
+ exports.MENU_CLASSES = MENU_CLASSES;
27056
27225
  exports.MENU_STYLES = MENU_STYLES;
27057
27226
  exports.MasterDetailPlugin = MasterDetailPlugin;
27058
27227
  exports.MenuManager = MenuManager;
@@ -27078,10 +27247,15 @@ ${recordsStr}
27078
27247
  exports.extendNumberRule = extendNumberRule;
27079
27248
  exports.formulaRule = formulaRule;
27080
27249
  exports.loopSeriesRule = loopSeriesRule;
27250
+ exports.mergeClasses = mergeClasses;
27251
+ exports.mergeStyles = mergeStyles;
27252
+ exports.normalizeClassName = normalizeClassName;
27253
+ exports.normalizeItemClassNameConfig = normalizeItemClassNameConfig;
27081
27254
  exports.numberRule = numberRule;
27082
27255
  exports.otherRule = otherRule;
27083
27256
  exports.replayCommand = replayCommand;
27084
27257
  exports.reverseIfNeed = reverseIfNeed;
27085
27258
  exports.rotate90WithTransform = rotate90WithTransform;
27259
+ exports.sanitizeSvg = sanitizeSvg;
27086
27260
 
27087
27261
  }));