@cube-nl/dmp-sdk 1.0.0-rc.4 → 1.1.0-rc.2

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 (96) hide show
  1. package/README.md +372 -9
  2. package/dist/app.d.ts +41 -4
  3. package/dist/app.js +252 -14
  4. package/dist/browser.js +14 -1
  5. package/dist/client.d.ts +2 -0
  6. package/dist/client.js +5 -1
  7. package/dist/cube-sdk.js +186 -2
  8. package/dist/cube-ui.manifest.json +971 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +4 -0
  11. package/dist/preview.d.ts +11 -0
  12. package/dist/preview.js +60 -7
  13. package/dist/size.json +6 -0
  14. package/dist/trial.d.ts +32 -0
  15. package/dist/trial.js +55 -0
  16. package/dist/ui/aggregate-block.d.ts +73 -0
  17. package/dist/ui/aggregate-block.js +139 -0
  18. package/dist/ui/attributes.d.ts +5 -0
  19. package/dist/ui/attributes.js +72 -0
  20. package/dist/ui/author-error.d.ts +3 -0
  21. package/dist/ui/author-error.js +3 -0
  22. package/dist/ui/block.d.ts +69 -0
  23. package/dist/ui/block.js +238 -0
  24. package/dist/ui/blocks/app-shell.d.ts +4 -0
  25. package/dist/ui/blocks/app-shell.js +167 -0
  26. package/dist/ui/blocks/board.d.ts +8 -0
  27. package/dist/ui/blocks/board.js +333 -0
  28. package/dist/ui/blocks/calc.d.ts +9 -0
  29. package/dist/ui/blocks/calc.js +263 -0
  30. package/dist/ui/blocks/chart.d.ts +7 -0
  31. package/dist/ui/blocks/chart.js +166 -0
  32. package/dist/ui/blocks/detail.d.ts +8 -0
  33. package/dist/ui/blocks/detail.js +270 -0
  34. package/dist/ui/blocks/field.d.ts +8 -0
  35. package/dist/ui/blocks/field.js +16 -0
  36. package/dist/ui/blocks/form.d.ts +8 -0
  37. package/dist/ui/blocks/form.js +153 -0
  38. package/dist/ui/blocks/gap-list.d.ts +4 -0
  39. package/dist/ui/blocks/gap-list.js +36 -0
  40. package/dist/ui/blocks/kpi.d.ts +4 -0
  41. package/dist/ui/blocks/kpi.js +75 -0
  42. package/dist/ui/blocks/period-picker.d.ts +4 -0
  43. package/dist/ui/blocks/period-picker.js +155 -0
  44. package/dist/ui/blocks/state.d.ts +4 -0
  45. package/dist/ui/blocks/state.js +135 -0
  46. package/dist/ui/blocks/table.d.ts +8 -0
  47. package/dist/ui/blocks/table.js +666 -0
  48. package/dist/ui/blocks/top.d.ts +4 -0
  49. package/dist/ui/blocks/top.js +67 -0
  50. package/dist/ui/chart-svg.d.ts +28 -0
  51. package/dist/ui/chart-svg.js +111 -0
  52. package/dist/ui/csv.d.ts +9 -0
  53. package/dist/ui/csv.js +55 -0
  54. package/dist/ui/define.d.ts +24 -0
  55. package/dist/ui/define.js +107 -0
  56. package/dist/ui/dom.d.ts +12 -0
  57. package/dist/ui/dom.js +58 -0
  58. package/dist/ui/fields.d.ts +39 -0
  59. package/dist/ui/fields.js +73 -0
  60. package/dist/ui/format.d.ts +44 -0
  61. package/dist/ui/format.js +215 -0
  62. package/dist/ui/inputs.d.ts +35 -0
  63. package/dist/ui/inputs.js +161 -0
  64. package/dist/ui/manifest.d.ts +40 -0
  65. package/dist/ui/manifest.js +880 -0
  66. package/dist/ui/period.d.ts +18 -0
  67. package/dist/ui/period.js +52 -0
  68. package/dist/ui/query.d.ts +26 -0
  69. package/dist/ui/query.js +99 -0
  70. package/dist/ui/schema.d.ts +23 -0
  71. package/dist/ui/schema.js +21 -0
  72. package/dist/ui/styles.d.ts +8 -0
  73. package/dist/ui/styles.js +51 -0
  74. package/dist/ui/texts.d.ts +146 -0
  75. package/dist/ui/texts.js +138 -0
  76. package/dist/ui/theme.d.ts +58 -0
  77. package/dist/ui/theme.js +311 -0
  78. package/dist/ui/trial-banner.d.ts +25 -0
  79. package/dist/ui/trial-banner.js +180 -0
  80. package/dist/ui/write.d.ts +20 -0
  81. package/dist/ui/write.js +54 -0
  82. package/dist/version.d.ts +1 -1
  83. package/dist/version.js +1 -1
  84. package/dist/wireframe.d.ts +37 -0
  85. package/dist/wireframe.js +352 -0
  86. package/fonts/OFL-albert-sans.txt +93 -0
  87. package/fonts/OFL-ibm-plex-sans.txt +93 -0
  88. package/fonts/OFL-inter.txt +93 -0
  89. package/fonts/OFL-nunito-sans.txt +93 -0
  90. package/fonts/OFL-source-sans-3.txt +93 -0
  91. package/fonts/albert-sans.woff2 +0 -0
  92. package/fonts/ibm-plex-sans.woff2 +0 -0
  93. package/fonts/inter.woff2 +0 -0
  94. package/fonts/nunito-sans.woff2 +0 -0
  95. package/fonts/source-sans-3.woff2 +0 -0
  96. package/package.json +12 -4
