@freegridjs/core 1.7.1 → 1.9.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.js CHANGED
@@ -1296,6 +1296,7 @@ function columnFormulaValue(def, row) {
1296
1296
  if (scope) return valueIn(scope, cacheOf(scope), row, def.field);
1297
1297
  return run(row[def.field], { row, scope, seen: /* @__PURE__ */ new Set([def.field]), depth: 0, memo: /* @__PURE__ */ new Map() });
1298
1298
  }
1299
+ var formulasOff = (def) => !!scopes.get(def)?.off?.();
1299
1300
  var isFormula = (v) => typeof v === "string" && v.length > 1 && v.startsWith("=");
1300
1301
  function formulaRefs(formula) {
1301
1302
  let n;
@@ -1327,10 +1328,10 @@ function getCellValue(def, row) {
1327
1328
  return void 0;
1328
1329
  }
1329
1330
  }
1330
- if (typeof def.formula === "string" && isFormula(def.formula)) return columnFormulaValue(def, row);
1331
+ if (typeof def.formula === "string" && isFormula(def.formula) && !formulasOff(def)) return columnFormulaValue(def, row);
1331
1332
  if (def.field == null) return void 0;
1332
1333
  const raw = row[def.field];
1333
- return def.formulas && isFormula(raw) ? columnFormulaValue(def, row) : raw;
1334
+ return def.formulas && isFormula(raw) && !formulasOff(def) ? columnFormulaValue(def, row) : raw;
1334
1335
  }
1335
1336
 
1336
1337
  // src/interactions/filterRow.ts
