@cube-nl/dmp-sdk 1.0.0-rc.4 → 1.1.0-rc.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.
Files changed (89) hide show
  1. package/README.md +298 -6
  2. package/dist/app.d.ts +36 -2
  3. package/dist/app.js +192 -10
  4. package/dist/browser.js +6 -1
  5. package/dist/client.d.ts +2 -0
  6. package/dist/cube-sdk.js +179 -2
  7. package/dist/cube-ui.manifest.json +971 -0
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.js +4 -0
  10. package/dist/preview.d.ts +9 -0
  11. package/dist/preview.js +39 -1
  12. package/dist/size.json +6 -0
  13. package/dist/ui/aggregate-block.d.ts +73 -0
  14. package/dist/ui/aggregate-block.js +138 -0
  15. package/dist/ui/attributes.d.ts +5 -0
  16. package/dist/ui/attributes.js +72 -0
  17. package/dist/ui/block.d.ts +63 -0
  18. package/dist/ui/block.js +229 -0
  19. package/dist/ui/blocks/app-shell.d.ts +4 -0
  20. package/dist/ui/blocks/app-shell.js +167 -0
  21. package/dist/ui/blocks/board.d.ts +8 -0
  22. package/dist/ui/blocks/board.js +292 -0
  23. package/dist/ui/blocks/calc.d.ts +9 -0
  24. package/dist/ui/blocks/calc.js +240 -0
  25. package/dist/ui/blocks/chart.d.ts +7 -0
  26. package/dist/ui/blocks/chart.js +166 -0
  27. package/dist/ui/blocks/detail.d.ts +8 -0
  28. package/dist/ui/blocks/detail.js +270 -0
  29. package/dist/ui/blocks/field.d.ts +8 -0
  30. package/dist/ui/blocks/field.js +16 -0
  31. package/dist/ui/blocks/form.d.ts +8 -0
  32. package/dist/ui/blocks/form.js +153 -0
  33. package/dist/ui/blocks/gap-list.d.ts +4 -0
  34. package/dist/ui/blocks/gap-list.js +36 -0
  35. package/dist/ui/blocks/kpi.d.ts +4 -0
  36. package/dist/ui/blocks/kpi.js +74 -0
  37. package/dist/ui/blocks/period-picker.d.ts +4 -0
  38. package/dist/ui/blocks/period-picker.js +155 -0
  39. package/dist/ui/blocks/state.d.ts +4 -0
  40. package/dist/ui/blocks/state.js +135 -0
  41. package/dist/ui/blocks/table.d.ts +8 -0
  42. package/dist/ui/blocks/table.js +607 -0
  43. package/dist/ui/blocks/top.d.ts +4 -0
  44. package/dist/ui/blocks/top.js +67 -0
  45. package/dist/ui/chart-svg.d.ts +28 -0
  46. package/dist/ui/chart-svg.js +111 -0
  47. package/dist/ui/csv.d.ts +9 -0
  48. package/dist/ui/csv.js +55 -0
  49. package/dist/ui/define.d.ts +24 -0
  50. package/dist/ui/define.js +107 -0
  51. package/dist/ui/dom.d.ts +12 -0
  52. package/dist/ui/dom.js +57 -0
  53. package/dist/ui/fields.d.ts +39 -0
  54. package/dist/ui/fields.js +73 -0
  55. package/dist/ui/format.d.ts +44 -0
  56. package/dist/ui/format.js +214 -0
  57. package/dist/ui/inputs.d.ts +35 -0
  58. package/dist/ui/inputs.js +161 -0
  59. package/dist/ui/manifest.d.ts +40 -0
  60. package/dist/ui/manifest.js +878 -0
  61. package/dist/ui/period.d.ts +18 -0
  62. package/dist/ui/period.js +52 -0
  63. package/dist/ui/query.d.ts +26 -0
  64. package/dist/ui/query.js +98 -0
  65. package/dist/ui/schema.d.ts +23 -0
  66. package/dist/ui/schema.js +21 -0
  67. package/dist/ui/styles.d.ts +8 -0
  68. package/dist/ui/styles.js +51 -0
  69. package/dist/ui/texts.d.ts +134 -0
  70. package/dist/ui/texts.js +122 -0
  71. package/dist/ui/theme.d.ts +58 -0
  72. package/dist/ui/theme.js +311 -0
  73. package/dist/ui/write.d.ts +20 -0
  74. package/dist/ui/write.js +54 -0
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/dist/wireframe.d.ts +37 -0
  78. package/dist/wireframe.js +315 -0
  79. package/fonts/OFL-albert-sans.txt +93 -0
  80. package/fonts/OFL-ibm-plex-sans.txt +93 -0
  81. package/fonts/OFL-inter.txt +93 -0
  82. package/fonts/OFL-nunito-sans.txt +93 -0
  83. package/fonts/OFL-source-sans-3.txt +93 -0
  84. package/fonts/albert-sans.woff2 +0 -0
  85. package/fonts/ibm-plex-sans.woff2 +0 -0
  86. package/fonts/inter.woff2 +0 -0
  87. package/fonts/nunito-sans.woff2 +0 -0
  88. package/fonts/source-sans-3.woff2 +0 -0
  89. package/package.json +8 -3