@@ -0,0 +1,36 @@
1
+ import { CARD_CSS } from '../aggregate-block.js';
2
+ import { clear, h } from '../dom.js';
3
+ import { NL } from '../texts.js';
4
+ // One card per gap: a label sits beside its value where there is room and above it where there is not (320 px).
5
+ // role="list" on the <ul>: Safari drops the list role of a list styled without bullets.
6
+ const CSS = `${CARD_CSS}
7
+ ul{list-style:none;margin:0;padding:0;display:grid;gap:.75rem}
8
+ h3{margin:0 0 .5rem;font-size:1rem;overflow-wrap:anywhere}
9
+ dl{margin:0;display:grid;gap:.375rem}
10
+ dl div{display:flex;flex-wrap:wrap;gap:0 .75rem}
11
+ dt{flex:0 0 8rem;font-weight:600}
12
+ dd{flex:1 1 12rem;margin:0;min-width:0;overflow-wrap:break-word}
13
+ p{margin:0}
14
+ `;
15
+ /** `<cube-gap-list>`: what a wireframe shows that the DMP does not have yet, and why, for the people who build it. */
16
+ export function gapListBlock(Base, scope) {
17
+ const doc = scope.document;
18
+ class CubeGapList extends Base {
19
+ static tag = 'cube-gap-list';
20
+ static css = CSS;
21
+ async load(signal, session) {
22
+ if (session === null || signal.aborted)
23
+ return;
24
+ const screen = this.attr('scherm')?.trim() ?? '';
25
+ const gaps = (session.gaps ?? []).filter((gap) => screen === '' || gap.scherm === screen);
26
+ const pair = (label, value) => h(doc, 'div', {}, h(doc, 'dt', {}, label), h(doc, 'dd', {}, value));
27
+ clear(this.body);
28
+ this.body.appendChild(gaps.length === 0
29
+ ? h(doc, 'div', { class: 'card' }, h(doc, 'p', {}, NL.gaps.none))
30
+ : h(doc, 'ul', { role: 'list', 'aria-label': NL.gaps.what }, ...gaps.map((gap) => h(doc, 'li', { class: 'card' }, h(doc, 'h3', {}, gap.veld === undefined ? gap.resource : `${gap.resource}.${gap.veld}`), h(doc, 'dl', {}, pair(NL.gaps.screen, gap.scherm), pair(NL.gaps.type, gap.type === 'keuze'
31
+ ? `${NL.gaps.types.keuze}: ${(gap.waarden ?? []).join(', ')}`
32
+ : NL.gaps.types[gap.type]), pair(NL.gaps.why, gap.reden))))));
33
+ }
34
+ }
35
+ return CubeGapList;
36
+ }
@@ -0,0 +1,4 @@
1
+ import type { BlockClass } from '../block.js';
2
+ import type { BlockScope } from '../define.js';
3
+ /** `<cube-kpi>`: one figure (count, sum, average…) of a resource, optionally over a period and compared. */
4
+ export declare function kpiBlock(Base: BlockClass, scope: BlockScope): BlockClass;
@@ -0,0 +1,75 @@
1
+ import { aggregateFields, aggregateQuery, CARD_CSS, PeriodFollower, requiredAttr, toNumber, valueFormatter, } from '../aggregate-block.js';
2
+ import { AuthorError } from '../author-error.js';
3
+ import { clear, h } from '../dom.js';
4
+ import { NL } from '../texts.js';
5
+ const CHANGE = new Intl.NumberFormat('nl-NL', { minimumFractionDigits: 1, maximumFractionDigits: 1 });
6
+ const CSS = `${CARD_CSS}
7
+ .kpi{display:flex;flex-direction:column;gap:.25rem}
8
+ .label{margin:0;font-size:.875rem;font-weight:600;color:var(--cube-color-text-muted)}
9
+ .value{margin:0;font-size:2rem;font-weight:700;line-height:1.2;overflow-wrap:anywhere}
10
+ .compare{margin:0;font-size:.875rem;color:var(--cube-color-text-muted)}
11
+ .compare.up{color:var(--cube-color-success)}
12
+ .compare.down{color:var(--cube-color-danger)}
13
+ `;
14
+ /** `<cube-kpi>`: one figure (count, sum, average…) of a resource, optionally over a period and compared. */
15
+ export function kpiBlock(Base, scope) {
16
+ class CubeKpi extends Base {
17
+ static tag = 'cube-kpi';
18
+ static css = CSS;
19
+ shownFields() {
20
+ return aggregateFields(this);
21
+ }
22
+ /** The picker this block follows, and so listens to. */
23
+ period = new PeriodFollower(this);
24
+ async load(signal, session) {
25
+ if (session === null)
26
+ return;
27
+ const resource = requiredAttr(this, 'resource');
28
+ if (this.flag('compare') && !this.attr('period'))
29
+ throw new AuthorError(NL.invalid.compare(this.localName));
30
+ const query = await aggregateQuery(this, this.period, signal);
31
+ if (query === null)
32
+ return;
33
+ const { fn, period } = query;
34
+ const api = session.resource(resource);
35
+ const ask = async (range) => {
36
+ const { data } = await api.aggregate({ fn, filter: query.filterFor(range) }, { signal });
37
+ return toNumber(data[0]?.[fn]);
38
+ };
39
+ const before = this.flag('compare') ? (period?.previous ?? null) : null;
40
+ const [current, previous] = await Promise.all([ask(period), before === null ? null : ask(before)]);
41
+ if (signal.aborted)
42
+ return;
43
+ this.render(current, before === null ? undefined : previous, query.count);
44
+ }
45
+ /** `previous` undefined: no comparison asked; null: nothing to compare with. */
46
+ render(current, previous, count) {
47
+ const doc = scope.document;
48
+ const format = valueFormatter(this, count);
49
+ const label = this.attr('label');
50
+ const value = current === null
51
+ ? h(doc, 'p', { class: 'value' }, h(doc, 'span', { 'aria-hidden': 'true' }, NL.emptyValue), h(doc, 'span', { class: 'sr-only' }, NL.noData))
52
+ : h(doc, 'p', { class: 'value' }, h(doc, 'data', { value: String(current) }, format(current)));
53
+ clear(this.body);
54
+ this.body.appendChild(h(doc, 'div', { class: 'card kpi' }, label === null || label === '' ? null : h(doc, 'p', { class: 'label' }, label), value, previous === undefined || current === null ? null : this.compareLine(current, previous)));
55
+ }
56
+ compareLine(current, previous) {
57
+ const doc = scope.document;
58
+ // Nothing (or zero) before: any change would be infinite, so the figure is simply new.
59
+ if (previous === null || previous === 0)
60
+ return h(doc, 'p', { class: 'compare new' }, NL.newValue);
61
+ const change = (current - previous) / Math.abs(previous);
62
+ const percent = CHANGE.format(Math.abs(change) * 100);
63
+ if (percent === CHANGE.format(0)) {
64
+ return h(doc, 'p', { class: 'compare same' }, `${percent}% ${NL.comparedTo}`);
65
+ }
66
+ const up = change > 0;
67
+ return h(doc, 'p', {
68
+ class: up ? 'compare up' : 'compare down',
69
+ role: 'img',
70
+ 'aria-label': NL.change(percent, up ? NL.higher : NL.lower),
71
+ }, `${up ? '▲' : '▼'} ${percent}% ${NL.comparedTo}`);
72
+ }
73
+ }
74
+ return CubeKpi;
75
+ }
@@ -0,0 +1,4 @@
1
+ import type { BlockClass } from '../block.js';
2
+ import type { BlockScope } from '../define.js';
3
+ /** `<cube-period-picker>`: chooses the period that period-aware blocks on the page follow. */
4
+ export declare function periodPickerBlock(Base: BlockClass, scope: BlockScope): BlockClass;
@@ -0,0 +1,155 @@
1
+ import { h } from '../dom.js';
2
+ import { isoDate, parseLocalDate } from '../format.js';
3
+ import { resolvePeriod } from '../period.js';
4
+ import { NL } from '../texts.js';
5
+ const KEYS = ['this-month', '12m', 'this-year', 'custom'];
6
+ const DEFAULT = '12m';
7
+ const CSS = `
8
+ .picker{display:flex;flex-wrap:wrap;align-items:flex-end;gap:.75rem}
9
+ .field{display:flex;flex-direction:column;gap:.25rem;min-width:0}
10
+ label{font-weight:600;font-size:.875rem}
11
+ .custom{display:flex;flex-wrap:wrap;gap:.75rem}
12
+ .custom[hidden]{display:none}
13
+ .custom .error{flex-basis:100%;margin:0;font-weight:600;color:var(--cube-color-danger)}
14
+ .custom .error:empty{flex-basis:0}
15
+ input[aria-invalid=true]{border:2px solid var(--cube-color-danger)}
16
+ @media (max-width:640px){.picker,.custom{flex-direction:column;align-items:stretch}}
17
+ `;
18
+ function field(doc, id, text, control) {
19
+ return h(doc, 'div', { class: 'field' }, h(doc, 'label', { for: id }, text), control);
20
+ }
21
+ /** `<cube-period-picker>`: chooses the period that period-aware blocks on the page follow. */
22
+ export function periodPickerBlock(Base, scope) {
23
+ class CubePeriodPicker extends Base {
24
+ static tag = 'cube-period-picker';
25
+ static css = CSS;
26
+ static needsSession = false;
27
+ select;
28
+ label;
29
+ custom;
30
+ from;
31
+ to;
32
+ error;
33
+ /** The user's choice; until there is one the `default` attribute decides. */
34
+ chosen = null;
35
+ /** The last valid custom range, kept while an input is half-typed. */
36
+ range = null;
37
+ /** The value last made known, to dispatch cube-change only when it changes. */
38
+ announced = null;
39
+ constructor() {
40
+ super();
41
+ const doc = scope.document;
42
+ this.select = h(doc, 'select', { id: 'period', onchange: () => this.onSelect() }, ...KEYS.map((key) => h(doc, 'option', { value: key }, NL.period[key])));
43
+ this.label = h(doc, 'label', { for: 'period' });
44
+ const date = (id) => h(doc, 'input', { type: 'date', id, onchange: () => this.onDates() });
45
+ this.from = date('from');
46
+ this.to = date('to');
47
+ // A polite live region that stays in the page (empty while the range is valid), so a message is read.
48
+ this.error = h(doc, 'p', { id: 'range-error', class: 'error', 'aria-live': 'polite' });
49
+ this.custom = h(doc, 'div', { class: 'custom', hidden: true }, field(doc, 'from', NL.period.from, this.from), field(doc, 'to', NL.period.to, this.to), this.error);
50
+ const picker = h(doc, 'div', { class: 'field' }, this.label, this.select);
51
+ this.body.appendChild(h(doc, 'div', { class: 'picker' }, picker, this.custom));
52
+ }
53
+ /** The chosen period around `today`, with the one before it and that today. */
54
+ get value() {
55
+ const key = this.key();
56
+ const today = this.today();
57
+ const period = key === 'custom' && this.range !== null
58
+ ? resolvePeriod('custom', today, this.range)
59
+ // Custom without dates yet stands for the period it was switched from: 12 months.
60
+ : resolvePeriod(key === 'custom' ? DEFAULT : key, today);
61
+ return { ...period, today: isoDate(today) };
62
+ }
63
+ // Renders from its attributes and the user's choice: no loading state, which would drop the controls.
64
+ // A script that changes `default` or `today` changes the period as much as the user does.
65
+ refresh() {
66
+ this.render();
67
+ this.changed();
68
+ return Promise.resolve();
69
+ }
70
+ async load() {
71
+ this.render();
72
+ }
73
+ showState() {
74
+ this.render();
75
+ }
76
+ key() {
77
+ if (this.chosen !== null)
78
+ return this.chosen;
79
+ const wanted = this.attr('default');
80
+ return KEYS.includes(wanted) ? wanted : DEFAULT;
81
+ }
82
+ today() {
83
+ const raw = this.attr('today');
84
+ return (raw === null ? null : parseLocalDate(raw)) ?? new Date();
85
+ }
86
+ render() {
87
+ const key = this.key();
88
+ this.label.textContent = this.attr('label') ?? NL.period.label;
89
+ this.select.value = key;
90
+ this.custom.hidden = key !== 'custom';
91
+ if (key === 'custom' && this.range === null)
92
+ this.fillDates();
93
+ }
94
+ /** Switching to custom starts from the range that was shown, so the period does not jump. */
95
+ fillDates() {
96
+ const { from, to } = this.value;
97
+ this.range = { from, to };
98
+ this.from.value = from;
99
+ this.to.value = to;
100
+ this.explain(null);
101
+ }
102
+ onSelect() {
103
+ const key = this.select.value;
104
+ if (!KEYS.includes(key))
105
+ return;
106
+ if (key === 'custom' && this.key() !== 'custom') {
107
+ this.range = null;
108
+ this.fillDates();
109
+ }
110
+ this.chosen = key;
111
+ this.render();
112
+ this.changed();
113
+ }
114
+ /** A range that is not a period keeps the last valid one; the inputs say why, nothing is dispatched. */
115
+ onDates() {
116
+ const range = { from: this.from.value, to: this.to.value };
117
+ const from = parseLocalDate(range.from);
118
+ const to = parseLocalDate(range.to);
119
+ if (from === null || to === null) {
120
+ this.explain(NL.period.incompleteDate);
121
+ return;
122
+ }
123
+ if (to < from) {
124
+ this.explain(NL.period.invalidRange);
125
+ return;
126
+ }
127
+ this.explain(null);
128
+ this.range = range;
129
+ this.changed();
130
+ }
131
+ /** Dispatches cube-change when the value differs from the one last made known (not on the first render). */
132
+ changed() {
133
+ const value = this.value;
134
+ const seen = JSON.stringify(value);
135
+ const before = this.announced;
136
+ this.announced = seen;
137
+ if (before !== null && before !== seen)
138
+ this.emit('cube-change', value);
139
+ }
140
+ explain(message) {
141
+ this.error.textContent = message ?? '';
142
+ for (const input of [this.from, this.to]) {
143
+ if (message === null) {
144
+ input.removeAttribute('aria-invalid');
145
+ input.removeAttribute('aria-describedby');
146
+ }
147
+ else {
148
+ input.setAttribute('aria-invalid', 'true');
149
+ input.setAttribute('aria-describedby', this.error.id);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ return CubePeriodPicker;
155
+ }
@@ -0,0 +1,4 @@
1
+ import type { BlockClass } from '../block.js';
2
+ import type { BlockScope } from '../define.js';
3
+ /** `<cube-state>`: renders straight from its attributes, without a load step of its own. */
4
+ export declare function stateBlock(Base: BlockClass, scope: BlockScope): BlockClass;
@@ -0,0 +1,135 @@
1
+ import { clear, h } from '../dom.js';
2
+ import { NL } from '../texts.js';
3
+ const KINDS = ['loading', 'empty', 'error', 'forbidden', 'signed-out'];
4
+ const TEXT = {
5
+ loading: NL.loading,
6
+ empty: NL.empty,
7
+ error: NL.error,
8
+ forbidden: NL.forbidden,
9
+ 'signed-out': NL.signedOut,
10
+ };
11
+ const CSS = `
12
+ .state{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem;padding:1rem;border:1px solid var(--cube-color-border);
13
+ border-radius:var(--cube-radius);background:var(--cube-color-surface)}
14
+ .state[data-kind=error]{border-left:4px solid var(--cube-color-danger)}
15
+ .state[data-kind=forbidden],.state[data-kind=signed-out]{border-left:4px solid var(--cube-color-warning)}
16
+ .state p{margin:0;flex:1 1 12rem}
17
+ .state[data-kind=loading] p,.state[data-kind=empty] p{color:var(--cube-color-text-muted)}
18
+ `;
19
+ /** Kinds that interrupt: of these, only the first one showing in a document is an alert. */
20
+ const URGENT = ['error', 'forbidden', 'signed-out'];
21
+ /** Per document, the cube-state that holds the one urgent alert. A dashboard of blocks must not raise N of them. */
22
+ const alertHolder = new WeakMap();
23
+ /** `<cube-state>`: renders straight from its attributes, without a load step of its own. */
24
+ export function stateBlock(Base, scope) {
25
+ class CubeState extends Base {
26
+ static tag = 'cube-state';
27
+ static css = CSS;
28
+ static needsSession = false;
29
+ /** The sign-in click failed: show the generic error until the attributes change again. */
30
+ failed = false;
31
+ kind = 'loading';
32
+ /** The live region stays in place; its text is set after it is in the page, so it is announced. */
33
+ region;
34
+ message;
35
+ action = null;
36
+ announce = null;
37
+ constructor() {
38
+ super();
39
+ this.message = h(scope.document, 'p');
40
+ this.region = h(scope.document, 'div', { class: 'state' }, this.message);
41
+ }
42
+ disconnectedCallback() {
43
+ super.disconnectedCallback();
44
+ if (alertHolder.get(this.ownerDocument) === this)
45
+ alertHolder.delete(this.ownerDocument);
46
+ if (this.announce !== null)
47
+ scope.clearTimeout(this.announce);
48
+ this.announce = null;
49
+ }
50
+ // No loading flash and no wait for the page: the state is known from the attributes alone.
51
+ refresh() {
52
+ this.failed = false;
53
+ this.render();
54
+ return Promise.resolve();
55
+ }
56
+ async load() {
57
+ this.render();
58
+ }
59
+ // The base would put a cube-state inside this one.
60
+ showState() {
61
+ this.render();
62
+ }
63
+ render() {
64
+ const raw = this.attr('kind');
65
+ const kind = this.failed ? 'error' : KINDS.includes(raw) ? raw : 'loading';
66
+ const doc = this.ownerDocument;
67
+ this.kind = kind;
68
+ const alert = URGENT.includes(kind) && this.claimAlert();
69
+ this.action?.remove();
70
+ this.action = null;
71
+ if (kind === 'signed-out') {
72
+ this.action = h(doc, 'button', { type: 'button', onclick: () => void this.signIn() }, NL.signIn);
73
+ }
74
+ else if (kind === 'error' && (this.failed || this.attr('retry') !== 'false')) {
75
+ this.action = h(doc, 'button', { type: 'button', class: 'secondary', onclick: () => void this.emit('cube-retry') }, NL.retry);
76
+ }
77
+ this.region.setAttribute('data-kind', kind);
78
+ // Loading is not announced: N blocks would each say it once per period change. The host is aria-busy.
79
+ if (kind === 'loading')
80
+ this.region.removeAttribute('role');
81
+ else
82
+ this.region.setAttribute('role', alert ? 'alert' : 'status');
83
+ if (alert)
84
+ this.region.removeAttribute('aria-live');
85
+ else
86
+ this.region.setAttribute('aria-live', kind === 'loading' ? 'off' : 'polite');
87
+ if (this.action !== null)
88
+ this.region.appendChild(this.action);
89
+ if (this.region.parentNode !== this.body) {
90
+ clear(this.body);
91
+ this.body.appendChild(this.region);
92
+ }
93
+ const text = this.failed ? NL.error : (this.attr('message') ?? TEXT[kind]);
94
+ if (this.announce !== null)
95
+ scope.clearTimeout(this.announce);
96
+ this.announce = null;
97
+ if (this.message.textContent === text)
98
+ return;
99
+ if (kind === 'loading') {
100
+ this.message.textContent = text;
101
+ return;
102
+ }
103
+ this.message.textContent = '';
104
+ this.announce = scope.setTimeout(() => {
105
+ this.announce = null;
106
+ this.message.textContent = text;
107
+ }, 0);
108
+ }
109
+ /** True when no other connected cube-state in this document shows an urgent kind first. */
110
+ claimAlert() {
111
+ const doc = this.ownerDocument;
112
+ const holder = alertHolder.get(doc);
113
+ if (holder !== undefined && holder !== this && holder.isConnected && URGENT.includes(holder.kind)) {
114
+ return false;
115
+ }
116
+ alertHolder.set(doc, this);
117
+ return true;
118
+ }
119
+ async signIn() {
120
+ try {
121
+ const session = await this.session();
122
+ await session.signIn();
123
+ }
124
+ catch {
125
+ this.failed = true;
126
+ this.render();
127
+ return;
128
+ }
129
+ // Live, the page has navigated away by now; in the preview every block loads again as the user.
130
+ const View = this.ownerDocument.defaultView?.CustomEvent ?? CustomEvent;
131
+ this.ownerDocument.dispatchEvent(new View('cube-session-change'));
132
+ }
133
+ }
134
+ return CubeState;
135
+ }
@@ -0,0 +1,8 @@
1
+ import { type BlockClass } from '../block.js';
2
+ import type { BlockScope } from '../define.js';
3
+ /**
4
+ * `<cube-table>`: the rows of a resource, a page at a time, with search, filters, sortable headers and a CSV export.
5
+ * The controls are built once per load; what they change only asks for rows again, and only the newest answer is
6
+ * shown.
7
+ */
8
+ export declare function tableBlock(Base: BlockClass, scope: BlockScope): BlockClass;