@@ -3416,6 +3417,8 @@ function nextFocus(key, mods, p, s) {
3416
3417
 
3417
3418
  // src/registry.ts
3418
3419
  var liveGrids = /* @__PURE__ */ new Map();
3420
+ var apiGrids = /* @__PURE__ */ new WeakMap();
3421
+ var gridOfApi = (api) => api ? apiGrids.get(api) : void 0;
3419
3422
  var gridOfRoot = (root) => root ? liveGrids.get(root) : void 0;
3420
3423
 
3421
3424
  // src/interactions/body.ts
@@ -3492,7 +3495,7 @@ function attachBody(grid) {
3492
3495
  const data = grid.columns.displayed().filter((c) => !isSystemCol(c.id));
3493
3496
  if (data.length) {
3494
3497
  grid.setFocus(ev.rowIndex, data[0].id);
3495
- grid.setRange({ start: { rowIndex: ev.rowIndex, colId: data[0].id }, end: { rowIndex: ev.rowIndex, colId: data[data.length - 1].id } });
3498
+ if (grid.rangesOn()) grid.setRange({ start: { rowIndex: ev.rowIndex, colId: data[0].id }, end: { rowIndex: ev.rowIndex, colId: data[data.length - 1].id } });
3496
3499
  }
3497
3500
  return;
3498
3501
  }
@@ -3501,9 +3504,9 @@ function attachBody(grid) {
3501
3504
  const prev = grid.store.state.focus;
3502
3505
  const onBox = ev.colId === SELECT_COL_ID;
3503
3506
  if (dragged) {
3504
- } else if (e.shiftKey && prev && !onBox && !isSystemCol(prev.colId) && grid.options.rangeSelection !== false) {
3507
+ } else if (e.shiftKey && prev && !onBox && !isSystemCol(prev.colId) && grid.rangesOn()) {
3505
3508
  grid.setRange({ start: prev, end: { rowIndex: ev.rowIndex, colId: ev.colId } }, true);
3506
- } else if ((e.ctrlKey || e.metaKey) && !onBox && !isSystemCol(ev.colId) && grid.options.rangeSelection !== false) {
3509
+ } else if ((e.ctrlKey || e.metaKey) && !onBox && !isSystemCol(ev.colId) && grid.rangesOn()) {
3507
3510
  const at = { rowIndex: ev.rowIndex, colId: ev.colId };
3508
3511
  grid.addRange({ start: at, end: at });
3509
3512
  grid.setFocus(ev.rowIndex, ev.colId);
@@ -3511,7 +3514,7 @@ function attachBody(grid) {
3511
3514
  grid.setFocus(ev.rowIndex, ev.colId);
3512
3515
  grid.setRange(null);
3513
3516
  }
3514
- if (grid.options.rowSelection && !dragged) {
3517
+ if (grid.options.rowSelection && !dragged && (onBox || grid.options.rowClickSelection !== false)) {
3515
3518
  grid.selectFromClick(ev.rowIndex, onBox || e.ctrlKey || e.metaKey, e.shiftKey);
3516
3519
  }
3517
3520
  grid.events.emit("cellClick", ev);
@@ -3569,7 +3572,7 @@ function attachBody(grid) {
3569
3572
  grid.selectAllCells();
3570
3573
  return true;
3571
3574
  case "find":
3572
- if (grid.options.findBar === false) return false;
3575
+ if (grid.options.findBar === false || !grid.paid) return false;
3573
3576
  grid.finder.openBar();
3574
3577
  return true;
3575
3578
  case "paste":
@@ -3679,7 +3682,7 @@ function attachBody(grid) {
3679
3682
  clearTimeout(pressTimer);
3680
3683
  pressTimer = window.setTimeout(() => {
3681
3684
  cancel();
3682
- if (grid.options.contextMenu === false) return;
3685
+ if (!grid.contextMenu.enabled()) return;
3683
3686
  suppressClick = true;
3684
3687
  if (!isSystemCol(hit.colId)) {
3685
3688
  grid.setFocus(hit.rowIndex, hit.colId);
@@ -3698,7 +3701,7 @@ function attachBody(grid) {
3698
3701
  return;
3699
3702
  }
3700
3703
  if (e.button === 0 && onHandleDown(e)) return;
3701
- if (grid.options.rangeSelection === false) return;
3704
+ if (!grid.rangesOn()) return;
3702
3705
  if (e.button !== 0 || e.shiftKey || e.target.closest("input, a, button")) return;
3703
3706
  const start = renderer.cellAt(e.target);
3704
3707
  if (!start || isSystemCol(start.colId)) return;
@@ -3746,7 +3749,7 @@ function attachBody(grid) {
3746
3749
  grid.setRange(null);
3747
3750
  return;
3748
3751
  }
3749
- if (ctrl && e.key.toLowerCase() === "a" && grid.options.rangeSelection !== false) {
3752
+ if (ctrl && e.key.toLowerCase() === "a" && grid.rangesOn()) {
3750
3753
  e.preventDefault();
3751
3754
  grid.selectAllCells();
3752
3755
  return;
@@ -3763,7 +3766,7 @@ function attachBody(grid) {
3763
3766
  }
3764
3767
  const size = { rows: count, cols: cols.length, pageRows: renderer.pageRows() };
3765
3768
  const firstData = cols.findIndex((c) => !isSystemCol(c.id));
3766
- const ranges = grid.options.rangeSelection !== false;
3769
+ const ranges = grid.rangesOn();
3767
3770
  if (ranges && e.shiftKey && e.key !== "Tab" && NAV_KEYS.has(e.key)) {
3768
3771
  const anchor = f ?? { rowIndex: 0, colId: cols[firstData].id };
3769
3772
  const range = grid.store.state.range;
@@ -4264,6 +4267,10 @@ var ContextMenu = class {
4264
4267
  this.close();
4265
4268
  this.detach();
4266
4269
  }
4270
+ /** The cell menu is on: not switched off and the paid feature allowed (closed: the browser's menu shows). */
4271
+ enabled() {
4272
+ return this.grid.options.contextMenu !== false && this.grid.allow("contextMenu");
4273
+ }
4267
4274
  onContextMenu(e) {
4268
4275
  const { grid } = this;
4269
4276
  if (grid.options.contextMenu === false) return;
@@ -4278,7 +4285,7 @@ var ContextMenu = class {
4278
4285
  return;
4279
4286
  }
4280
4287
  const hit = grid.renderer.cellAt(target);
4281
- if (!hit) return;
4288
+ if (!hit || !this.enabled()) return;
4282
4289
  e.preventDefault();
4283
4290
  grid.renderer.gridEl.focus({ preventScroll: true });
4284
4291
  if (!isSystemCol(hit.colId) && !grid.rangeContains(hit.rowIndex, hit.colId)) {
@@ -4290,7 +4297,7 @@ var ContextMenu = class {
4290
4297
  /** Keyboard: Shift+F10 or the ContextMenu key. */
4291
4298
  openAtFocus() {
4292
4299
  const f = this.grid.store.state.focus;
4293
- if (!f || this.grid.options.contextMenu === false) return;
4300
+ if (!f || !this.enabled()) return;
4294
4301
  const r = this.grid.renderer.root.getBoundingClientRect();
4295
4302
  const cell = this.grid.renderer.root.querySelector(`.fg-row[data-row="${f.rowIndex}"] .fg-cell[data-col="${CSS.escape(f.colId)}"]`);
4296
4303
  const box = cell?.getBoundingClientRect() ?? r;
@@ -4333,7 +4340,7 @@ var ContextMenu = class {
4333
4340
  if (grid.options.rowSelection === "multiple") items.push({ label: loc.selectAllRows, action: () => grid.api.selectAll() });
4334
4341
  items.push({ label: loc.chooseColumns, action: () => grid.chooser.open() });
4335
4342
  const b = grid.rangeBounds();
4336
- if (b && (b.r2 > b.r1 || b.c2 > b.c1)) {
4343
+ if (b && (b.r2 > b.r1 || b.c2 > b.c1) && grid.paid) {
4337
4344
  items.push(
4338
4345
  "separator",
4339
4346
  { label: loc.chartBar, action: () => grid.api.createChart({ type: "bar" }) },
@@ -4344,12 +4351,12 @@ var ContextMenu = class {
4344
4351
  items.push(
4345
4352
  "separator",
4346
4353
  { label: loc.exportCsv, action: () => grid.api.exportCsv() },
4347
- { label: loc.exportExcel, action: () => grid.api.exportXlsx() },
4354
+ ...grid.paid ? [{ label: loc.exportExcel, action: () => grid.api.exportXlsx() }] : [],
4348
4355
  { label: loc.exportJson, action: () => grid.api.exportJson() },
4349
- ...grid.options.assistant?.url ? [{ label: `${loc.ai.title}\u2026`, action: () => grid.api.openAssistant() }] : [],
4350
- { label: loc.exportPdf, action: () => grid.api.exportPdf() }
4356
+ ...grid.options.assistant?.url && grid.paid ? [{ label: `${loc.ai.title}\u2026`, action: () => grid.api.openAssistant() }] : [],
4357
+ ...grid.paid ? [{ label: loc.exportPdf, action: () => grid.api.exportPdf() }] : []
4351
4358
  );
4352
- if (grid.options.advancedFilter !== false && !grid.options.pivot) items.push("separator", { label: `${loc.advancedFilter}\u2026`, action: () => grid.filterBuilder.open() });
4359
+ if (grid.options.advancedFilter !== false && !grid.options.pivot && grid.paid) items.push("separator", { label: `${loc.advancedFilter}\u2026`, action: () => grid.filterBuilder.open() });
4353
4360
  items.push("separator", { label: loc.print, action: () => grid.api.print() });
4354
4361
  return items;
4355
4362
  }
@@ -4492,8 +4499,7 @@ var FillHandle = class {
4492
4499
  this.handle.addEventListener("pointerdown", this.onDown);
4493
4500
  }
4494
4501
  enabled() {
4495
- const o = this.grid.options;
4496
- return o.fillHandle !== false && o.rangeSelection !== false;
4502
+ return this.grid.options.fillHandle !== false && this.grid.rangesOn();
4497
4503
  }
4498
4504
  /** Current source block: the range, or the focused cell. */
4499
4505
  source() {
@@ -4670,7 +4676,7 @@ var Finder = class {
4670
4676
  this.countEl.textContent = !this.query ? "" : this.matches.length ? `${this.index + 1}/${this.matches.length}` : loc.noMatches;
4671
4677
  }
4672
4678
  openBar() {
4673
- if (this.grid.options.findBar === false) return;
4679
+ if (this.grid.options.findBar === false || !this.grid.paid) return;
4674
4680
  if (this.bar) {
4675
4681
  this.bar.querySelector("input").focus();
4676
4682
  return;
@@ -7603,12 +7609,36 @@ function buildPivot(source, config, defOf, labels, locale) {
7603
7609
  return { columns: [...rowColumns, ...valueColumns], rows, groupBy: cfg.rows.slice(0, -1) };
7604
7610
  }
7605
7611
 
7612
+ // src/features.ts
7613
+ var PAID_FEATURES = {
7614
+ grouping: { en: "Row grouping and aggregation", ko: "\uD589 \uADF8\uB8F9\xB7\uC9D1\uACC4" },
7615
+ pivot: { en: "Pivot", ko: "\uD53C\uBC97" },
7616
+ charts: { en: "Charts", ko: "\uCC28\uD2B8" },
7617
+ range: { en: "Cell range selection", ko: "\uC140 \uBC94\uC704 \uC120\uD0DD" },
7618
+ clipboard: { en: "Range copy and paste", ko: "\uBC94\uC704 \uBCF5\uC0AC\xB7\uBD99\uC5EC\uB123\uAE30" },
7619
+ fill: { en: "Fill handle", ko: "\uCC44\uC6B0\uAE30 \uD578\uB4E4" },
7620
+ excel: { en: "Excel export", ko: "\uC5D1\uC140 \uB0B4\uBCF4\uB0B4\uAE30" },
7621
+ pdf: { en: "PDF export", ko: "PDF \uB0B4\uBCF4\uB0B4\uAE30" },
7622
+ serverGroups: { en: "Server-side groups", ko: "\uC11C\uBC84 \uADF8\uB8F9" },
7623
+ advancedFilter: { en: "Advanced filter", ko: "\uACE0\uAE09 \uD544\uD130" },
7624
+ sideBar: { en: "Side bar panels", ko: "\uC0AC\uC774\uB4DC\uBC14 \uD328\uB110" },
7625
+ contextMenu: { en: "Context menu", ko: "\uCEE8\uD14D\uC2A4\uD2B8 \uBA54\uB274" },
7626
+ summary: { en: "Summary rows", ko: "\uD569\uACC4 \uD589" },
7627
+ formulas: { en: "Formulas", ko: "\uC218\uC2DD" },
7628
+ find: { en: "Find", ko: "\uCC3E\uAE30" },
7629
+ conditional: { en: "Conditional formatting", ko: "\uC870\uAC74\uBD80 \uC11C\uC2DD" },
7630
+ assistant: { en: "AI assistant", ko: "AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8" },
7631
+ merge: { en: "Merged cells", ko: "\uC140 \uBCD1\uD569" },
7632
+ workbook: { en: "Workbook", ko: "\uC6CC\uD06C\uBD81" }
7633
+ };
7634
+
7606
7635
  // src/version.ts
7607
- var version = "1.7.1";
7636
+ var version = "1.9.0";
7608
7637
 
7609
7638
  // src/license.ts
7610
7639
  var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
7611
7640
  var PRICING_URL = "https://freegrid.app/pricing/";
7641
+ var PERSONAL_URL = "https://freegrid.app/license/personal/";
7612
7642
  var USAGE_URL = "https://freegrid.app/api/usage";
7613
7643
  var STATUS_URL = "https://freegrid.app/api/domain-status";
7614
7644
  var majorOf = (v) => Number.parseInt(String(v).split(".")[0], 10);
@@ -7624,11 +7654,9 @@ function domainMatches(host, domains) {
7624
7654
  const h = host.toLowerCase().replace(/\.$/, "");
7625
7655
  return domains.some((d) => {
7626
7656
  const raw = d.toLowerCase().replace(/\.$/, "");
7627
- const wildcard = raw.startsWith("*.");
7628
- const dom = wildcard ? raw.slice(2) : raw;
7657
+ const dom = raw.startsWith("*.") ? raw.slice(2) : raw;
7629
7658
  if (!dom) return false;
7630
- if (h === dom || h === `www.${dom}`) return true;
7631
- return wildcard && h.endsWith(`.${dom}`);
7659
+ return h === dom || h.endsWith(`.${dom}`);
7632
7660
  });
7633
7661
  }
7634
7662
  var isDay = (s) => {
@@ -7646,6 +7674,7 @@ async function verifyLicense(key, host, opts = {}) {
7646
7674
  info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
7647
7675
  if (!Array.isArray(info.domains) || !isDay(info.expires)) return { ok: false, reason: "format" };
7648
7676
  if (info.major !== void 0 && !(Number.isInteger(info.major) && info.major > 0)) return { ok: false, reason: "format" };
7677
+ if (info.plan !== void 0 && info.plan !== "company" && info.plan !== "personal") return { ok: false, reason: "format" };
7649
7678
  } catch {
7650
7679
  return { ok: false, reason: "format" };
7651
7680
  }
@@ -7666,13 +7695,18 @@ async function verifyLicense(key, host, opts = {}) {
7666
7695
  }
7667
7696
  var globalKey;
7668
7697
  var statuses = /* @__PURE__ */ new Map();
7669
- var badgeCheck = null;
7698
+ var statusChecks = /* @__PURE__ */ new Map();
7699
+ var gateNotices = /* @__PURE__ */ new Set();
7700
+ var blockedWarned = /* @__PURE__ */ new Set();
7670
7701
  function setLicenseKey(key) {
7671
7702
  globalKey = key;
7672
7703
  }
7673
7704
  var NOTICE = {
7674
- missing: `FreeGrid is free for personal use. Company use needs a domain license (\u20A9200,000 per domain per year): ${PRICING_URL}
7675
- FreeGrid\uB294 \uAC1C\uC778\uC5D0\uAC8C \uBB34\uB8CC\uC785\uB2C8\uB2E4. \uD68C\uC0AC\uC5D0\uC11C \uC4F0\uB824\uBA74 \uB3C4\uBA54\uC778 \uB77C\uC774\uC120\uC2A4(\uB3C4\uBA54\uC778\uB2F9 \uC5F0 20\uB9CC \uC6D0)\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: ${PRICING_URL}`,
7705
+ missing: `FreeGrid: every feature is free for 30 days on a domain. After that, paid features need a free personal key (${PERSONAL_URL}) or a company key: ${PRICING_URL}
7706
+ FreeGrid: \uB3C4\uBA54\uC778\uB9C8\uB2E4 30\uC77C \uB3D9\uC548 \uBAA8\uB4E0 \uAE30\uB2A5\uC744 \uBB34\uB8CC\uB85C \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uADF8 \uB4A4 \uC720\uB8CC \uAE30\uB2A5\uC5D0\uB294 \uBB34\uB8CC \uAC1C\uC778 \uD0A4(${PERSONAL_URL}) \uB610\uB294 \uD68C\uC0AC \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: ${PRICING_URL}`,
7707
+ trial: (ends) => `FreeGrid: the 30-day trial of paid features on this domain ends ${ends}. / \uC774 \uB3C4\uBA54\uC778\uC758 \uC720\uB8CC \uAE30\uB2A5 30\uC77C \uCCB4\uD5D8\uC740 ${ends}\uC5D0 \uB05D\uB0A9\uB2C8\uB2E4.`,
7708
+ ended: `FreeGrid: the 30-day trial on this domain has ended, so paid features are now off. Get a free personal key (${PERSONAL_URL}) or a company key: ${PRICING_URL}
7709
+ FreeGrid: \uC774 \uB3C4\uBA54\uC778\uC758 30\uC77C \uCCB4\uD5D8\uC774 \uB05D\uB098 \uC720\uB8CC \uAE30\uB2A5\uC774 \uAEBC\uC84C\uC2B5\uB2C8\uB2E4. \uBB34\uB8CC \uAC1C\uC778 \uD0A4(${PERSONAL_URL}) \uB610\uB294 \uD68C\uC0AC \uD0A4\uB97C \uBC1B\uC73C\uC138\uC694: ${PRICING_URL}`,
7676
7710
  format: "FreeGrid: the license key is not in the right format. / \uB77C\uC774\uC120\uC2A4 \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
7677
7711
  signature: "FreeGrid: the license key could not be verified. / \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
7678
7712
  expired: "FreeGrid: the license key has expired. / \uB77C\uC774\uC120\uC2A4 \uAE30\uAC04\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4.",
@@ -7716,31 +7750,98 @@ ${PRICING_URL}`);
7716
7750
  statuses.set(id, status);
7717
7751
  return status;
7718
7752
  }
7719
- function badgeRequested(host = typeof location !== "undefined" ? location.hostname : "") {
7720
- if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(false);
7721
- if (badgeCheck) return badgeCheck;
7753
+ var gateHost;
7754
+ var currentHost = () => gateHost ?? (typeof location !== "undefined" ? location.hostname : "");
7755
+ var parseStatus = (d) => {
7756
+ if (!d || typeof d !== "object") return null;
7757
+ const { badge, trial } = d;
7758
+ const valid = !!trial && typeof trial === "object" && isDay(trial.started) && isDay(trial.ends) && typeof trial.active === "boolean";
7759
+ return { badge: badge === true, trial: valid ? { started: trial.started, ends: trial.ends, active: trial.active } : null };
7760
+ };
7761
+ function fetchDomainStatus(host = currentHost()) {
7762
+ if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(null);
7763
+ const pending = statusChecks.get(host);
7764
+ if (pending) return pending;
7722
7765
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7723
7766
  let store = null;
7724
7767
  try {
7725
7768
  store = typeof localStorage !== "undefined" ? localStorage : null;
7726
- const cached = JSON.parse(store?.getItem("freegrid:badge") ?? "null");
7727
- if (cached && cached.host === host && cached.day === day) return badgeCheck = Promise.resolve(cached.show);
7769
+ const cached = JSON.parse(store?.getItem("freegrid:status") ?? "null");
7770
+ const status = cached && cached.host === host && cached.day === day ? parseStatus(cached.status) : null;
7771
+ if (status) {
7772
+ const hit = Promise.resolve(status);
7773
+ statusChecks.set(host, hit);
7774
+ return hit;
7775
+ }
7728
7776
  } catch {
7729
7777
  }
7730
- badgeCheck = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : { badge: false }).then((d) => {
7731
- const show = d.badge === true;
7732
- try {
7733
- store?.setItem("freegrid:badge", JSON.stringify({ host, day, show }));
7734
- } catch {
7778
+ const check = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : null).then((d) => {
7779
+ const status = parseStatus(d);
7780
+ if (status) {
7781
+ try {
7782
+ store?.setItem("freegrid:status", JSON.stringify({ host, day, status }));
7783
+ } catch {
7784
+ }
7735
7785
  }
7736
- return show;
7737
- }, () => false);
7738
- return badgeCheck;
7786
+ return status;
7787
+ }).catch(() => null);
7788
+ statusChecks.set(host, check);
7789
+ return check;
7790
+ }
7791
+ function badgeRequested(host = currentHost()) {
7792
+ return fetchDomainStatus(host).then((s) => s?.badge === true);
7739
7793
  }
7740
- async function needsFreeBadge(key, host = typeof location !== "undefined" ? location.hostname : "") {
7794
+ async function needsFreeBadge(key, host = currentHost()) {
7741
7795
  if (await checkLicense(key, host)) return false;
7742
7796
  return badgeRequested(host);
7743
7797
  }
7798
+ var GATE_KEY = "freegrid:gate";
7799
+ var keyIdOf = (key) => {
7800
+ if (!key) return "";
7801
+ const parts = key.trim().split(".");
7802
+ return parts[parts.length - 1].slice(-16);
7803
+ };
7804
+ function paidAllowed(key, host = currentHost()) {
7805
+ if (isDevHost(host)) return true;
7806
+ try {
7807
+ const cache2 = JSON.parse(localStorage.getItem(GATE_KEY) ?? "null");
7808
+ if (!cache2 || cache2.host !== host || cache2.keyId !== keyIdOf(key ?? globalKey)) return true;
7809
+ return cache2.paid !== false;
7810
+ } catch {
7811
+ return true;
7812
+ }
7813
+ }
7814
+ var writeGate = (cache2) => {
7815
+ try {
7816
+ localStorage.setItem(GATE_KEY, JSON.stringify(cache2));
7817
+ } catch {
7818
+ }
7819
+ };
7820
+ async function resolveGate(key, host = currentHost()) {
7821
+ const k = key ?? globalKey;
7822
+ const keyId = keyIdOf(k);
7823
+ if (await checkLicense(k, host)) {
7824
+ writeGate({ host, keyId, paid: true });
7825
+ return true;
7826
+ }
7827
+ const trial = (await fetchDomainStatus(host))?.trial;
7828
+ if (!trial) return true;
7829
+ writeGate({ host, keyId, paid: trial.active, trialEnds: trial.ends });
7830
+ const notice = `${trial.active ? "trial" : "ended"}@${host}`;
7831
+ if (!gateNotices.has(notice)) {
7832
+ gateNotices.add(notice);
7833
+ if (trial.active) console.info(NOTICE.trial(trial.ends));
7834
+ else console.warn(NOTICE.ended);
7835
+ }
7836
+ return trial.active;
7837
+ }
7838
+ function paidBlocked(feature) {
7839
+ if (blockedWarned.has(feature)) return;
7840
+ blockedWarned.add(feature);
7841
+ const name = PAID_FEATURES[feature] ?? { en: feature, ko: feature };
7842
+ console.warn(`FreeGrid: ${name.en} is a paid feature and is off on this domain. Get a free personal key (${PERSONAL_URL}) or a company key: ${PRICING_URL}
7843
+ FreeGrid: ${name.ko}\uC740(\uB294) \uC720\uB8CC \uAE30\uB2A5\uC774\uB77C \uC774 \uB3C4\uBA54\uC778\uC5D0\uC11C \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4. \uBB34\uB8CC \uAC1C\uC778 \uD0A4(${PERSONAL_URL}) \uB610\uB294 \uD68C\uC0AC \uD0A4\uB97C \uBC1B\uC73C\uC138\uC694: ${PRICING_URL}`);
7844
+ }
7744
7845
 
7745
7846
  // src/interactions/menu.ts
7746
7847
  var OPS = {
@@ -7785,7 +7886,7 @@ var ColumnMenu = class {
7785
7886
  [loc.unpin, () => this.grid.setPinned(colId, null)]
7786
7887
  ]));
7787
7888
  const grouped = this.grid.getGroupBy();
7788
- if (!this.grid.options.treeData && (this.grid.options.groupPanel || grouped.length)) {
7889
+ if (!this.grid.options.treeData && this.grid.paid && (this.grid.options.groupPanel || grouped.length)) {
7789
7890
  const g = grouped.includes(colId) ? [[loc.ungroup, () => this.grid.setGroupBy(grouped.filter((x) => x !== colId))]] : [[loc.groupByThis, () => this.grid.setGroupBy([...grouped, colId])]];
7790
7891
  if (grouped.length) g.push([loc.expandAll, () => this.grid.expandAll(true)], [loc.collapseAll, () => this.grid.expandAll(false)]);
7791
7892
  popup.append(this.section(g));
@@ -7796,7 +7897,7 @@ var ColumnMenu = class {
7796
7897
  ];
7797
7898
  if (!def.lockVisible) items.push([loc.hideColumn, () => this.grid.setColumnsVisible([colId], false)]);
7798
7899
  items.push([loc.chooseColumns, () => this.grid.chooser.open()], [loc.resetColumns, () => this.grid.resetColumns()]);
7799
- if (this.grid.options.conditionalFormatMenu !== false) items.push([`${loc.cf.title}\u2026`, () => this.grid.formatDialog.open(colId)]);
7900
+ if (this.grid.options.conditionalFormatMenu !== false && this.grid.paid) items.push([`${loc.cf.title}\u2026`, () => this.grid.formatDialog.open(colId)]);
7800
7901
  popup.append(this.section(items));
7801
7902
  const filter = this.filterSection(colId, () => this.close());
7802
7903
  if (filter) popup.append(filter);
@@ -9704,6 +9805,7 @@ var Grid = class {
9704
9805
  this.pageSize = DEFAULT_PAGE_SIZE;
9705
9806
  /** What formulas see: every source row, and computed columns by id. */
9706
9807
  this.formulaScope = {
9808
+ off: () => !this.allow("formulas"),
9707
9809
  rows: () => this.store.state.data,
9708
9810
  version: () => this.editVersion,
9709
9811
  computed: (name) => {
@@ -9768,6 +9870,7 @@ var Grid = class {
9768
9870
  this.selectionVersion = 0;
9769
9871
  /** Rows select-all acts on: displayed data rows that are selectable. */
9770
9872
  this.selectableCache = null;
9873
+ this.paid = paidAllowed(options.licenseKey);
9771
9874
  if (!Array.isArray(options.columns)) warn("options.columns must be an array");
9772
9875
  if (options.data != null && !Array.isArray(options.data)) warn("options.data must be an array");
9773
9876
  this.store = new Store({
@@ -9809,12 +9912,12 @@ var Grid = class {
9809
9912
  collator: () => this.collator(),
9810
9913
  textVersion: () => `${this.store.state.locale.code}|${this.editVersion}`
9811
9914
  });
9812
- this.groupBy = this.validGroupBy(options.groupBy ?? []);
9813
- if (Array.isArray(options.conditionalFormats)) this.cfRules = options.conditionalFormats.filter((r) => r && typeof r === "object" && Array.isArray(r.columns)).map((r) => ({ ...r, columns: [...r.columns] }));
9814
- this.defaultExpanded = options.groupDefaultExpanded ?? 0;
9915
+ if (options.groupBy?.length && this.allow(this.rowModel.server ? "serverGroups" : "grouping")) this.groupBy = this.validGroupBy(options.groupBy);
9916
+ if (Array.isArray(options.conditionalFormats) && options.conditionalFormats.length && this.allow("conditional")) this.cfRules = options.conditionalFormats.filter((r) => r && typeof r === "object" && Array.isArray(r.columns)).map((r) => ({ ...r, columns: [...r.columns] }));
9917
+ this.defaultExpanded = options.treeData || this.paid ? options.groupDefaultExpanded ?? 0 : 0;
9815
9918
  if (options.treeData && !options.getRowId) warn("treeData needs getRowId so parent ids can be matched");
9816
9919
  this.updateRows();
9817
- this.summaryOpts = options.summary ?? null;
9920
+ this.summaryOpts = options.summary && this.allow("summary") ? options.summary : null;
9818
9921
  this.summarySnapshot = snapshot(this.summaryOpts);
9819
9922
  this.refreshSummary();
9820
9923
  const explicit = options.fontSize != null && options.fontSize > 0 ? options.fontSize : null;
@@ -9835,9 +9938,9 @@ var Grid = class {
9835
9938
  this.editor = new CellEditor(this);
9836
9939
  this.finder = new Finder(this);
9837
9940
  this.fillHandle = new FillHandle(this);
9838
- this.groupPanel = options.groupPanel ? new GroupPanel(this) : null;
9941
+ this.groupPanel = options.groupPanel && this.allow("grouping") ? new GroupPanel(this) : null;
9839
9942
  this.pinnedRows = { top: cleanRows(options.pinnedTopRows ?? []), bottom: cleanRows(options.pinnedBottomRows ?? []) };
9840
- this.sideBar = options.sideBar ? new SideBar(
9943
+ this.sideBar = options.sideBar && this.allow("sideBar") ? new SideBar(
9841
9944
  this,
9842
9945
  typeof options.sideBar === "object" && !!options.sideBar.open,
9843
9946
  (typeof options.sideBar === "object" ? options.sideBar.panels : void 0) ?? (options.pivot || options.pivotPanel ? ["columns", "filters", "pivot"] : void 0)
@@ -9870,11 +9973,23 @@ var Grid = class {
9870
9973
  this.renderer.render();
9871
9974
  this.api = this.createApi();
9872
9975
  liveGrids.set(this.renderer.root, this);
9976
+ apiGrids.set(this.api, this);
9977
+ void resolveGate(options.licenseKey);
9873
9978
  }
9874
9979
  /** Bumped whenever cell values change (edits, transactions, undo). */
9875
9980
  get valuesVersion() {
9876
9981
  return this.editVersion;
9877
9982
  }
9983
+ /** Cell ranges are on: not switched off and paid features allowed. Closed behaves exactly like `rangeSelection: false`. */
9984
+ rangesOn() {
9985
+ return this.options.rangeSelection !== false && this.paid;
9986
+ }
9987
+ /** Whether a paid feature may run; warns once per feature when it may not. */
9988
+ allow(feature) {
9989
+ if (this.paid) return true;
9990
+ paidBlocked(feature);
9991
+ return false;
9992
+ }
9878
9993
  /** The column definitions as given (setColumns), without the system columns. */
9879
9994
  columnDefs() {
9880
9995
  return [...this.sourceDefs];
@@ -9887,6 +10002,7 @@ var Grid = class {
9887
10002
  warn("setConditionalFormats: formats must be an array");
9888
10003
  return;
9889
10004
  }
10005
+ if (list.length && !this.allow("conditional")) return;
9890
10006
  this.cfRules = list.filter((r) => r && typeof r === "object" && Array.isArray(r.columns)).map((r) => ({ ...r, columns: [...r.columns] }));
9891
10007
  this.renderer.invalidate({ rows: true });
9892
10008
  this.events.emit("conditionalFormatsChange", { formats: this.getConditionalFormats() });
@@ -9984,6 +10100,7 @@ var Grid = class {
9984
10100
  return out;
9985
10101
  }
9986
10102
  setPivot(cfg) {
10103
+ if (cfg && !this.allow("pivot")) return;
9987
10104
  if (cfg && this.clientOnly("pivot")) return;
9988
10105
  if (cfg && (typeof cfg !== "object" || !Array.isArray(cfg.rows) || !Array.isArray(cfg.values))) {
9989
10106
  warn("setPivot: expected { rows: string[], values: [...] }");
@@ -10173,6 +10290,15 @@ var Grid = class {
10173
10290
  }
10174
10291
  const update = (r, k, row) => {
10175
10292
  const orig = this.tracker.originals.get(row);
10293
+ if (r === row) {
10294
+ for (const field of Object.keys(r)) {
10295
+ if (orig) orig[field] = r[field];
10296
+ changed.fields.add(field);
10297
+ }
10298
+ changed.ids.add(k);
10299
+ result.updated.push(row);
10300
+ return;
10301
+ }
10176
10302
  for (const [field, value] of Object.entries(r)) {
10177
10303
  if (orig) orig[field] = value;
10178
10304
  const old = row[field];
@@ -10230,6 +10356,7 @@ var Grid = class {
10230
10356
  }
10231
10357
  setAdvancedFilter(filter) {
10232
10358
  const next = filter && typeof filter === "object" && Array.isArray(filter.conditions) && filter.conditions.length ? filter : null;
10359
+ if (next && !this.allow("advancedFilter")) return;
10233
10360
  if (next === this.store.state.advancedFilter) return;
10234
10361
  this.store.set({ advancedFilter: next });
10235
10362
  this.events.emit("advancedFilterChange", { advancedFilter: next });
@@ -10441,6 +10568,7 @@ var Grid = class {
10441
10568
  return [...this.groupBy];
10442
10569
  }
10443
10570
  setGroupBy(ids) {
10571
+ if (Array.isArray(ids) && ids.length && !this.allow(this.rowModel.server ? "serverGroups" : "grouping")) return;
10444
10572
  const next = this.validGroupBy(ids);
10445
10573
  if (next.join("") === this.groupBy.join("")) return;
10446
10574
  this.groupBy = next;
@@ -10726,6 +10854,7 @@ var Grid = class {
10726
10854
  warn("setSummary: summary.rows must be an array");
10727
10855
  return;
10728
10856
  }
10857
+ if (opts && !this.allow("summary")) return;
10729
10858
  if (same2(opts, this.summarySnapshot)) {
10730
10859
  this.summaryOpts = opts;
10731
10860
  return;
@@ -10750,13 +10879,14 @@ var Grid = class {
10750
10879
  return this.columns.displayed().filter((c) => !isSystemCol(c.id));
10751
10880
  }
10752
10881
  validRange(range) {
10753
- if (!range || this.options.rangeSelection === false) return null;
10882
+ if (!range || !this.rangesOn()) return null;
10754
10883
  const count = this.rowModel.getRowCount();
10755
10884
  const ok = (p) => !!p && !isSystemCol(p.colId) && !!this.columns.getColumn(p.colId) && p.rowIndex >= 0 && p.rowIndex < count;
10756
10885
  return ok(range.start) && ok(range.end) ? { start: { ...range.start }, end: { ...range.end } } : null;
10757
10886
  }
10758
10887
  /** Set the active range. Earlier Ctrl ranges are dropped unless `keepOthers`. */
10759
10888
  setRange(range, keepOthers = false) {
10889
+ if (range && (range.start?.rowIndex !== range.end?.rowIndex || range.start?.colId !== range.end?.colId) && !this.allow("range")) return;
10760
10890
  const next = this.validRange(range);
10761
10891
  const extras = keepOthers || !this.store.state.extraRanges.length ? this.store.state.extraRanges : [];
10762
10892
  const cur = this.store.state.range;
@@ -10766,6 +10896,7 @@ var Grid = class {
10766
10896
  }
10767
10897
  /** Ctrl+click / Ctrl+drag: keep what is selected (the focused cell when there is no range) and start a new range. */
10768
10898
  addRange(range) {
10899
+ if (!this.allow("range")) return;
10769
10900
  const { range: active, focus } = this.store.state;
10770
10901
  const keep = this.validRange(active ?? (focus ? { start: focus, end: focus } : null));
10771
10902
  if (keep) this.store.set({ extraRanges: [...this.store.state.extraRanges, keep] });
@@ -10777,6 +10908,7 @@ var Grid = class {
10777
10908
  return all2.map((r) => ({ start: { ...r.start }, end: { ...r.end } }));
10778
10909
  }
10779
10910
  setRanges(ranges) {
10911
+ if (Array.isArray(ranges) && ranges.length && !this.allow("range")) return;
10780
10912
  const list = (Array.isArray(ranges) ? ranges : []).map((r) => this.validRange(r)).filter((r) => !!r);
10781
10913
  this.store.set({ extraRanges: list.slice(0, -1) });
10782
10914
  this.setRange(list[list.length - 1] ?? null, true);
@@ -10800,6 +10932,7 @@ var Grid = class {
10800
10932
  return !!col && this.allRangeBounds().some((b) => rangeContains(b, rowIndex, col.index));
10801
10933
  }
10802
10934
  selectAllCells() {
10935
+ if (!this.allow("range")) return;
10803
10936
  const cols = this.dataColumns();
10804
10937
  const count = this.rowModel.getRowCount();
10805
10938
  if (!cols.length || !count) return;
@@ -10809,6 +10942,7 @@ var Grid = class {
10809
10942
  // ── clipboard ───────────────────────────────────────────────
10810
10943
  /** TSV for the range, else the focused cell; `rows` copies the checked rows instead. */
10811
10944
  selectionTSV(headers, rows = false) {
10945
+ if (!rows && this.multiCell() && !this.allow("clipboard")) return "";
10812
10946
  const code = this.store.state.locale.code;
10813
10947
  const displayed = this.columns.displayed();
10814
10948
  const b = rows ? null : this.rangeBounds();
@@ -10850,6 +10984,11 @@ var Grid = class {
10850
10984
  if (headers) matrix.unshift(cols.map((c) => c.def.header ?? c.def.field ?? c.id));
10851
10985
  return toTSV(matrix);
10852
10986
  }
10987
+ /** The selection covers more than one cell (several ranges, or one range wider than a cell). */
10988
+ multiCell() {
10989
+ const all2 = this.allRangeBounds();
10990
+ return all2.length > 1 || all2.some((b) => b.r1 !== b.r2 || b.c1 !== b.c2);
10991
+ }
10853
10992
  /** Like Excel: ranges stack when they share columns or rows; otherwise only the active one is copied. */
10854
10993
  rangesStack(all2) {
10855
10994
  return all2.length > 1 && (all2.every((x) => x.c1 === all2[0].c1 && x.c2 === all2[0].c2) || all2.every((x) => x.r1 === all2[0].r1 && x.r2 === all2[0].r2));
@@ -10900,6 +11039,7 @@ var Grid = class {
10900
11039
  const matrix = parseTSV(text2);
10901
11040
  const result = { changed: 0, skipped: 0 };
10902
11041
  if (!matrix.length) return result;
11042
+ if ((matrix.length > 1 || matrix[0].length > 1) && !this.allow("clipboard")) return result;
10903
11043
  const displayed = this.columns.displayed();
10904
11044
  const count = this.rowModel.getRowCount();
10905
11045
  const b = this.rangeBounds();
@@ -11008,6 +11148,7 @@ var Grid = class {
11008
11148
  /** Merge blocks for every `merge` column over the displayed rows (memoized). */
11009
11149
  mergeSpans() {
11010
11150
  if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan)) return NO_SPANS;
11151
+ if (!this.allow("merge")) return NO_SPANS;
11011
11152
  const rows = this.rowModel.rows();
11012
11153
  const ver = this.editVersion * 1e6 + this.columns.displayed().length;
11013
11154
  if (this.spanCache && this.spanCache.rows === rows && this.spanCache.ver === ver) return this.spanCache.spans;
@@ -11022,7 +11163,7 @@ var Grid = class {
11022
11163
  }
11023
11164
  rangeAggregates() {
11024
11165
  const all2 = this.allRangeBounds();
11025
- if (!all2.length) return null;
11166
+ if (!all2.length || !this.paid) return null;
11026
11167
  const displayed = this.columns.displayed();
11027
11168
  const key = [JSON.stringify(all2), this.rowModel.rows(), this.editVersion, ...displayed.map((c) => c.def)];
11028
11169
  const memo = this.aggMemo;
@@ -11061,6 +11202,7 @@ var Grid = class {
11061
11202
  }
11062
11203
  /** Fill from the current selection into `target`, Excel-style (one undo step). */
11063
11204
  fillRange(target) {
11205
+ if (!this.allow("fill")) return;
11064
11206
  const src = this.rangeBounds() ?? (() => {
11065
11207
  const f = this.store.state.focus;
11066
11208
  const col = f && this.columns.getColumn(f.colId);
@@ -11109,30 +11251,31 @@ var Grid = class {
11109
11251
  this.renderer.invalidate({ rows: true });
11110
11252
  }
11111
11253
  // ── edits, change tracking, undo ────────────────────────────
11112
- /** Write one cell, record it for undo, and emit cellValueChanged. Returns false when unchanged. */
11254
+ /** Write one cell and record it for undo; endCellTx emits cellValueChanged. Returns false when unchanged. */
11113
11255
  writeCell(changes, row, field, colId, rowIndex, value) {
11114
11256
  const oldValue = row[field];
11115
11257
  if (Object.is(oldValue, value)) return false;
11116
11258
  if (this.veto("beforeCellChange", { row, rowIndex, colId, oldValue, newValue: value })) return false;
11117
11259
  this.tracker.beforeChange(row);
11118
11260
  row[field] = value;
11119
- changes.push({ row, field, colId, oldValue, newValue: value });
11120
- this.events.emit("cellValueChanged", { row, rowIndex, colId, oldValue, newValue: value });
11261
+ changes.push({ row, field, colId, rowIndex, oldValue, newValue: value });
11121
11262
  return true;
11122
11263
  }
11123
- /** Finish a batch of cell writes: one undo step, refresh summary and repaint. */
11264
+ /** Finish a batch of cell writes: one undo step, refresh summary and repaint, then emit cellValueChanged. */
11124
11265
  endCellTx(changes) {
11125
11266
  if (!changes.length) return;
11126
11267
  const apply = (list, key) => {
11127
- for (const c of list) {
11268
+ const done = list.map((c) => {
11128
11269
  const from = c.row[c.field];
11129
11270
  c.row[c.field] = c[key];
11130
- this.events.emit("cellValueChanged", { row: c.row, rowIndex: this.rowModel.indexOfId(this.rowModel.idOf(c.row)), colId: c.colId, oldValue: from, newValue: c[key] });
11131
- }
11271
+ return { row: c.row, rowIndex: this.rowModel.indexOfId(this.rowModel.idOf(c.row)), colId: c.colId, oldValue: from, newValue: c[key] };
11272
+ });
11132
11273
  this.afterDataEdit();
11274
+ for (const e of done) this.events.emit("cellValueChanged", e);
11133
11275
  };
11134
11276
  this.history.push({ undo: () => apply([...changes].reverse(), "oldValue"), redo: () => apply(changes, "newValue") });
11135
11277
  this.afterDataEdit();
11278
+ for (const c of changes) this.events.emit("cellValueChanged", { row: c.row, rowIndex: c.rowIndex, colId: c.colId, oldValue: c.oldValue, newValue: c.newValue });
11136
11279
  }
11137
11280
  afterDataEdit() {
11138
11281
  this.renderer.invalidateHeights();
@@ -11536,7 +11679,8 @@ var Grid = class {
11536
11679
  this.treeLoaded.clear();
11537
11680
  this.treeLoading.clear();
11538
11681
  this.renderer.dropDetail();
11539
- this.store.set({ data: cleanRows(rows) });
11682
+ const clean = cleanRows(rows);
11683
+ this.store.set({ data: clean === this.store.state.data ? [...clean] : clean });
11540
11684
  if (options?.scrollToTop) this.renderer.setScroll({ top: 0, left: 0 });
11541
11685
  const sel = this.store.state.selected;
11542
11686
  if (sel.size) {
@@ -11644,11 +11788,13 @@ var Grid = class {
11644
11788
  this.forcedOverlay = kind === "loading" || kind === "noRows" ? kind : null;
11645
11789
  this.renderer.invalidate();
11646
11790
  }),
11647
- getXlsxSheet: guard((o) => buildXlsxSheet(this, o)),
11791
+ // closed: an empty sheet, so writeXlsx([api.getXlsxSheet()]) is not an Excel export
11792
+ getXlsxSheet: guard((o) => this.allow("excel") ? buildXlsxSheet(this, o) : { name: o?.sheetName ?? "Sheet1", rows: [] }),
11648
11793
  print: guard((o) => printHtml(buildPrintHtml(this, o))),
11649
11794
  getPrintHtml: guard((o) => buildPrintHtml(this, o)),
11650
- getPdf: guard((o) => renderPdf(printModel(this, o), browserSurface, { scale: o?.resolution, quality: o?.quality })),
11795
+ getPdf: guard((o) => this.allow("pdf") ? renderPdf(printModel(this, o), browserSurface, { scale: o?.resolution, quality: o?.quality }) : null),
11651
11796
  exportPdf: guard((name = "export.pdf", o) => {
11797
+ if (!this.allow("pdf")) return;
11652
11798
  const bytes = renderPdf(printModel(this, o), browserSurface, { scale: o?.resolution, quality: o?.quality });
11653
11799
  if (bytes) download(name, [bytes], "application/pdf");
11654
11800
  else warn("exportPdf: this browser cannot draw on a canvas");
@@ -11656,16 +11802,26 @@ var Grid = class {
11656
11802
  setColumnGroupOpened: guard((id, open) => this.setColumnGroupOpened(id, open)),
11657
11803
  setAdvancedFilter: guard((f) => this.setAdvancedFilter(f)),
11658
11804
  getAdvancedFilter: guard(() => this.store.state.advancedFilter ? JSON.parse(JSON.stringify(this.store.state.advancedFilter)) : null),
11659
- openFilterBuilder: guard(() => this.filterBuilder.open()),
11805
+ openFilterBuilder: guard(() => {
11806
+ if (!this.allow("advancedFilter")) return;
11807
+ this.filterBuilder.open();
11808
+ }),
11660
11809
  isColumnGroupOpened: guard((id) => !!this.columns.getNode(id)?.open),
11661
11810
  applyTransaction: guard((tx) => this.applyTransaction(tx)),
11662
11811
  applyTransactionAsync: guard((tx) => this.applyTransactionAsync(tx)),
11663
11812
  flushAsyncTransactions: guard(() => this.flushAsyncTransactions()),
11664
11813
  setConditionalFormats: guard((f) => this.setConditionalFormats(f)),
11665
11814
  getConditionalFormats: guard(() => this.getConditionalFormats()),
11666
- openConditionalFormats: guard((colId) => this.formatDialog.open(colId)),
11667
- askAssistant: guard((prompt) => askAssistant(this, prompt, this.options.assistant)),
11668
- openAssistant: guard(() => this.assistantPanel.open()),
11815
+ openConditionalFormats: guard((colId) => {
11816
+ if (!this.allow("conditional")) return;
11817
+ this.formatDialog.open(colId);
11818
+ }),
11819
+ // closed: as if `assistant` were not set
11820
+ askAssistant: guard((prompt) => askAssistant(this, prompt, this.allow("assistant") ? this.options.assistant : void 0)),
11821
+ openAssistant: guard(() => {
11822
+ if (!this.allow("assistant")) return;
11823
+ this.assistantPanel.open();
11824
+ }),
11669
11825
  getState: guard(() => this.getState()),
11670
11826
  setState: guard((s, o) => this.setState(s, o)),
11671
11827
  getScrollPosition: guard(() => this.renderer.getScroll()),
@@ -11679,7 +11835,10 @@ var Grid = class {
11679
11835
  resetColumnState: guard(() => this.resetColumns()),
11680
11836
  openColumnChooser: guard(() => this.chooser.open()),
11681
11837
  closeColumnChooser: guard(() => this.chooser.close()),
11682
- toggleSideBar: guard((open, panel) => this.sideBar?.toggle(open, panel)),
11838
+ toggleSideBar: guard((open, panel) => {
11839
+ if (!this.allow("sideBar")) return;
11840
+ this.sideBar?.toggle(open, panel);
11841
+ }),
11683
11842
  setSummary: guard((s) => this.setSummary(s)),
11684
11843
  getSummaryValues: guard(() => this.summaryValues.map((v) => ({ ...v }))),
11685
11844
  getRange: guard(() => {
@@ -11709,7 +11868,7 @@ var Grid = class {
11709
11868
  canRedo: guard(() => this.history.canRedo()),
11710
11869
  fillRange: guard((t) => this.fillRange(t)),
11711
11870
  setPinnedRows: guard((r) => this.setPinnedRows(r)),
11712
- find: guard((text2) => this.finder.find(text2)),
11871
+ find: guard((text2) => this.allow("find") ? this.finder.find(text2) : 0),
11713
11872
  findNext: guard(() => this.finder.next()),
11714
11873
  findPrevious: guard(() => this.finder.previous()),
11715
11874
  clearFind: guard(() => this.finder.clear()),
@@ -11730,13 +11889,17 @@ var Grid = class {
11730
11889
  refreshRows: guard(() => this.refreshRows()),
11731
11890
  setPivot: guard((p) => this.setPivot(p)),
11732
11891
  getPivot: guard(() => this.getPivot()),
11733
- createChart: guard((o) => this.charts.create(o)),
11892
+ // closed: an inert handle whose methods do nothing
11893
+ createChart: guard((o) => this.allow("charts") ? this.charts.create(o) : { el: el("div", "fg-chart-body"), setType: () => void 0, update: () => void 0, destroy: () => void 0 }),
11734
11894
  getCsv: guard((o) => buildCsv(this, o)),
11735
11895
  getJson: guard((o) => buildJson(this, o)),
11736
11896
  exportJson: guard((name = "export.json", o) => download(name, [buildJson(this, o)], "application/json;charset=utf-8")),
11737
11897
  exportCsv: guard((name = "export.csv", o) => download(name, ["\uFEFF", buildCsv(this, o)], "text/csv;charset=utf-8")),
11738
- getXlsx: guard((o) => buildXlsx(this, o)),
11739
- exportXlsx: guard((name = "export.xlsx", o) => download(name, [buildXlsx(this, o)], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")),
11898
+ getXlsx: guard((o) => this.allow("excel") ? buildXlsx(this, o) : new Uint8Array(0)),
11899
+ exportXlsx: guard((name = "export.xlsx", o) => {
11900
+ if (!this.allow("excel")) return;
11901
+ download(name, [buildXlsx(this, o)], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
11902
+ }),
11740
11903
  importFile: guard((file, o) => importInto(this, file, o)),
11741
11904
  on: guard((type, fn) => this.events.on(type, fn)),
11742
11905
  destroy: () => this.destroy()
@@ -11764,10 +11927,13 @@ function buildWorkbook(sheets) {
11764
11927
  warn("buildWorkbook: no grids given; the file has one empty sheet");
11765
11928
  return writeXlsx([{ name: "Sheet1", rows: [] }]);
11766
11929
  }
11930
+ if (list.some((s) => gridOfApi(s.grid)?.allow("workbook") === false)) return new Uint8Array(0);
11767
11931
  return writeXlsx(list.map((s, i) => s.grid.getXlsxSheet({ ...s.options, sheetName: s.sheetName ?? s.options?.sheetName ?? `Sheet${i + 1}` })));
11768
11932
  }
11769
11933
  function exportWorkbook(filename, sheets) {
11770
- download(filename || "export.xlsx", [buildWorkbook(sheets)], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
11934
+ const bytes = buildWorkbook(sheets);
11935
+ if (!bytes.length) return;
11936
+ download(filename || "export.xlsx", [bytes], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
11771
11937
  }
11772
11938
 
11773
11939
  // src/rest.ts