package/dist/index.d.ts CHANGED
@@ -5,4 +5,10 @@ export { buildQuery } from './query.js';
5
5
  export type * from './types.js';
6
6
  export { CubeApp, type ConnectOptions, type CubeAppConfig, type CubeAppSession, type CubeAppStatic, type CubeAppUser, } from './app.js';
7
7
  export type { AppManifest, MockData, MockField, MockResource, MockSchema } from './preview.js';
8
+ export { defineBlocks, type BlockScope, type DefineOptions } from './ui/define.js';
9
+ export { contrastRatio, deriveTheme, FONTS, TOKEN_NAMES } from './ui/theme.js';
10
+ export type { Corners, FontFile, FontId, ResolvedTheme, ThemeFile, ThemeSource, ThemeTokens, ThemeWarning, TokenName, } from './ui/theme.js';
11
+ export { formatValue, type Format } from './ui/format.js';
12
+ export type { FieldInfo, ResourceSchema } from './ui/schema.js';
13
+ export { blockManifest, type AttributeSpec, type BlockManifest, type BlockSpec } from './ui/manifest.js';
8
14
  export { VERSION } from './version.js';
package/dist/index.js CHANGED
@@ -3,4 +3,8 @@ export { bearer, createOAuth, discoverMetadata, memoryKeyValueStore, memoryToken
3
3
  export { DmpError, isDmpError } from './errors.js';
4
4
  export { buildQuery } from './query.js';
5
5
  export { CubeApp, } from './app.js';
6
+ export { defineBlocks } from './ui/define.js';
7
+ export { contrastRatio, deriveTheme, FONTS, TOKEN_NAMES } from './ui/theme.js';
8
+ export { formatValue } from './ui/format.js';
9
+ export { blockManifest } from './ui/manifest.js';
6
10
  export { VERSION } from './version.js';
package/dist/preview.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ResourceClient } from './client.js';
2
2
  import type { DmpRecord } from './types.js';
3
+ import type { ResourceSchema } from './ui/schema.js';
3
4
  /**
4
5
  * One field as `dmp.describe` (and so `cube.mock_data`) writes it: a JSON Schema. `type` is a
5
6
  * name, or a list of names where `'null'` makes it nullable. `_capabilities`' own field shape
@@ -53,6 +54,10 @@ export interface AppManifest {
53
54
  description?: string;
54
55
  uses?: Record<string, readonly string[]>;
55
56
  sdk?: string;
57
+ /** `'wireframe'`: a proposal whose data partly does not exist yet; see `ontbreekt`. */
58
+ soort?: string;
59
+ /** What a wireframe needs that the DMP does not have yet: the gap entries (`readGaps`). */
60
+ ontbreekt?: unknown[];
56
61
  [key: string]: unknown;
57
62
  }
58
63
  /**
@@ -68,7 +73,11 @@ export declare class PreviewStore {
68
73
  constructor(data: MockData, manifest?: AppManifest | null,
69
74
  /** Runs before every call; throw to refuse it (the preview's signed-out state). */
70
75
  guard?: () => void);
76
+ /** The rows a resource holds now; empty for an unknown resource. */
77
+ rows(name: string): readonly DmpRecord[];
71
78
  resourceNames(): string[];
79
+ /** The resource as a block sees it, built from the parsed schema; null for an unknown resource. */
80
+ describe(name: string): ResourceSchema | null;
72
81
  resource<T extends DmpRecord = DmpRecord>(name: string): ResourceClient<T>;
