@svgrid/enterprise 1.0.1 → 1.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/src/ai.test.ts CHANGED
@@ -70,7 +70,7 @@ beforeEach(() => {
70
70
  // Default to a licensed state so the soft-gate doesn't dump noise on the
71
71
  // console during expected-failure tests. Individual cases clear it when
72
72
  // they want to assert soft-gate behaviour.
73
- setLicenseKey('SVPRO-DEV-TEST')
73
+ setLicenseKey('SVENTERPRISE-DEV-TEST')
74
74
  })
75
75
  afterEach(() => {
76
76
  setAIProvider(null)
package/src/ai.ts CHANGED
@@ -20,13 +20,13 @@
20
20
  * `AIProvider` that knows how to call OpenAI / Anthropic / a local
21
21
  * model / a server-side proxy. We never bundle a model client.
22
22
  *
23
- * License gate: every AI call routes through `assertProLicensed()`,
23
+ * License gate: every AI call routes through `assertEnterpriseLicensed()`,
24
24
  * which soft-gates (still works, adds a watermark, console nudge) when
25
25
  * no key is set so demos and evaluation stay frictionless.
26
26
  */
27
27
 
28
28
  import type { RowData, SvGridApi, TableFeatures } from '@svgrid/grid'
29
- import { assertProLicensed } from './license'
29
+ import { assertEnterpriseLicensed } from './license'
30
30
 
31
31
  // ---------------------------------------------------------------------------
32
32
  // Provider contract
@@ -230,7 +230,7 @@ export async function aiFilter<
230
230
  query: string,
231
231
  opts: AIFilterOptions = {},
232
232
  ): Promise<AIFilterResult> {
233
- assertProLicensed('AI assistant')
233
+ assertEnterpriseLicensed('AI assistant')
234
234
  const schema = buildColumnSchema(api)
235
235
  const prompt =
236
236
  `You are a data-grid filter planner. Translate the user's natural-language ` +
@@ -317,7 +317,7 @@ export async function aiSmartFill<
317
317
  api: SvGridApi<TFeatures, TData>,
318
318
  opts: AISmartFillOptions,
319
319
  ): Promise<AISmartFillResult<TValue>> {
320
- assertProLicensed('AI assistant')
320
+ assertEnterpriseLicensed('AI assistant')
321
321
  if (opts.examples.length === 0) {
322
322
  throw new Error('@svgrid/enterprise/ai: aiSmartFill requires at least one example.')
323
323
  }
@@ -408,7 +408,7 @@ export async function aiSummarize<
408
408
  api: SvGridApi<TFeatures, TData>,
409
409
  opts: AISummarizeOptions,
410
410
  ): Promise<AISummary> {
411
- assertProLicensed('AI assistant')
411
+ assertEnterpriseLicensed('AI assistant')
412
412
  const all = api.getData()
413
413
  const rows: TData[] = (() => {
414
414
  const t = opts.target
@@ -497,7 +497,7 @@ export async function aiClassify<
497
497
  api: SvGridApi<TFeatures, TData>,
498
498
  opts: AIClassifyOptions,
499
499
  ): Promise<AIClassifyResult> {
500
- assertProLicensed('AI assistant')
500
+ assertEnterpriseLicensed('AI assistant')
501
501
  const data = api.getData()
502
502
  const targets = opts.targetRowIndices ?? data.map((_, i) => i)
503
503
  const rubric = opts.classDescriptions
package/src/export.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference path="./pdfmake-shims.d.ts" />
2
2
  import type { RowData, TableFeatures } from '@svgrid/grid'
3
3
  import type { SvGridApi } from '@svgrid/grid'
4
- import { assertProLicensed } from './license'
4
+ import { assertEnterpriseLicensed } from './license'
5
5
  import { installSmartShim, type SmartDataExporterInstance } from './smart-shim'
6
6
 
7
7
  export type ExportFormat = 'xlsx' | 'pdf' | 'csv' | 'tsv' | 'html'
@@ -432,7 +432,7 @@ export async function exportGrid<
432
432
  TFeatures extends TableFeatures,
433
433
  TData extends RowData,
434
434
  >(api: SvGridApi<TFeatures, TData>, opts: ExportOptions<TData>): Promise<void> {
435
- assertProLicensed('Export')
435
+ assertEnterpriseLicensed('Export')
436
436
  await ensureGlobals(opts.format)
437
437
  const Ctor = await getDataExporter()
438
438
 
@@ -43,7 +43,7 @@ function fakeApi() {
43
43
  }
44
44
 
45
45
  beforeEach(() => {
46
- setLicenseKey('SVPRO-DEV-TEST')
46
+ setLicenseKey('SVENTERPRISE-DEV-TEST')
47
47
  })
48
48
  afterEach(() => {
49
49
  clearLicenseKey()
package/src/import.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  */
26
26
 
27
27
  import type { RowData, SvGridApi, TableFeatures } from '@svgrid/grid'
28
- import { assertProLicensed } from './license'
28
+ import { assertEnterpriseLicensed } from './license'
29
29
 
30
30
  // ---------------------------------------------------------------------------
31
31
  // Public types
@@ -557,7 +557,7 @@ export async function importData<
557
557
  api: SvGridApi<TFeatures, TData>,
558
558
  opts: ImportOptions<TData>,
559
559
  ): Promise<ImportResult<TData>> {
560
- assertProLicensed('Import')
560
+ assertEnterpriseLicensed('Import')
561
561
  const format = sniffFormat(opts.file, opts.format ?? 'auto')
562
562
 
563
563
  let matrix: string[][]
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ export {
4
4
  getLicenseKey,
5
5
  isLicenseKeySet,
6
6
  hasValidLicense,
7
- assertProLicensed,
7
+ assertEnterpriseLicensed,
8
8
  } from './license'
9
9
 
10
10
  export {
@@ -29,7 +29,7 @@ export {
29
29
  type ImportRowError,
30
30
  type ImportValidator,
31
31
  } from './import'
32
- export { installPro, type ProGridApi, type ProAIApi, type ProPivotApi } from './install'
32
+ export { installEnterprise, type EnterpriseGridApi, type EnterpriseAIApi, type EnterprisePivotApi } from './install'
33
33
  export {
34
34
  createStagedEditing,
35
35
  type StagedChange,
@@ -40,7 +40,7 @@ export { dismissUnlicensedNudge } from './watermark'
40
40
  export {
41
41
  showUpgradePrompt,
42
42
  dismissUpgradePrompt,
43
- type ProFeatureLabel,
43
+ type EnterpriseFeatureLabel,
44
44
  } from './upgrade-prompt'
45
45
 
46
46
  export {
@@ -56,6 +56,20 @@ export {
56
56
  type PivotValueConfig,
57
57
  } from './pivot'
58
58
 
59
+ export { default as SvPivotDesigner } from './SvPivotDesigner.svelte'
60
+ export {
61
+ ALL_AGGREGATORS,
62
+ AGG_LABEL,
63
+ EMPTY_LAYOUT,
64
+ defaultLayoutFor,
65
+ type PivotField,
66
+ type PivotValueChip,
67
+ type PivotFilterChip,
68
+ type PivotLayout,
69
+ type PivotPreset,
70
+ type Well,
71
+ } from './pivot-designer'
72
+
59
73
  export {
60
74
  // Provider plumbing
61
75
  setAIProvider,
package/src/install.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  type PivotResult,
18
18
  } from './pivot'
19
19
 
20
- export type ProAIApi<TData extends RowData> = {
20
+ export type EnterpriseAIApi<TData extends RowData> = {
21
21
  /** Natural-language -> filter + sort plan (and optionally apply it). */
22
22
  filter(query: string, opts?: AIFilterOptions): Promise<AIFilterResult>
23
23
  /** Propose values for empty cells based on a few worked examples. */
@@ -32,7 +32,7 @@ export type ProAIApi<TData extends RowData> = {
32
32
  readonly _rowType?: TData
33
33
  }
34
34
 
35
- export type ProPivotApi<
35
+ export type EnterprisePivotApi<
36
36
  TFeatures extends TableFeatures,
37
37
  TData extends RowData,
38
38
  > = {
@@ -54,7 +54,7 @@ export type ProPivotApi<
54
54
  ): PivotResult<TFeatures>
55
55
  }
56
56
 
57
- export type ProGridApi<
57
+ export type EnterpriseGridApi<
58
58
  TFeatures extends TableFeatures,
59
59
  TData extends RowData,
60
60
  > = SvGridApi<TFeatures, TData> & {
@@ -68,10 +68,10 @@ export type ProGridApi<
68
68
  importData(opts: ImportOptions<TData>): Promise<ImportResult<TData>>
69
69
  /** AI helpers (Pro). All calls route through a consumer-registered
70
70
  * AIProvider via setAIProvider(); no model client is bundled. */
71
- ai: ProAIApi<TData>
71
+ ai: EnterpriseAIApi<TData>
72
72
  /** Pivot table builder (Pro). Pure - returns `{ rows, columns }` you
73
73
  * hand to a separate `<SvGrid>` instance. */
74
- pivot: ProPivotApi<TFeatures, TData>
74
+ pivot: EnterprisePivotApi<TFeatures, TData>
75
75
  }
76
76
 
77
77
  /**
@@ -84,11 +84,11 @@ export type ProGridApi<
84
84
  * the console emits a one-time nudge. Revoked or malformed keys throw on
85
85
  * first call.
86
86
  */
87
- export function installPro<
87
+ export function installEnterprise<
88
88
  TFeatures extends TableFeatures,
89
89
  TData extends RowData,
90
- >(api: SvGridApi<TFeatures, TData>): ProGridApi<TFeatures, TData> {
91
- const pro = api as ProGridApi<TFeatures, TData>
90
+ >(api: SvGridApi<TFeatures, TData>): EnterpriseGridApi<TFeatures, TData> {
91
+ const pro = api as EnterpriseGridApi<TFeatures, TData>
92
92
  pro.exportData = (opts) => exportGrid(pro, opts)
93
93
  pro.print = (opts) => printGrid(pro, opts)
94
94
  pro.importData = (opts) => importData(pro, opts)
package/src/license.ts CHANGED
@@ -8,17 +8,17 @@
8
8
  // ────────────────────────────────────────────────────────────────────
9
9
  // null (no key set) -> soft-gate: watermark + console.log,
10
10
  // feature still runs
11
- // does not start with "SVPRO-" -> throws (programmer error)
11
+ // does not start with "SVENTERPRISE-" -> throws (programmer error)
12
12
  // in REVOKED_KEYS -> throws (revoked / leaked / expired)
13
- // starts with "SVPRO-DEV" or
14
- // "SVPRO-EVAL" -> works; one-time console.info notice
15
- // any other "SVPRO-..." -> works silently (paid production)
13
+ // starts with "SVENTERPRISE-DEV" or
14
+ // "SVENTERPRISE-EVAL" -> works; one-time console.info notice
15
+ // any other "SVENTERPRISE-..." -> works silently (paid production)
16
16
 
17
17
  import { REVOKED_KEYS } from './revoked'
18
18
  import { emitUnlicensedNudge } from './watermark'
19
- import { showUpgradePrompt, type ProFeatureLabel } from './upgrade-prompt'
19
+ import { showUpgradePrompt, type EnterpriseFeatureLabel } from './upgrade-prompt'
20
20
 
21
- const VALID_PREFIX = 'SVPRO-'
21
+ const VALID_PREFIX = 'SVENTERPRISE-'
22
22
  let currentKey: string | null = null
23
23
  let noticedDev = false
24
24
 
@@ -56,7 +56,7 @@ export function hasValidLicense(): boolean {
56
56
  return true
57
57
  }
58
58
 
59
- export function assertProLicensed(feature?: ProFeatureLabel): void {
59
+ export function assertEnterpriseLicensed(feature?: EnterpriseFeatureLabel): void {
60
60
  if (currentKey == null) {
61
61
  // Soft-gate: the feature still runs, but the user gets a watermark +
62
62
  // a one-time console.log nudge directing them to pricing, plus a
@@ -68,7 +68,7 @@ export function assertProLicensed(feature?: ProFeatureLabel): void {
68
68
  }
69
69
  if (!currentKey.startsWith(VALID_PREFIX)) {
70
70
  throw new Error(
71
- '@svgrid/enterprise: invalid license key format (expected "SVPRO-..." prefix).',
71
+ '@svgrid/enterprise: invalid license key format (expected "SVENTERPRISE-..." prefix).',
72
72
  )
73
73
  }
74
74
  if (REVOKED_KEYS.has(currentKey)) {
@@ -79,7 +79,7 @@ export function assertProLicensed(feature?: ProFeatureLabel): void {
79
79
  }
80
80
  if (
81
81
  !noticedDev &&
82
- (currentKey.startsWith('SVPRO-DEV') || currentKey.startsWith('SVPRO-EVAL'))
82
+ (currentKey.startsWith('SVENTERPRISE-DEV') || currentKey.startsWith('SVENTERPRISE-EVAL'))
83
83
  ) {
84
84
  // eslint-disable-next-line no-console
85
85
  console.info(
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Types + helpers for the SvPivotDesigner component.
3
+ *
4
+ * Why a separate module: the component file (.svelte) imports these,
5
+ * and so do consumers wiring up custom presets, formatters, or wells.
6
+ * Keeping the pure-TS surface here also lets us unit-test the layout
7
+ * normalisation + serialisation without instantiating the component.
8
+ */
9
+
10
+ import type { CellFormatConfig } from '@svgrid/grid'
11
+ import type { PivotAggregatorId } from './pivot'
12
+
13
+ /** One field the designer offers in its left-rail picker.
14
+ * Dimensions land in Rows by default; measures land in Values. */
15
+ export type PivotField<T extends Record<string, unknown> = Record<string, unknown>> = {
16
+ field: keyof T & string
17
+ /** Display name shown in the picker + on chips. */
18
+ label: string
19
+ /** Default well: 'dimension' -> Rows, 'measure' -> Values. */
20
+ kind: 'dimension' | 'measure'
21
+ /** Optional group name for the field picker (e.g. 'Demographics'). */
22
+ group?: string
23
+ /** Optional formatter applied to value-well chips by default. */
24
+ format?: CellFormatConfig
25
+ /** Optional default aggregator when added to Values (default 'sum'). */
26
+ defaultAgg?: PivotAggregatorId
27
+ }
28
+
29
+ /** A single chip in the Values well. */
30
+ export type PivotValueChip = {
31
+ field: string
32
+ agg: PivotAggregatorId
33
+ label?: string
34
+ format?: CellFormatConfig
35
+ }
36
+
37
+ /** A single chip in the Filters well. `allowed === null` => all values pass. */
38
+ export type PivotFilterChip = {
39
+ field: string
40
+ /** Distinct values that pass the filter. `null` means no restriction. */
41
+ allowed: string[] | null
42
+ }
43
+
44
+ /** The full pivot layout. The designer is a controlled component that
45
+ * reads + writes this single shape via `bind:layout`. */
46
+ export type PivotLayout = {
47
+ rows: string[]
48
+ cols: string[]
49
+ values: PivotValueChip[]
50
+ filters: PivotFilterChip[]
51
+ /** Hide subtotal rows between groups. Default false. */
52
+ hideSubtotals?: boolean
53
+ /** Hide grand-total row + column. Default false. */
54
+ hideGrandTotals?: boolean
55
+ }
56
+
57
+ /** A saved layout the user can pick from the toolbar's preset menu. */
58
+ export type PivotPreset = {
59
+ name: string
60
+ layout: PivotLayout
61
+ }
62
+
63
+ /** A built-in aggregator option. Consumers can pass a subset to
64
+ * `aggregators` to restrict the chip menu (e.g. 'count' only). */
65
+ export const ALL_AGGREGATORS: PivotAggregatorId[] = [
66
+ 'sum', 'avg', 'min', 'max', 'count', 'countDistinct', 'first', 'last',
67
+ ]
68
+
69
+ /** Empty layout used by Reset + the initial state. */
70
+ export const EMPTY_LAYOUT: PivotLayout = {
71
+ rows: [], cols: [], values: [], filters: [],
72
+ }
73
+
74
+ /** Build an initial layout from a field list - useful when the
75
+ * consumer just wants "throw the first dimension in Rows + first
76
+ * measure in Values" defaults. */
77
+ export function defaultLayoutFor<T extends Record<string, unknown>>(
78
+ fields: ReadonlyArray<PivotField<T>>,
79
+ ): PivotLayout {
80
+ const dim = fields.find((f) => f.kind === 'dimension')
81
+ const meas = fields.find((f) => f.kind === 'measure')
82
+ return {
83
+ rows: dim ? [dim.field] : [],
84
+ cols: [],
85
+ values: meas
86
+ ? [{ field: meas.field, agg: meas.defaultAgg ?? 'sum', label: meas.label, format: meas.format }]
87
+ : [],
88
+ filters: [],
89
+ }
90
+ }
91
+
92
+ /** Move / add / remove a chip across the four wells. The well names
93
+ * match the layout shape. */
94
+ export type Well = 'rows' | 'cols' | 'values' | 'filters'
95
+
96
+ /** Pretty label for an aggregator id. */
97
+ export const AGG_LABEL: Record<PivotAggregatorId, string> = {
98
+ sum: 'Sum', avg: 'Average', min: 'Min', max: 'Max',
99
+ count: 'Count', countDistinct: 'Distinct count',
100
+ first: 'First', last: 'Last',
101
+ }
package/src/pivot.test.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  createPivotModel,
8
8
  filterCollapsedPivotRows,
9
9
  pivotAggregators,
10
+ type PivotConfig,
10
11
  type PivotRow,
11
12
  } from './pivot'
12
13
 
@@ -162,15 +163,42 @@ describe('createPivotModel - configuration toggles', () => {
162
163
  expect(r.rows.find((row) => row.__pivotKind === 'grandTotal')).toBeUndefined()
163
164
  })
164
165
 
165
- it('omits row subtotals when rowSubtotals=false', () => {
166
+ it('keeps group headers but blanks their values when rowSubtotals=false', () => {
166
167
  const r = createPivotModel(facts, {
167
168
  rows: ['region', 'salesPerson'],
168
169
  cols: ['quarter'],
169
170
  values: [{ field: 'amount', agg: 'sum' }],
170
171
  rowSubtotals: false,
171
172
  })
172
- // Only leaves + grand total - no `group` rows.
173
- expect(r.rows.filter((row) => row.__pivotKind === 'group')).toEqual([])
173
+ // Group header rows are STILL emitted - the grouping hierarchy is
174
+ // preserved; turning off subtotals never removes a grouping level.
175
+ const groups = r.rows.filter((row) => row.__pivotKind === 'group')
176
+ expect(groups.length).toBeGreaterThan(0)
177
+ expect(groups.map((row) => row.__pivotLabel)).toContain('AMER')
178
+ // ...but each header carries no aggregate values: the value cells are
179
+ // present (so columns line up) and explicitly null.
180
+ for (const g of groups) {
181
+ const valueKeys = Object.keys(g).filter((k) => !k.startsWith('__pivot'))
182
+ expect(valueKeys.length).toBeGreaterThan(0)
183
+ expect(valueKeys.every((k) => (g as Record<string, unknown>)[k] === null)).toBe(true)
184
+ }
185
+ })
186
+
187
+ it('keeps the grand-total value identical whether or not subtotals show', () => {
188
+ const base: PivotConfig<Sale> = {
189
+ rows: ['region', 'salesPerson'],
190
+ cols: ['quarter'],
191
+ values: [{ field: 'amount', agg: 'sum' }],
192
+ }
193
+ const withSub = createPivotModel(facts, { ...base, rowSubtotals: true })
194
+ const noSub = createPivotModel(facts, { ...base, rowSubtotals: false })
195
+ const totalValues = (m: typeof withSub) => {
196
+ const g = m.rows.find((row) => row.__pivotKind === 'grandTotal')!
197
+ return Object.fromEntries(
198
+ Object.entries(g).filter(([k]) => !k.startsWith('__pivot')),
199
+ )
200
+ }
201
+ expect(totalValues(noSub)).toEqual(totalValues(withSub))
174
202
  })
175
203
 
176
204
  it('honours a custom rowSort comparator', () => {
package/src/pivot.ts CHANGED
@@ -349,6 +349,28 @@ function computeRowValues(
349
349
  return result
350
350
  }
351
351
 
352
+ /**
353
+ * Build the same value-column keys as `computeRowValues`, but with every cell
354
+ * `null`. Used for group HEADER rows when `rowSubtotals` is off: the row keeps
355
+ * its place in the hierarchy (label + expand/collapse) yet shows no aggregate
356
+ * numbers. Cells are `null` rather than absent so consumers can distinguish a
357
+ * deliberately-blank subtotal from a missing column.
358
+ */
359
+ function blankRowValues(
360
+ colLeafPaths: ReadonlyArray<ReadonlyArray<unknown>>,
361
+ values: ReadonlyArray<PivotValueConfig<unknown>>,
362
+ includeGrandTotalCol: boolean,
363
+ ): Record<string, null> {
364
+ const result: Record<string, null> = {}
365
+ for (const colPath of colLeafPaths) {
366
+ for (let i = 0; i < values.length; i += 1) result[leafColumnId(colPath, i)] = null
367
+ }
368
+ if (includeGrandTotalCol) {
369
+ for (let i = 0; i < values.length; i += 1) result[leafColumnId(['__total'], i)] = null
370
+ }
371
+ return result
372
+ }
373
+
352
374
  function walkRowTree(
353
375
  node: AxisNode,
354
376
  out: PivotRow[],
@@ -387,11 +409,16 @@ function walkRowTree(
387
409
  return
388
410
  }
389
411
 
390
- // Group node. Emit a subtotal-style row IF requested AND this node has
391
- // a real path (not the synthetic root) AND it's not at the deepest level.
412
+ // Group node. Emit the group HEADER row whenever this node is a real group
413
+ // (not the synthetic root, not the deepest level). The header is emitted
414
+ // regardless of `rowSubtotals` so the grouping hierarchy and the
415
+ // expand/collapse parent chain stay intact; the flag only controls whether
416
+ // the header carries its aggregate (subtotal) values or renders as a
417
+ // label-only row. This matches Excel / AG Grid, where hiding subtotals never
418
+ // removes a grouping level from the row axis.
392
419
  const isRealGroup = node.level > 0 && node.level < rowFields.length
393
420
  let nextParentId: string | null = parentId
394
- if (isRealGroup && wantSubtotals) {
421
+ if (isRealGroup) {
395
422
  const id = `group__${node.pathKey}`
396
423
  out.push({
397
424
  __pivotId: id,
@@ -400,13 +427,15 @@ function walkRowTree(
400
427
  __pivotLabel: String(node.value ?? '(All)'),
401
428
  __pivotParentId: parentId,
402
429
  __pivotExpandable: true,
403
- ...computeRowValues(
404
- node.rows as ReadonlyArray<Record<string, unknown>>,
405
- colLeafPaths,
406
- colFields,
407
- values,
408
- includeGrandTotalCol,
409
- ),
430
+ ...(wantSubtotals
431
+ ? computeRowValues(
432
+ node.rows as ReadonlyArray<Record<string, unknown>>,
433
+ colLeafPaths,
434
+ colFields,
435
+ values,
436
+ includeGrandTotalCol,
437
+ )
438
+ : blankRowValues(colLeafPaths, values, includeGrandTotalCol)),
410
439
  })
411
440
  // Children of this group point at this row as their parent so a
412
441
  // collapse hides the whole subtree, not just the immediate leaves.
package/src/print.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { RowData, TableFeatures, SvGridApi } from '@svgrid/grid'
2
- import { assertProLicensed } from './license'
2
+ import { assertEnterpriseLicensed } from './license'
3
3
  import { installSmartShim } from './smart-shim'
4
4
  import type { ExportColumn } from './export'
5
5
 
@@ -51,7 +51,7 @@ export async function printGrid<
51
51
  TFeatures extends TableFeatures,
52
52
  TData extends RowData,
53
53
  >(api: SvGridApi<TFeatures, TData>, opts?: PrintOptions<TData>): Promise<void> {
54
- assertProLicensed('Print')
54
+ assertEnterpriseLicensed('Print')
55
55
  const sourceRows: ReadonlyArray<TData> = opts?.rows ?? api.getDisplayedRows()
56
56
  let cols: ReadonlyArray<ExportColumn>
57
57
  if (opts?.columns && opts.columns.length > 0) {
package/src/revoked.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Revoked license keys. A key in this set throws on use even if it has the
2
- // valid SVPRO- prefix. Use this to invalidate keys that have been leaked,
2
+ // valid SVENTERPRISE- prefix. Use this to invalidate keys that have been leaked,
3
3
  // shared outside their seat count, or issued for trials that have expired.
4
4
  //
5
5
  // =============================================================================
@@ -44,6 +44,6 @@
44
44
  // =============================================================================
45
45
 
46
46
  export const REVOKED_KEYS: ReadonlySet<string> = new Set<string>([
47
- // 'SVPRO-EVAL-acme-202506-3F7K9P', // example - expired ACME trial
48
- // 'SVPRO-LEAKED-globex-202612-X8N4P', // example - key leaked on GitHub
47
+ // 'SVENTERPRISE-EVAL-acme-202506-3F7K9P', // example - expired ACME trial
48
+ // 'SVENTERPRISE-LEAKED-globex-202612-X8N4P', // example - key leaked on GitHub
49
49
  ])
@@ -8,7 +8,7 @@
8
8
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
9
9
  import { showUpgradePrompt, dismissUpgradePrompt } from './upgrade-prompt'
10
10
 
11
- const CARD_ATTR = 'data-@svgrid/enterprise-upgrade'
11
+ const CARD_ATTR = 'data-svgrid-enterprise-upgrade'
12
12
 
13
13
  function card(): HTMLElement | null {
14
14
  return document.querySelector(`[${CARD_ATTR}]`)
@@ -17,16 +17,16 @@
17
17
  // - Zero dependencies, inline styles - no CSS file to import, nothing to
18
18
  // bundle, works in any host app regardless of its styling.
19
19
 
20
- const CARD_ATTR = 'data-@svgrid/enterprise-upgrade'
20
+ const CARD_ATTR = 'data-svgrid-enterprise-upgrade'
21
21
  const PRICING_URL = 'https://www.svgrid.com/pricing'
22
22
  // ?ref=in-app lets us measure how many trials start from this exact prompt
23
23
  // vs. the pricing page itself - the whole point of a moment-of-intent CTA.
24
- const TRIAL_URL = 'https://www.svgrid.com/pricing?ref=in-app&utm_source=@svgrid/enterprise&utm_medium=upgrade-prompt'
24
+ const TRIAL_URL = 'https://www.svgrid.com/pricing?ref=in-app&utm_source=svgrid-enterprise&utm_medium=upgrade-prompt'
25
25
 
26
26
  let shownThisSession = false
27
27
 
28
28
  /** Human-readable labels for the Pro surfaces that gate behind a license. */
29
- export type ProFeatureLabel =
29
+ export type EnterpriseFeatureLabel =
30
30
  | 'Export'
31
31
  | 'Import'
32
32
  | 'Print'
@@ -38,7 +38,7 @@ export type ProFeatureLabel =
38
38
  * been shown this session, a card is already up, or we're on the server. Safe
39
39
  * to call on every Pro feature invocation - it self-throttles.
40
40
  */
41
- export function showUpgradePrompt(feature?: ProFeatureLabel | string): void {
41
+ export function showUpgradePrompt(feature?: EnterpriseFeatureLabel | string): void {
42
42
  if (shownThisSession) return
43
43
  if (typeof document === 'undefined' || typeof window === 'undefined') return
44
44
  if (document.querySelector(`[${CARD_ATTR}]`)) return
@@ -72,7 +72,7 @@ function close(card: HTMLElement): void {
72
72
  window.setTimeout(() => card.remove(), 220)
73
73
  }
74
74
 
75
- function buildCard(feature?: ProFeatureLabel | string): HTMLElement {
75
+ function buildCard(feature?: EnterpriseFeatureLabel | string): HTMLElement {
76
76
  const card = document.createElement('div')
77
77
  card.setAttribute(CARD_ATTR, '1')
78
78
  card.setAttribute('role', 'dialog')
@@ -9,7 +9,7 @@
9
9
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
10
10
  import { emitUnlicensedNudge, dismissUnlicensedNudge } from './watermark'
11
11
 
12
- const WATERMARK_ATTR = 'data-@svgrid/enterprise-watermark'
12
+ const WATERMARK_ATTR = 'data-svgrid-enterprise-watermark'
13
13
 
14
14
  function makeGridRoot(): HTMLElement {
15
15
  const el = document.createElement('div')
package/src/watermark.ts CHANGED
@@ -5,10 +5,10 @@
5
5
  // this file is only for the "no key set" case where the feature continues to
6
6
  // work but the user gets nudged.
7
7
 
8
- const WATERMARK_ATTR = 'data-@svgrid/enterprise-watermark'
8
+ const WATERMARK_ATTR = 'data-svgrid-enterprise-watermark'
9
9
  // Marks a grid that has already shown (and faded) its watermark, so the
10
10
  // MutationObserver doesn't re-add it once it fades out and is removed.
11
- const NUDGED_ATTR = 'data-@svgrid/enterprise-nudged'
11
+ const NUDGED_ATTR = 'data-svgrid-enterprise-nudged'
12
12
  const HOMEPAGE_URL = 'https://www.svgrid.com'
13
13
  const LICENSE_URL = 'https://www.svgrid.com/pricing'
14
14
  const WATERMARK_LABEL = 'www.svgrid.com'
@@ -24,7 +24,7 @@ let observer: MutationObserver | null = null
24
24
  export function emitUnlicensedNudge(): void {
25
25
  showConsoleOnce()
26
26
  if (typeof document === 'undefined' || typeof window === 'undefined') return
27
- // Defer one tick: installPro may run inside onApiReady before the grid's
27
+ // Defer one tick: installEnterprise may run inside onApiReady before the grid's
28
28
  // root element is in the DOM. The microtask gives Svelte time to flush.
29
29
  queueMicrotask(() => attachToAllGrids())
30
30
  // Watch for grids that mount later (route changes, dynamic loads).