73
82
  /**
74
83
  * Unknown resource first (404, as the REST face answers it), then the manifest: live, the app's
package/dist/preview.js CHANGED
@@ -37,9 +37,42 @@ export class PreviewStore {
37
37
  }
38
38
  }
39
39
  }
40
+ /** The rows a resource holds now; empty for an unknown resource. */
41
+ rows(name) {
42
+ return this.tables.get(name)?.rows ?? [];
43
+ }
40
44
  resourceNames() {
41
45
  return [...this.tables.keys()];
42
46
  }
47
+ /** The resource as a block sees it, built from the parsed schema; null for an unknown resource. */
48
+ describe(name) {
49
+ const table = this.tables.get(name);
50
+ if (table === undefined) {
51
+ return null;
52
+ }
53
+ return {
54
+ name,
55
+ actions: [...table.actions],
56
+ fields: [...table.fields.values()].map((field) => {
57
+ const choices = table.choices.get(field.name);
58
+ return {
59
+ name: field.name,
60
+ type: field.types.join('|'),
61
+ format: field.format,
62
+ nullable: field.nullable,
63
+ readonly: !table.writable.has(field.name),
64
+ enum: choices === undefined ? null : choices.map(String),
65
+ description: field.description,
66
+ };
67
+ }),
68
+ summary: [...table.summary],
69
+ filterable: [...table.filterable],
70
+ sortable: [...table.fields.keys()],
71
+ groupable: [...table.groupable],
72
+ // Live lists the field functions only: a bare count needs no field, so `count` is never offered per field.
73
+ aggregatable: Object.fromEntries([...table.aggregatable].map(([key, fns]) => [key, fns.filter((fn) => fn !== 'count')])),
74
+ };
75
+ }
43
76
  resource(name) {
44
77
  if (!RESOURCE_NAME.test(name)) {
45
78
  throw new TypeError(`'${name}' is not a resource name: cube/api names match [a-z][a-z0-9_]*.`);
@@ -119,7 +152,7 @@ function table(name, resource) {
119
152
  for (const row of fields.size === 0 ? rows : []) {
120
153
  for (const key of Object.keys(row)) {
121
154
  if (!fields.has(key)) {
122
- fields.set(key, { name: key, types: [], nullable: true });
155
+ fields.set(key, { name: key, types: [], nullable: true, format: null, description: null });
123
156
  }
124
157
  }
125
158
  }
@@ -146,6 +179,9 @@ function table(name, resource) {
146
179
  const known = (list) => Array.isArray(list) ? list.filter((item) => typeof item === 'string' && fields.has(item)) : null;
147
180
  return {
148
181
  name,
182
+ actions: Array.isArray(schema.actions)
183
+ ? schema.actions.filter((action) => typeof action === 'string')
184
+ : ['list', 'get', 'create', 'update', 'delete'],
149
185
  fields,
150
186
  filterable: known(schema.filters) ?? names,
151
187
  groupable: known(schema.groupable) ?? names,
@@ -169,6 +205,8 @@ function field(name, definition) {
169
205
  name,
170
206
  types: list.filter((type) => type !== 'null' && type !== ''),
171
207
  nullable: definition.nullable === true || list.includes('null'),
208
+ format: typeof definition.format === 'string' ? definition.format : null,
209
+ description: typeof definition.description === 'string' ? definition.description : null,
172
210
  };
173
211
  }
174
212
  /**
package/dist/size.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "core_gzip": 17364,
3
+ "total_gzip": 43471,
4
+ "ui_gzip": 26107,
5
+ "budget_gzip": 40960
6
+ }
@@ -0,0 +1,73 @@
1
+ import type { CubeAppSession } from '../app.js';
2
+ import type { DmpRecord, DmpResponse, FilterValue } from '../types.js';
3
+ import type { Period } from './period.js';
4
+ import { type PeriodSourceLike } from './query.js';
5
+ /** The frame every aggregate block draws its content in, so a dashboard reads as one set of cards. */
6
+ export declare const CARD_CSS = "\n.card{padding:1rem 1.25rem;border:1px solid var(--cube-color-border);border-radius:var(--cube-radius);\nbackground:var(--cube-color-surface)}\n";
7
+ /** The visible title above a chart or a ranking. */
8
+ export declare const LABEL_CSS = "\n.label{margin:0 0 .5rem;font-weight:600}\n";
9
+ export interface AggregateQueryParts {
10
+ /** The `fn` the API is asked for, and the key of the value in each row: `count`, `sum(total)`. */
11
+ fn: string;
12
+ /** The measured field; null for a row count. */
13
+ field: string | null;
14
+ count: boolean;
15
+ /** The period of the followed picker, or null without one. */
16
+ period: Period | null;
17
+ /** The block's `filter`, narrowed to `range` on the date field when both are there. */
18
+ filterFor(range: {
19
+ from: string;
20
+ to: string;
21
+ } | null): Record<string, FilterValue>;
22
+ }
23
+ /** An attribute the block cannot do without; its absence is explained in the block. */
24
+ export declare function requiredAttr(el: Element, name: string): string;
25
+ /** The fields an aggregate block reads: the measured one, the `by` field (without its truncation), the date field. */
26
+ export declare function aggregateFields(el: Element): string[];
27
+ /** A number from an aggregate row (the API may send decimals as strings); null for nothing or not a number. */
28
+ export declare function toNumber(value: unknown): number | null;
29
+ /**
30
+ * Listens to one period picker at a time and reloads its block on a change. A block that was removed lets go at
31
+ * the next change instead of reloading, so the blocks need no disconnect step of their own for it.
32
+ */
33
+ export declare class PeriodFollower {
34
+ private readonly block;
35
+ private source;
36
+ private readonly onChange;
37
+ constructor(block: {
38
+ readonly isConnected: boolean;
39
+ refresh(): Promise<void>;
40
+ });
41
+ follow(source: PeriodSourceLike | null): void;
42
+ }
43
+ /**
44
+ * Reads `measure`, `filter`, `period` and `date-field` (falling back to the field of a truncated `by`), waits for
45
+ * the picker and follows it. Null when the load was aborted meanwhile: a newer load (or none) owns the block.
46
+ */
47
+ export declare function aggregateQuery(el: HTMLElement, follower: PeriodFollower, signal: AbortSignal, by?: string | null): Promise<AggregateQueryParts | null>;
48
+ /**
49
+ * The aggregate of a grouped block (chart, top) over its period. More groups than the DMP returns in one answer
50
+ * (1000) is refused with a 422; trying again gives the same answer, so the error says what to change instead.
51
+ */
52
+ export declare function groupedAggregate(session: CubeAppSession, resource: string, by: string, query: AggregateQueryParts, signal: AbortSignal): Promise<DmpResponse<DmpRecord[]>>;
53
+ /** A whole number attribute of at least `min`; absent, not a number or too small gives `fallback`. */
54
+ export declare function wholeAttr(el: Element, name: string, min: number, fallback: number): number;
55
+ export interface RankedGroup {
56
+ key: string;
57
+ value: number;
58
+ /** The lookup's label, `#<id>` for an id it has no row for, or the key itself without a lookup. */
59
+ label: string;
60
+ }
61
+ /**
62
+ * The groups of an aggregate ranked by value (`descending` highest first) and cut at `limit`, labelled through the
63
+ * block's `lookup`. A group without a key or without a value is left out. Null when the load was aborted meanwhile.
64
+ */
65
+ export declare function rankGroups(el: Element, session: CubeAppSession, rows: DmpRecord[], keys: {
66
+ by: string;
67
+ fn: string;
68
+ }, order: {
69
+ descending: boolean;
70
+ limit: number;
71
+ }, signal: AbortSignal): Promise<RankedGroup[] | null>;
72
+ /** How the block writes a value: its `format` (default integer for count, else number) and `currency`. */
73
+ export declare function valueFormatter(el: Element, count: boolean): (n: number, compact?: boolean) => string;
@@ -0,0 +1,138 @@
1
+ // What the aggregate blocks (cube-kpi, cube-chart, cube-top) share: their required attributes, the measure and
2
+ // filter they ask with, the period picker they follow and how they write a value.
3
+ import { isDmpError } from '../errors.js';
4
+ import { BlockStateError } from './block.js';
5
+ import { formatValue, parseMeasure } from './format.js';
6
+ import { blockFilter, dateFieldFor, lookupLabels, periodFilter, resolvePeriodSource, } from './query.js';
7
+ import { NL } from './texts.js';
8
+ const FORMATS = ['number', 'integer', 'currency', 'percent'];
9
+ /** The frame every aggregate block draws its content in, so a dashboard reads as one set of cards. */
10
+ export const CARD_CSS = `
11
+ .card{padding:1rem 1.25rem;border:1px solid var(--cube-color-border);border-radius:var(--cube-radius);
12
+ background:var(--cube-color-surface)}
13
+ `;
14
+ /** The visible title above a chart or a ranking. */
15
+ export const LABEL_CSS = `
16
+ .label{margin:0 0 .5rem;font-weight:600}
17
+ `;
18
+ /** An attribute the block cannot do without; its absence is explained in the block. */
19
+ export function requiredAttr(el, name) {
20
+ const value = el.getAttribute(name)?.trim() ?? '';
21
+ if (value === '')
22
+ throw new TypeError(NL.invalid.missing(el.localName, name));
23
+ return value;
24
+ }
25
+ /** The fields an aggregate block reads: the measured one, the `by` field (without its truncation), the date field. */
26
+ export function aggregateFields(el) {
27
+ const measured = /\(([^)]+)\)/.exec(el.getAttribute('measure') ?? '')?.[1]?.trim();
28
+ const by = el.getAttribute('by')?.split('.')[0];
29
+ return [measured, by, el.getAttribute('date-field')].filter((field) => !!field);
30
+ }
31
+ /** A number from an aggregate row (the API may send decimals as strings); null for nothing or not a number. */
32
+ export function toNumber(value) {
33
+ if (typeof value === 'string' && value.trim() === '')
34
+ return null;
35
+ const n = typeof value === 'number' || typeof value === 'string' ? Number(value) : NaN;
36
+ return Number.isFinite(n) ? n : null;
37
+ }
38
+ /**
39
+ * Listens to one period picker at a time and reloads its block on a change. A block that was removed lets go at
40
+ * the next change instead of reloading, so the blocks need no disconnect step of their own for it.
41
+ */
42
+ export class PeriodFollower {
43
+ block;
44
+ source = null;
45
+ onChange = () => {
46
+ if (this.block.isConnected)
47
+ void this.block.refresh();
48
+ else
49
+ this.follow(null);
50
+ };
51
+ constructor(block) {
52
+ this.block = block;
53
+ }
54
+ follow(source) {
55
+ if (source === this.source)
56
+ return;
57
+ this.source?.removeEventListener('cube-change', this.onChange);
58
+ this.source = source;
59
+ source?.addEventListener('cube-change', this.onChange);
60
+ }
61
+ }
62
+ /**
63
+ * Reads `measure`, `filter`, `period` and `date-field` (falling back to the field of a truncated `by`), waits for
64
+ * the picker and follows it. Null when the load was aborted meanwhile: a newer load (or none) owns the block.
65
+ */
66
+ export async function aggregateQuery(el, follower, signal, by = null) {
67
+ const measure = parseMeasure(requiredAttr(el, 'measure'));
68
+ const filter = blockFilter(el);
69
+ const selector = el.getAttribute('period');
70
+ const dateField = selector === null || selector === ''
71
+ ? null
72
+ : dateFieldFor(el.getAttribute('date-field'), by, el.localName);
73
+ const source = await resolvePeriodSource(el, selector);
74
+ if (signal.aborted)
75
+ return null;
76
+ follower.follow(source);
77
+ return {
78
+ fn: measure.field === null ? measure.fn : `${measure.fn}(${measure.field})`,
79
+ field: measure.field,
80
+ count: measure.fn === 'count',
81
+ period: source?.value ?? null,
82
+ filterFor: (range) => range === null || dateField === null ? filter : { ...filter, ...periodFilter(dateField, range) },
83
+ };
84
+ }
85
+ /**
86
+ * The aggregate of a grouped block (chart, top) over its period. More groups than the DMP returns in one answer
87
+ * (1000) is refused with a 422; trying again gives the same answer, so the error says what to change instead.
88
+ */
89
+ export async function groupedAggregate(session, resource, by, query, signal) {
90
+ try {
91
+ return await session
92
+ .resource(resource)
93
+ .aggregate({ fn: query.fn, group: by, filter: query.filterFor(query.period) }, { signal });
94
+ }
95
+ catch (error) {
96
+ if (isDmpError(error) && error.status === 422
97
+ && (error.reason === 'too_many_groups' || error.code === 'cardinality_exceeded')) {
98
+ throw new BlockStateError({ kind: 'error', detail: NL.tooManyGroups, retry: false });
99
+ }
100
+ throw error;
101
+ }
102
+ }
103
+ /** A whole number attribute of at least `min`; absent, not a number or too small gives `fallback`. */
104
+ export function wholeAttr(el, name, min, fallback) {
105
+ const raw = el.getAttribute(name);
106
+ const n = raw === null || raw.trim() === '' ? NaN : Number(raw);
107
+ return Number.isFinite(n) && n >= min ? Math.floor(n) : fallback;
108
+ }
109
+ /**
110
+ * The groups of an aggregate ranked by value (`descending` highest first) and cut at `limit`, labelled through the
111
+ * block's `lookup`. A group without a key or without a value is left out. Null when the load was aborted meanwhile.
112
+ */
113
+ export async function rankGroups(el, session, rows, keys, order, signal) {
114
+ const direction = order.descending ? -1 : 1;
115
+ const ranked = rows
116
+ .filter((row) => row[keys.by] !== null && row[keys.by] !== undefined)
117
+ .map((row) => ({ key: String(row[keys.by]), value: toNumber(row[keys.fn]) }))
118
+ .filter((group) => group.value !== null)
119
+ .sort((a, b) => direction * (a.value - b.value))
120
+ .slice(0, order.limit);
121
+ const lookup = el.getAttribute('lookup');
122
+ const labels = lookup === null || lookup === '' || ranked.length === 0
123
+ ? null
124
+ : await lookupLabels(session, lookup, ranked.map((group) => group.key), signal);
125
+ if (signal.aborted)
126
+ return null;
127
+ return ranked.map((group) => ({
128
+ ...group,
129
+ label: labels === null ? group.key : (labels.get(group.key) ?? `#${group.key}`),
130
+ }));
131
+ }
132
+ /** How the block writes a value: its `format` (default integer for count, else number) and `currency`. */
133
+ export function valueFormatter(el, count) {
134
+ const wanted = el.getAttribute('format');
135
+ const format = (wanted !== null && FORMATS.includes(wanted) ? wanted : count ? 'integer' : 'number');
136
+ const currency = el.getAttribute('currency') || 'EUR';
137
+ return (n, compact) => formatValue(n, format, { currency, compact: compact === true });
138
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The attribute names each block observes, apart from manifest.ts so the browser bundle does not carry the
3
+ * manifest's documentation texts. test/ui-manifest.test.js keeps the two in step.
4
+ */
5
+ export declare const BLOCK_ATTRIBUTES: Readonly<Record<string, readonly string[]>>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The attribute names each block observes, apart from manifest.ts so the browser bundle does not carry the
3
+ * manifest's documentation texts. test/ui-manifest.test.js keeps the two in step.
4
+ */
5
+ export const BLOCK_ATTRIBUTES = {
6
+ 'cube-state': ['kind', 'message', 'retry'],
7
+ 'cube-app-shell': ['name', 'logo', 'logo-alt'],
8
+ 'cube-page': ['name', 'label'],
9
+ 'cube-field': ['name', 'label', 'format', 'multiline', 'options', 'hidden'],
10
+ 'cube-period-picker': ['default', 'label', 'today'],
11
+ 'cube-kpi': [
12
+ 'resource',
13
+ 'measure',
14
+ 'label',
15
+ 'format',
16
+ 'currency',
17
+ 'filter',
18
+ 'period',
19
+ 'date-field',
20
+ 'compare',
21
+ ],
22
+ 'cube-chart': [
23
+ 'resource',
24
+ 'measure',
25
+ 'by',
26
+ 'kind',
27
+ 'label',
28
+ 'format',
29
+ 'currency',
30
+ 'filter',
31
+ 'period',
32
+ 'date-field',
33
+ 'lookup',
34
+ 'height',
35
+ 'limit',
36
+ ],
37
+ 'cube-top': [
38
+ 'resource',
39
+ 'measure',
40
+ 'by',
41
+ 'limit',
42
+ 'order',
43
+ 'label',
44
+ 'format',
45
+ 'currency',
46
+ 'filter',
47
+ 'period',
48
+ 'date-field',
49
+ 'lookup',
50
+ ],
51
+ 'cube-table': [
52
+ 'resource',
53
+ 'fields',
54
+ 'search',
55
+ 'filters',
56
+ 'sort',
57
+ 'page-size',
58
+ 'export',
59
+ 'detail',
60
+ 'filter',
61
+ 'period',
62
+ 'date-field',
63
+ ],
64
+ 'cube-detail': ['resource', 'record-id', 'fields', 'editable'],
65
+ 'cube-form': ['resource', 'fields', 'submit-label', 'success-text'],
66
+ 'cube-calc': ['label'],
67
+ 'cube-input': ['name', 'label', 'type', 'value', 'min', 'max', 'step', 'options'],
68
+ 'cube-result': ['name', 'label', 'format', 'currency', 'emphasis'],
69
+ 'cube-board': ['resource', 'status-field', 'title-field', 'fields', 'sort', 'filter', 'page-size'],
70
+ 'cube-column': ['value', 'label'],
71
+ 'cube-gap-list': ['scherm'],
72
+ };
@@ -0,0 +1,63 @@
1
+ import { type CubeAppSession } from '../app.js';
2
+ import type { BlockScope, DefineOptions } from './define.js';
3
+ export type StateKind = 'loading' | 'empty' | 'error' | 'forbidden' | 'signed-out';
4
+ export interface BlockState {
5
+ kind: StateKind;
6
+ detail: string | null;
7
+ /** False for an error that trying again cannot solve: the state offers no retry. */
8
+ retry?: boolean;
9
+ }
10
+ /** Thrown by a block's load to show exactly this state. */
11
+ export declare class BlockStateError extends Error {
12
+ readonly state: BlockState;
13
+ constructor(state: BlockState);
14
+ }
15
+ /**
16
+ * What a failure looks like to the person on the page. Only a TypeError carries its message: the blocks throw
17
+ * those for a wrong attribute, in Dutch. A server's text is never shown.
18
+ */
19
+ export declare function stateFor(error: unknown): BlockState;
20
+ /**
21
+ * Resolves once the page's own scripts have run. A block must not take the page session before the app had its
22
+ * chance to call `CubeApp.connect()` with its own options, or the block would start a second, default one.
23
+ */
24
+ export declare function pageReady(doc: Document): Promise<void>;
25
+ /** The class every block extends. Declared, not defined: the real one is made per window by `blockBase`. */
26
+ export declare abstract class CubeBlockShape extends HTMLElement {
27
+ static tag: string;
28
+ /** The block's own stylesheet text, adopted after BASE_CSS. */
29
+ static css: string;
30
+ /** False for blocks that render without data (cube-state): they never take the session to render. */
31
+ static needsSession: boolean;
32
+ static observedAttributes: string[];
33
+ /** The block's content root inside its shadow root. */
34
+ readonly body: HTMLElement;
35
+ /** Settles when the current (and any newer) load has finished. */
36
+ readonly updateComplete: Promise<void>;
37
+ /** Loads again: aborts a running load, shows the loading state, then calls `load`. */
38
+ refresh(): Promise<void>;
39
+ connectedCallback(): void;
40
+ disconnectedCallback(): void;
41
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
42
+ /** Renders the block into `body`. Throw to show a state; check `signal` before touching the DOM after an await. */
43
+ protected abstract load(signal: AbortSignal, session: CubeAppSession | null): Promise<void>;
44
+ /** The fields of `resource` the block shows after its load: one of them invented marks the whole block. */
45
+ protected shownFields(): string[];
46
+ /** Whether `field` of `resource` is invented while the resource itself exists: its column is marked. */
47
+ protected exampleField(session: CubeAppSession, resource: string, field: string): boolean;
48
+ /** `Voorbeeld — wordt niet opgeslagen` next to a write action of a wireframe; null in any other app. */
49
+ protected notSaved(session: CubeAppSession): HTMLElement | null;
50
+ protected session(): Promise<CubeAppSession>;
51
+ protected showState(state: StateKind | BlockState, detail?: string | null): void;
52
+ protected attr(name: string): string | null;
53
+ protected flag(name: string): boolean;
54
+ protected num(name: string, fallback: number): number;
55
+ protected json<T>(name: string): T | null;
56
+ protected emit(name: string, detail?: unknown): boolean;
57
+ }
58
+ export type BlockClass = typeof CubeBlockShape;
59
+ /** A block module's export: makes its element class on top of the window's base class. */
60
+ export type BlockFactory = (Base: BlockClass, scope: BlockScope) => BlockClass;
61
+ /** The header mark of an invented field: `Voorbeeld` on screen, the whole of exampleMissing read out. */
62
+ export declare function exampleBadge(doc: Document): HTMLElement;
63
+ export declare function blockBase(scope: BlockScope, options?: DefineOptions): BlockClass;