@table-bro/mcp 0.0.0-stage → 1.2.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/LICENSE +21 -0
- package/README.md +97 -2
- package/dist/docs/docs.d.ts +21 -0
- package/dist/docs/docs.js +103 -0
- package/dist/docs/pages.d.ts +28 -0
- package/dist/docs/pages.js +2734 -0
- package/dist/docs/topics.d.ts +11 -0
- package/dist/docs/topics.js +410 -0
- package/dist/extension.d.ts +23 -0
- package/dist/extension.js +1 -0
- package/dist/generate/angular.d.ts +26 -0
- package/dist/generate/angular.js +241 -0
- package/dist/generate/cells.d.ts +22 -0
- package/dist/generate/cells.js +157 -0
- package/dist/generate/code.d.ts +48 -0
- package/dist/generate/code.js +153 -0
- package/dist/generate/config.d.ts +16 -0
- package/dist/generate/config.js +95 -0
- package/dist/generate/fillings.d.ts +49 -0
- package/dist/generate/fillings.js +130 -0
- package/dist/generate/filters.d.ts +36 -0
- package/dist/generate/filters.js +254 -0
- package/dist/generate/generate-code.d.ts +34 -0
- package/dist/generate/generate-code.js +59 -0
- package/dist/generate/names.d.ts +52 -0
- package/dist/generate/names.js +93 -0
- package/dist/generate/rows.d.ts +5 -0
- package/dist/generate/rows.js +22 -0
- package/dist/generate/scaffold.d.ts +35 -0
- package/dist/generate/scaffold.js +62 -0
- package/dist/generate/service.d.ts +16 -0
- package/dist/generate/service.js +320 -0
- package/dist/generate/setup.d.ts +34 -0
- package/dist/generate/setup.js +158 -0
- package/dist/generate/vanilla.d.ts +10 -0
- package/dist/generate/vanilla.js +160 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +10 -0
- package/dist/project/inspect-project.d.ts +53 -0
- package/dist/project/inspect-project.js +223 -0
- package/dist/server/main.d.ts +2 -0
- package/dist/server/main.js +8 -0
- package/dist/server/server.d.ts +17 -0
- package/dist/server/server.js +212 -0
- package/dist/spec/builtin-specs.d.ts +13 -0
- package/dist/spec/builtin-specs.js +167 -0
- package/dist/spec/css-variables.d.ts +22 -0
- package/dist/spec/css-variables.js +54 -0
- package/dist/spec/schema.d.ts +19 -0
- package/dist/spec/schema.js +278 -0
- package/dist/spec/skin-config.d.ts +30 -0
- package/dist/spec/skin-config.js +67 -0
- package/dist/spec/spec.d.ts +157 -0
- package/dist/spec/spec.js +2 -0
- package/dist/spec/validate-spec.d.ts +54 -0
- package/dist/spec/validate-spec.js +979 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +71 -4
|
@@ -0,0 +1,2734 @@
|
|
|
1
|
+
import { setupSteps } from '../generate/setup.js';
|
|
2
|
+
import { tableBroVersion } from '../version.js';
|
|
3
|
+
/** The languages of the files of code by the languages of the steps of the setup. */
|
|
4
|
+
const setupLanguages = { bash: 'bash', html: 'html', json: 'json', scss: 'scss', typescript: 'ts' };
|
|
5
|
+
/**
|
|
6
|
+
* The setup of a project of one skin, as get_started of the MCP gives it: its steps as an ordered list of the page and
|
|
7
|
+
* their code as the files of code of the page.
|
|
8
|
+
* @param target - an Angular project or Vanilla JS
|
|
9
|
+
* @param skin - the skin of the project
|
|
10
|
+
*/
|
|
11
|
+
function setup(target, skin) {
|
|
12
|
+
const files = [];
|
|
13
|
+
const text = setupSteps(target, skin).map((step, i) => {
|
|
14
|
+
const blocks = [`${i + 1}. **${step.title}**`];
|
|
15
|
+
if (step.text) {
|
|
16
|
+
blocks.push(` ${step.text}`);
|
|
17
|
+
}
|
|
18
|
+
if (step.code) {
|
|
19
|
+
const language = setupLanguages[step.language ?? 'text'] ?? 'text';
|
|
20
|
+
const file = `setup-${skin}-${i + 1}.${language}`;
|
|
21
|
+
files.push({ file, language, code: `${step.code}\n` });
|
|
22
|
+
blocks.push(` {{${file}}}`);
|
|
23
|
+
}
|
|
24
|
+
return blocks.join('\n\n');
|
|
25
|
+
}).join('\n\n');
|
|
26
|
+
return { text, files };
|
|
27
|
+
}
|
|
28
|
+
const setupMat = setup('angular', 'mat');
|
|
29
|
+
const setupBs = setup('vanilla', 'bs');
|
|
30
|
+
const setupNv = setup('vanilla', 'nv');
|
|
31
|
+
const setupNgBs = setup('angular', 'bs');
|
|
32
|
+
const setupNgNv = setup('angular', 'nv');
|
|
33
|
+
/** The files of the setup that every page of Frameworks shows, as each project of samples/frameworks has them. */
|
|
34
|
+
const frameworkInstall = { file: 'install.sh', language: 'bash', code: `npm install @table-bro/core @table-bro/native
|
|
35
|
+
` };
|
|
36
|
+
const frameworkSkin = { file: 'src/skin.ts', language: 'ts', code: `// The CSS of the core and of the skin, then the skin: its import defines <table-bro> and registers the skin nv
|
|
37
|
+
import '@table-bro/core/dist/table-bro.min.css';
|
|
38
|
+
import '@table-bro/native/dist/table-bro.native.min.css';
|
|
39
|
+
import '@table-bro/native';
|
|
40
|
+
|
|
41
|
+
import './sample-page.css';
|
|
42
|
+
` };
|
|
43
|
+
const frameworkOrders = { file: 'src/orders.ts', language: 'ts', code: `import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
44
|
+
|
|
45
|
+
export interface Order extends BroRow {
|
|
46
|
+
description: string;
|
|
47
|
+
price: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const cols: BroCol<Order>[] = [
|
|
51
|
+
{
|
|
52
|
+
title: 'Description',
|
|
53
|
+
name: 'description'
|
|
54
|
+
}, {
|
|
55
|
+
title: 'Price',
|
|
56
|
+
name: 'price',
|
|
57
|
+
dataType: 'number',
|
|
58
|
+
className: 'bro-col_text-right'
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export const rows: Order[] = [
|
|
63
|
+
{
|
|
64
|
+
description: 'Modern Cotton Chair',
|
|
65
|
+
price: 100
|
|
66
|
+
}, {
|
|
67
|
+
description: 'Awesome Concrete Ball',
|
|
68
|
+
price: 200
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export const conf: BroConfig = {
|
|
73
|
+
skin: 'nv',
|
|
74
|
+
toolbarTitle: 'Orders'
|
|
75
|
+
};
|
|
76
|
+
` };
|
|
77
|
+
/** The pages of Docs, in the order of reading. Island Details have their own pages on the site, apart from this package. */
|
|
78
|
+
export const docPages = [
|
|
79
|
+
{
|
|
80
|
+
path: 'getting-started',
|
|
81
|
+
name: 'Getting started',
|
|
82
|
+
text: `<!-- steps -->
|
|
83
|
+
Table, Bro is the Custom Element \`‹table-bro›\`: it takes the columns, the rows and a config with the name of its skin, and renders the table with its toolbar and its paginator.
|
|
84
|
+
|
|
85
|
+
### Angular
|
|
86
|
+
|
|
87
|
+
{{orders.component.ts}}
|
|
88
|
+
|
|
89
|
+
### Vanilla JS
|
|
90
|
+
|
|
91
|
+
{{orders.ts}}
|
|
92
|
+
|
|
93
|
+
<!-- setup -->
|
|
94
|
+
### Table, Bro + Angular Material
|
|
95
|
+
|
|
96
|
+
[See a working page](https://table-bro.com/samples/angular-material)
|
|
97
|
+
|
|
98
|
+
${setupMat.text}
|
|
99
|
+
|
|
100
|
+
### Table, Bro + Vanilla Bootstrap
|
|
101
|
+
|
|
102
|
+
[See a working page](https://table-bro.com/samples/bootstrap)
|
|
103
|
+
|
|
104
|
+
${setupBs.text}
|
|
105
|
+
|
|
106
|
+
### Table, Bro + Vanilla HTML
|
|
107
|
+
|
|
108
|
+
[See a working page](https://table-bro.com/samples/native)
|
|
109
|
+
|
|
110
|
+
${setupNv.text}
|
|
111
|
+
|
|
112
|
+
#### Without a build
|
|
113
|
+
|
|
114
|
+
[See a working page](https://table-bro.com/samples/no-build), [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/no-build)
|
|
115
|
+
|
|
116
|
+
A page without a bundler takes the same packages from [jsDelivr](https://www.jsdelivr.com/), the CDN of npm, at an exact version: the CSS by \`‹link›\`, the modules by an import map, which gives \`@table-bro/core\` to the skin ([MDN, importmap](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap)). \`index.html\`:
|
|
117
|
+
|
|
118
|
+
{{index.html}}
|
|
119
|
+
|
|
120
|
+
\`app.js\` imports the skin and gives the table its columns, rows and config:
|
|
121
|
+
|
|
122
|
+
{{app.js}}`,
|
|
123
|
+
files: [
|
|
124
|
+
{ file: 'index.html', language: 'html', code: `<!doctype html>
|
|
125
|
+
<html lang="en">
|
|
126
|
+
<head>
|
|
127
|
+
<meta charset="UTF-8" />
|
|
128
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
129
|
+
<title>Table, Bro + Vanilla HTML without a build</title>
|
|
130
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@table-bro/core@${tableBroVersion}/dist/table-bro.min.css" />
|
|
131
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@table-bro/native@${tableBroVersion}/dist/table-bro.native.min.css" />
|
|
132
|
+
<!-- The page around the table, as on table-bro.com -->
|
|
133
|
+
<link rel="stylesheet" href="sample-page.css" />
|
|
134
|
+
<script type="importmap">
|
|
135
|
+
{
|
|
136
|
+
"imports": {
|
|
137
|
+
"@table-bro/core": "https://cdn.jsdelivr.net/npm/@table-bro/core@${tableBroVersion}/dist/table-bro.esm.js",
|
|
138
|
+
"@table-bro/native": "https://cdn.jsdelivr.net/npm/@table-bro/native@${tableBroVersion}/dist/table-bro.native.esm.js"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
</script>
|
|
142
|
+
</head>
|
|
143
|
+
<body>
|
|
144
|
+
<script type="module" src="app.js"></script>
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|
|
147
|
+
` },
|
|
148
|
+
{ file: 'app.js', language: 'js', code: `// The import of the skin defines <table-bro> and registers the skin nv
|
|
149
|
+
import '@table-bro/native';
|
|
150
|
+
|
|
151
|
+
const cols = [
|
|
152
|
+
{
|
|
153
|
+
title: 'Description',
|
|
154
|
+
name: 'description'
|
|
155
|
+
}, {
|
|
156
|
+
title: 'Price',
|
|
157
|
+
name: 'price',
|
|
158
|
+
dataType: 'number',
|
|
159
|
+
className: 'bro-col_text-right'
|
|
160
|
+
}
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const rows = [
|
|
164
|
+
{
|
|
165
|
+
description: 'Modern Cotton Chair',
|
|
166
|
+
price: 100
|
|
167
|
+
}, {
|
|
168
|
+
description: 'Awesome Concrete Ball',
|
|
169
|
+
price: 200
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const conf = {
|
|
174
|
+
skin: 'nv',
|
|
175
|
+
toolbarTitle: 'Orders'
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const table = document.createElement('table-bro');
|
|
179
|
+
|
|
180
|
+
table.id = 'orders';
|
|
181
|
+
table.configure({cols, rows, conf});
|
|
182
|
+
table.addEventListener('bro-action', event => console.log(event.detail.action));
|
|
183
|
+
|
|
184
|
+
// The shadows of the table fall into the margins of this element: the visible table takes its full width
|
|
185
|
+
const container = document.createElement('div');
|
|
186
|
+
|
|
187
|
+
container.className = 'bro-table-shadow-container';
|
|
188
|
+
container.append(table);
|
|
189
|
+
document.body.append(container);
|
|
190
|
+
` },
|
|
191
|
+
{ file: 'orders.component.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
|
192
|
+
|
|
193
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
194
|
+
import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
195
|
+
|
|
196
|
+
interface Order extends BroRow {
|
|
197
|
+
description: string;
|
|
198
|
+
price: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@Component({
|
|
202
|
+
selector: 'app-orders',
|
|
203
|
+
template: \`
|
|
204
|
+
<table-bro-ng class="bro-table-shadow-container" tableId="orders"
|
|
205
|
+
[cols]="cols()" [rows]="rows()" [conf]="conf()"></table-bro-ng>\`,
|
|
206
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
207
|
+
imports: [
|
|
208
|
+
TableBroNg
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
export class OrdersComponent {
|
|
212
|
+
cols = signal<BroCol<Order>[]>([
|
|
213
|
+
{
|
|
214
|
+
title: 'Description',
|
|
215
|
+
name: 'description'
|
|
216
|
+
}, {
|
|
217
|
+
title: 'Price',
|
|
218
|
+
name: 'price',
|
|
219
|
+
dataType: 'number',
|
|
220
|
+
className: 'bro-col_text-right'
|
|
221
|
+
}
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
rows = signal<Order[]>([
|
|
225
|
+
{
|
|
226
|
+
description: 'Modern Cotton Chair',
|
|
227
|
+
price: 100
|
|
228
|
+
}, {
|
|
229
|
+
description: 'Awesome Concrete Ball',
|
|
230
|
+
price: 200
|
|
231
|
+
}
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
conf = signal<BroConfig>({
|
|
235
|
+
skin: 'mat',
|
|
236
|
+
toolbarTitle: 'Orders'
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
` },
|
|
240
|
+
{ file: 'orders.ts', language: 'ts', code: `import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
241
|
+
|
|
242
|
+
interface Order extends BroRow {
|
|
243
|
+
description: string;
|
|
244
|
+
price: number;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const cols: BroCol<Order>[] = [
|
|
248
|
+
{
|
|
249
|
+
title: 'Description',
|
|
250
|
+
name: 'description'
|
|
251
|
+
}, {
|
|
252
|
+
title: 'Price',
|
|
253
|
+
name: 'price',
|
|
254
|
+
dataType: 'number',
|
|
255
|
+
className: 'bro-col_text-right'
|
|
256
|
+
}
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const rows: Order[] = [
|
|
260
|
+
{
|
|
261
|
+
description: 'Modern Cotton Chair',
|
|
262
|
+
price: 100
|
|
263
|
+
}, {
|
|
264
|
+
description: 'Awesome Concrete Ball',
|
|
265
|
+
price: 200
|
|
266
|
+
}
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const conf: BroConfig = {
|
|
270
|
+
skin: 'bs', // 'nv' for Vanilla HTML
|
|
271
|
+
toolbarTitle: 'Orders'
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const table = document.createElement('table-bro');
|
|
275
|
+
|
|
276
|
+
table.id = 'orders';
|
|
277
|
+
table.configure({cols, rows, conf});
|
|
278
|
+
|
|
279
|
+
// The shadows of the table fall into the margins of this element: the visible table takes its full width
|
|
280
|
+
const container = document.createElement('div');
|
|
281
|
+
|
|
282
|
+
container.className = 'bro-table-shadow-container';
|
|
283
|
+
container.append(table);
|
|
284
|
+
document.body.append(container);
|
|
285
|
+
` },
|
|
286
|
+
...setupMat.files,
|
|
287
|
+
...setupBs.files,
|
|
288
|
+
...setupNv.files
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
path: 'basics/fixed-row-height',
|
|
293
|
+
name: 'Fixed row height',
|
|
294
|
+
text: `<!-- intro -->
|
|
295
|
+
All body rows of a table have one fixed height, and its header has one too. Give them with the CSS variables of the skin on the table itself, before it renders: the CSS and the script of the table take them together, without a new build of the skin.
|
|
296
|
+
|
|
297
|
+
{{fixed-row-height.css}}
|
|
298
|
+
|
|
299
|
+
#### From the SCSS sources
|
|
300
|
+
|
|
301
|
+
The package of each skin ships its built CSS and its SCSS sources. A build of the SCSS of the skin gives it other heights for all its tables: configure the skin where your SCSS loads it, with \`node_modules/@table-bro/<skin>/styles\` and \`node_modules/@table-bro/core/styles/mappings/production\` among the load paths of Sass.
|
|
302
|
+
|
|
303
|
+
{{fixed-row-height.scss}}`,
|
|
304
|
+
files: [
|
|
305
|
+
{ file: 'fixed-row-height.css', language: 'css', code: `#orders {
|
|
306
|
+
--bro-header-height: 64px;
|
|
307
|
+
--bro-row-height: 64px;
|
|
308
|
+
}
|
|
309
|
+
` },
|
|
310
|
+
{ file: 'fixed-row-height.scss', language: 'scss', code: `// The skin of the project: "table-bro.material", "table-bro.bootstrap" or "table-bro.native"
|
|
311
|
+
@use "table-bro.material" with ($bro-table-header-height: 64px, $bro-table-row-height: 64px);
|
|
312
|
+
` }
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
path: 'basics/fixed-column-width',
|
|
317
|
+
name: 'Fixed column width',
|
|
318
|
+
text: `<!-- intro -->
|
|
319
|
+
The table gives every column its optimal width by itself: \`autoColumnWidth\`, on by default, measures the titles and the values of the columns and shares the width of the table between them.
|
|
320
|
+
|
|
321
|
+
Any column can still have a fixed width. Give the column a class with \`className\` and the width of its cells in your CSS: the table measures the column at this width and keeps it, the other columns share the rest, and a longer value ends with an ellipsis and shows whole in the tooltip.
|
|
322
|
+
|
|
323
|
+
{{fixed-col-width.ts}}
|
|
324
|
+
|
|
325
|
+
{{fixed-col-width.css}}
|
|
326
|
+
|
|
327
|
+
A build from the SCSS sources has the mixin \`bro-text-ellipsis-width\` of the module \`table-bro.api\` for it:
|
|
328
|
+
|
|
329
|
+
{{fixed-col-width.scss}}`,
|
|
330
|
+
files: [
|
|
331
|
+
{ file: 'fixed-col-width.ts', language: 'ts', code: `import type {BroCol, BroRow} from '@table-bro/core';
|
|
332
|
+
|
|
333
|
+
interface Order extends BroRow {
|
|
334
|
+
description: string;
|
|
335
|
+
price: number;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const cols: BroCol<Order>[] = [
|
|
339
|
+
{
|
|
340
|
+
title: 'Description',
|
|
341
|
+
name: 'description',
|
|
342
|
+
className: 'bro-order_description'
|
|
343
|
+
}, {
|
|
344
|
+
title: 'Price',
|
|
345
|
+
name: 'price',
|
|
346
|
+
dataType: 'number',
|
|
347
|
+
className: 'bro-col_text-right'
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
` },
|
|
351
|
+
{ file: 'fixed-col-width.css', language: 'css', code: `.bro-order_description .bro-cell_component {
|
|
352
|
+
width: 400px;
|
|
353
|
+
}
|
|
354
|
+
` },
|
|
355
|
+
{ file: 'fixed-col-width.scss', language: 'scss', code: `@use "table-bro.api" as *;
|
|
356
|
+
|
|
357
|
+
@include bro-text-ellipsis-width('.bro-order_description', 400px);
|
|
358
|
+
` }
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
path: 'basics/auto-detected-column-width',
|
|
363
|
+
name: 'Auto-detected column width',
|
|
364
|
+
text: `<!-- table-basics_auto-detected-column-width -->
|
|
365
|
+
Before its rows come, the table measures the titles of the columns: the header stands at their widths. The rows that come give the columns the widths of their values, and the header moves to them.
|
|
366
|
+
|
|
367
|
+
<!-- auto-column-width -->
|
|
368
|
+
#### Column widths without auto-detection
|
|
369
|
+
|
|
370
|
+
The auto-detection measures the values of a column and gives it the width of the longest one. An application that knows the widths it needs turns it off with \`autoColumnWidth: false\` in the config: the table measures nothing and writes no widths, and your CSS gives every column its width through its class, \`className\` of the column. Give the width with the mixin \`bro-text-ellipsis-width\` of the module \`table-bro.api\`, and the column is as wide as you need: a long value keeps to the one line of its row and ends with an ellipsis.
|
|
371
|
+
|
|
372
|
+
{{auto-column-width.ts}}
|
|
373
|
+
|
|
374
|
+
{{auto-column-width.scss}}
|
|
375
|
+
|
|
376
|
+
A table with a Filter Drawer keeps the auto-detection, \`autoColumnWidth: true\` by default. The drawer declares the width that it leaves the table: the columns take it at once and move with the drawer, and a value that does not fit any more ends with an ellipsis and shows whole in the tooltip. With \`autoColumnWidth: false\` the table follows neither the drawer nor a resize: the widths of your CSS stay as they are.`,
|
|
377
|
+
files: [
|
|
378
|
+
{ file: 'auto-column-width.ts', language: 'ts', code: `import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
379
|
+
|
|
380
|
+
interface Order extends BroRow {
|
|
381
|
+
description: string;
|
|
382
|
+
price: number;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const conf: BroConfig = {
|
|
386
|
+
autoColumnWidth: false
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const cols: BroCol<Order>[] = [
|
|
390
|
+
{
|
|
391
|
+
title: 'Description',
|
|
392
|
+
name: 'description',
|
|
393
|
+
className: 'bro-order_description'
|
|
394
|
+
}, {
|
|
395
|
+
title: 'Price',
|
|
396
|
+
name: 'price',
|
|
397
|
+
dataType: 'number',
|
|
398
|
+
className: 'bro-order_price bro-col_text-right'
|
|
399
|
+
}
|
|
400
|
+
];
|
|
401
|
+
` },
|
|
402
|
+
{ file: 'auto-column-width.scss', language: 'scss', code: `@use "table-bro.api" as *;
|
|
403
|
+
|
|
404
|
+
@include bro-text-ellipsis-width('.bro-order_description', 400px);
|
|
405
|
+
@include bro-text-ellipsis-width('.bro-order_price', 120px);
|
|
406
|
+
` }
|
|
407
|
+
]
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
path: 'basics/tooltip',
|
|
411
|
+
name: 'Truncated text with hover tooltips',
|
|
412
|
+
text: `<!-- table-basics_tooltip -->
|
|
413
|
+
Each truncated table cell shows a tooltip on hover.`,
|
|
414
|
+
files: []
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
path: 'basics/animated-pagination',
|
|
418
|
+
name: 'Animated pagination',
|
|
419
|
+
text: `<!-- table-basics_animated-pagination -->
|
|
420
|
+
When paginating, the table animates all visible rows, with no vertical scrollbar.
|
|
421
|
+
|
|
422
|
+
<!-- animated-pagination-server -->
|
|
423
|
+
##### Animated pagination _with server-side data loading_
|
|
424
|
+
|
|
425
|
+
<!-- animated-pagination-server code -->
|
|
426
|
+
{{animated-pagination_server.ts}}`,
|
|
427
|
+
files: [
|
|
428
|
+
{ file: 'animated-pagination_server.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
429
|
+
|
|
430
|
+
const config: BroConfig = {
|
|
431
|
+
server: true
|
|
432
|
+
};
|
|
433
|
+
` }
|
|
434
|
+
]
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
path: 'basics/animated-page-size',
|
|
438
|
+
name: 'Animated page size change',
|
|
439
|
+
text: `<!-- table-basics_animated-page-size -->
|
|
440
|
+
The table uses animation when the number of visible rows changes.
|
|
441
|
+
|
|
442
|
+
<!-- animated-page-size-server -->
|
|
443
|
+
##### Animated page size change _with server-side data loading_
|
|
444
|
+
|
|
445
|
+
<!-- animated-page-size-server code -->
|
|
446
|
+
{{animated-page-size_server.ts}}`,
|
|
447
|
+
files: [
|
|
448
|
+
{ file: 'animated-page-size_server.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
449
|
+
|
|
450
|
+
const config: BroConfig = {
|
|
451
|
+
server: true
|
|
452
|
+
};
|
|
453
|
+
` }
|
|
454
|
+
]
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
path: 'basics/table-states',
|
|
458
|
+
name: 'Animated table states',
|
|
459
|
+
text: `<!-- intro -->
|
|
460
|
+
The table supports and animates following states: _Loading_, _No data_, _No records found_, _Failed_.
|
|
461
|
+
|
|
462
|
+
The first tables show the states as Lottie animations, as every table of this site with Angular Material and Bootstrap; the Native skin shows its own icons. The tables after them show static icons: the SVG of Bootstrap Icons with Bootstrap and an icon font with Angular Material.
|
|
463
|
+
|
|
464
|
+
<!-- table-state-loading-lottie -->
|
|
465
|
+
#### Loading _with Lottie_
|
|
466
|
+
|
|
467
|
+
State icon and message as a Lottie animation:
|
|
468
|
+
|
|
469
|
+
<!-- table-state-no-data-lottie -->
|
|
470
|
+
#### No data _with Lottie_
|
|
471
|
+
|
|
472
|
+
State icon and message as a Lottie animation:
|
|
473
|
+
|
|
474
|
+
<!-- table-state-no-records-found-lottie -->
|
|
475
|
+
#### No records found _with Lottie_
|
|
476
|
+
|
|
477
|
+
State icon and message as a Lottie animation:
|
|
478
|
+
|
|
479
|
+
<!-- table-state-failed-lottie -->
|
|
480
|
+
#### Failed _with Lottie_
|
|
481
|
+
|
|
482
|
+
State icon and message as a Lottie animation:
|
|
483
|
+
|
|
484
|
+
<!-- lottie -->
|
|
485
|
+
#### Lottie animations
|
|
486
|
+
|
|
487
|
+
Define the icons of the states as Lottie animations (\`statesDark\` are optional):
|
|
488
|
+
|
|
489
|
+
{{lottie-icon.ts}}
|
|
490
|
+
|
|
491
|
+
The Lottie files of these tables, light and dark, come with the package \`@table-bro/core\` in \`dist/lottie\`: \`mat\` for Angular Material and \`bs\` for Bootstrap. An Angular application serves them as its assets, in \`angular.json\`:
|
|
492
|
+
|
|
493
|
+
{{lottie-assets.json}}
|
|
494
|
+
|
|
495
|
+
**Dependencies** (384 kB):
|
|
496
|
+
|
|
497
|
+
{{lottie-dependencies.html}}
|
|
498
|
+
|
|
499
|
+
<!-- table-state-loading -->
|
|
500
|
+
#### Loading _with animated icon_
|
|
501
|
+
|
|
502
|
+
This is the default view displayed when there's no data yet:
|
|
503
|
+
|
|
504
|
+
<!-- table-state-no-data -->
|
|
505
|
+
#### No data _with animated icon_
|
|
506
|
+
|
|
507
|
+
When the data is loaded and it's empty (empty array \`[]\` in \`rows\`):
|
|
508
|
+
|
|
509
|
+
<!-- table-state-no-records-found -->
|
|
510
|
+
#### No records found _with animated icon_
|
|
511
|
+
|
|
512
|
+
When no records are found for the user's search:
|
|
513
|
+
|
|
514
|
+
<!-- table-basics_state-failed -->
|
|
515
|
+
#### Failed _with animated icon_
|
|
516
|
+
|
|
517
|
+
When an error occurs during data loading:
|
|
518
|
+
|
|
519
|
+
<!-- options -->
|
|
520
|
+
#### Other options to configure state icons and messages:
|
|
521
|
+
|
|
522
|
+
1. **Define icons as _HTML_**
|
|
523
|
+
|
|
524
|
+
_SVG_-icons:
|
|
525
|
+
|
|
526
|
+
{{html-icon_bs.ts}}
|
|
527
|
+
|
|
528
|
+
_Emoji_, as the Native skin shows them:
|
|
529
|
+
|
|
530
|
+
{{html-icon_nv.ts}}
|
|
531
|
+
|
|
532
|
+
2. **Define icons as _icon font_**, adding \`.bro-state-icon\` to tag-container:
|
|
533
|
+
|
|
534
|
+
Material icons:
|
|
535
|
+
|
|
536
|
+
{{font-icon_mat.ts}}
|
|
537
|
+
|
|
538
|
+
**Dependencies** (0.364 kB of \`CSS\`, 129 kB of \`woff2\`):
|
|
539
|
+
|
|
540
|
+
{{font-icon_mat-dependencies.html}}`,
|
|
541
|
+
files: [
|
|
542
|
+
{ file: 'html-icon_bs.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
543
|
+
|
|
544
|
+
const config: BroConfig = {
|
|
545
|
+
components: {
|
|
546
|
+
state: {
|
|
547
|
+
states: {
|
|
548
|
+
loading: [
|
|
549
|
+
\`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-rocket-takeoff-fill" viewBox="0 0 16 16">
|
|
550
|
+
<path d="M12.17 9.53c2.307-2.592 3.278-4.684 3.641-6.218.21-.887.214-1.58.16-2.065a3.6 3.6 0 0 0-.108-.563 2 2 0 0 0-.078-.23V.453c-.073-.164-.168-.234-.352-.295a2 2 0 0 0-.16-.045 4 4 0 0 0-.57-.093c-.49-.044-1.19-.03-2.08.188-1.536.374-3.618 1.343-6.161 3.604l-2.4.238h-.006a2.55 2.55 0 0 0-1.524.734L.15 7.17a.512.512 0 0 0 .433.868l1.896-.271c.28-.04.592.013.955.132.232.076.437.16.655.248l.203.083c.196.816.66 1.58 1.275 2.195.613.614 1.376 1.08 2.191 1.277l.082.202c.089.218.173.424.249.657.118.363.172.676.132.956l-.271 1.9a.512.512 0 0 0 .867.433l2.382-2.386c.41-.41.668-.949.732-1.526zm.11-3.699c-.797.8-1.93.961-2.528.362-.598-.6-.436-1.733.361-2.532.798-.799 1.93-.96 2.528-.361s.437 1.732-.36 2.531Z"/>
|
|
551
|
+
<path d="M5.205 10.787a7.6 7.6 0 0 0 1.804 1.352c-1.118 1.007-4.929 2.028-5.054 1.903-.126-.127.737-4.189 1.839-5.18.346.69.837 1.35 1.411 1.925"/>
|
|
552
|
+
</svg>\`, 'Loading'],
|
|
553
|
+
'no-data': [
|
|
554
|
+
\`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-heartbreak-fill" viewBox="0 0 16 16">
|
|
555
|
+
<path d="M8.931.586 7 3l1.5 4-2 3L8 15C22.534 5.396 13.757-2.21 8.931.586M7.358.77 5.5 3 7 7l-1.5 3 1.815 4.537C-6.533 4.96 2.685-2.467 7.358.77"/>
|
|
556
|
+
</svg>\`, 'No data'],
|
|
557
|
+
'not-found': [
|
|
558
|
+
\`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
|
|
559
|
+
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
|
|
560
|
+
</svg>\`, 'No records found'],
|
|
561
|
+
fail: [
|
|
562
|
+
\`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-emoji-neutral-fill" viewBox="0 0 16 16">
|
|
563
|
+
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5m-3 4a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M10 8c-.552 0-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5S10.552 8 10 8"/>
|
|
564
|
+
</svg>\`, 'Failed']
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
` },
|
|
570
|
+
{ file: 'html-icon_nv.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
571
|
+
|
|
572
|
+
const config: BroConfig = {
|
|
573
|
+
components: {
|
|
574
|
+
state: {
|
|
575
|
+
states: {
|
|
576
|
+
loading: ['🚀', 'Loading'],
|
|
577
|
+
'no-data': ['💔', 'No data'],
|
|
578
|
+
'not-found': ['🔦', 'No records found'],
|
|
579
|
+
fail: ['😕', 'Failed']
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
` },
|
|
585
|
+
{ file: 'font-icon_mat.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
586
|
+
|
|
587
|
+
const config: BroConfig = {
|
|
588
|
+
components: {
|
|
589
|
+
fontIcon: {
|
|
590
|
+
html: (materialIcon: string) => \`<span class="material-icons bro-state-icon">\${materialIcon}</span>\`
|
|
591
|
+
},
|
|
592
|
+
state: {
|
|
593
|
+
fontIcon: true,
|
|
594
|
+
states: {
|
|
595
|
+
loading: ['front_loader', 'Loading'],
|
|
596
|
+
'no-data': ['heart_broken', 'No data'],
|
|
597
|
+
'not-found': ['heart_broken', 'No records found'],
|
|
598
|
+
fail: ['smart_toy', 'Failed']
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
` },
|
|
604
|
+
{ file: 'font-icon_mat-dependencies.html', language: 'html', code: `<head>
|
|
605
|
+
<link rel="preconnect" href="https://fonts.gstatic.com">
|
|
606
|
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
607
|
+
</head>
|
|
608
|
+
` },
|
|
609
|
+
{ file: 'lottie-icon.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
610
|
+
|
|
611
|
+
const config: BroConfig = {
|
|
612
|
+
components: {
|
|
613
|
+
state: {
|
|
614
|
+
lottie: true,
|
|
615
|
+
|
|
616
|
+
lottieHTML: (iconSrc: string): string => \`<lottie-player autoplay mode="normal" src="\${iconSrc}"></lottie-player>\`,
|
|
617
|
+
|
|
618
|
+
states: {
|
|
619
|
+
loading: ['lottie/mat/loading.json', 'Loading'],
|
|
620
|
+
'no-data': ['lottie/mat/no-data.json', 'No data'],
|
|
621
|
+
'not-found': ['lottie/mat/no-data.json', 'No records found'],
|
|
622
|
+
fail: ['lottie/mat/fail.json', 'Failed']
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
statesDark: {
|
|
626
|
+
loading: ['lottie/mat/dark/loading.json', 'Loading'],
|
|
627
|
+
'no-data': ['lottie/mat/dark/no-data.json', 'No data'],
|
|
628
|
+
'not-found': ['lottie/mat/dark/no-data.json', 'No records found'],
|
|
629
|
+
fail: ['lottie/mat/dark/fail.json', 'Failed']
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
` },
|
|
635
|
+
{ file: 'lottie-assets.json', language: 'json', code: `"assets": [
|
|
636
|
+
{
|
|
637
|
+
"glob": "**/*",
|
|
638
|
+
"input": "node_modules/@table-bro/core/dist/lottie",
|
|
639
|
+
"output": "lottie"
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
` },
|
|
643
|
+
{ file: 'lottie-dependencies.html', language: 'html', code: `<body>
|
|
644
|
+
<script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
|
|
645
|
+
</body>
|
|
646
|
+
` }
|
|
647
|
+
]
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
path: 'design/css-styling',
|
|
651
|
+
name: 'Supports CSS styling of ‹table›',
|
|
652
|
+
text: `<!-- table-css -->
|
|
653
|
+
Table, Bro uses standard \`‹table›\` CSS styling, allowing you to apply any of your existing \`‹table›\` styles:
|
|
654
|
+
|
|
655
|
+
<!-- table-css code -->
|
|
656
|
+
{{table-css.ts}}
|
|
657
|
+
|
|
658
|
+
<!-- after -->
|
|
659
|
+
Your \`CSS\` of these classes, as of any \`‹table›\` of the page:
|
|
660
|
+
|
|
661
|
+
{{table-css.css}}`,
|
|
662
|
+
files: [
|
|
663
|
+
{ file: 'table-css.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
664
|
+
|
|
665
|
+
const config: BroConfig = {
|
|
666
|
+
css: {
|
|
667
|
+
table: {
|
|
668
|
+
className: '',
|
|
669
|
+
|
|
670
|
+
thead: {
|
|
671
|
+
className: '',
|
|
672
|
+
|
|
673
|
+
tr: {
|
|
674
|
+
className: 'bro-header-row',
|
|
675
|
+
|
|
676
|
+
th: {
|
|
677
|
+
className: 'bro-header-cell'
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
tbody: {
|
|
683
|
+
className: '',
|
|
684
|
+
|
|
685
|
+
tr: {
|
|
686
|
+
className: 'bro-row',
|
|
687
|
+
|
|
688
|
+
td: {
|
|
689
|
+
className: 'bro-cell'
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
shadow: {
|
|
696
|
+
className: 'shadow'
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
` },
|
|
701
|
+
{ file: 'table-css.css', language: 'css', code: `table-bro th.bro-header-cell,
|
|
702
|
+
table-bro td.bro-cell {
|
|
703
|
+
padding: 0 0 0 16px;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
table-bro tr.bro-header-row {
|
|
707
|
+
height: var(--bro-header-height);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
table-bro tr.bro-row {
|
|
711
|
+
height: var(--bro-row-height);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
table-bro table {
|
|
715
|
+
border-color: #dee2e6;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
table-bro.bro-dark-theme table {
|
|
719
|
+
border-color: #495057;
|
|
720
|
+
}
|
|
721
|
+
` }
|
|
722
|
+
]
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
path: 'design/custom-content',
|
|
726
|
+
name: 'Custom content in tables using Custom Elements',
|
|
727
|
+
text: `<!-- table-custom-content -->
|
|
728
|
+
You can use any component inside table cells, creating it as a \`Custom Element\`, or transforming your _Angular/React/Svelte/Vue/[Your framework](https://custom-elements-everywhere.com/)_ components to \`Custom Element\`, e.g.
|
|
729
|
+
|
|
730
|
+
<!-- after -->
|
|
731
|
+
Name your own elements of the cells \`bro-cell-*\`: the library never defines this prefix and leaves it to the application.
|
|
732
|
+
|
|
733
|
+
Switcher-component as a _[Custom Element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)_:
|
|
734
|
+
|
|
735
|
+
{{bro-cell-switch.ts}}
|
|
736
|
+
|
|
737
|
+
and define it in your app and name it in the column of its cells:
|
|
738
|
+
|
|
739
|
+
{{bro-cell-switch_define.ts}}
|
|
740
|
+
|
|
741
|
+
Switcher-component as an _Angular_-component:
|
|
742
|
+
|
|
743
|
+
{{bro-cell-slide-toggle.component.ts}}
|
|
744
|
+
|
|
745
|
+
and define it in your app with \`@angular/elements\`, on the example of the page of the table:
|
|
746
|
+
|
|
747
|
+
{{bro-cell-slide-toggle.component_define.ts}}`,
|
|
748
|
+
files: [
|
|
749
|
+
{ file: 'bro-cell-switch.ts', language: 'ts', code: `import type {BroCellElement, BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
750
|
+
|
|
751
|
+
interface Order extends BroRow {
|
|
752
|
+
description: string;
|
|
753
|
+
price: number;
|
|
754
|
+
action: boolean;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export class BroCellSwitchComponent extends HTMLElement implements BroCellElement<Order> {
|
|
758
|
+
// The table sets the config, the column and the row of the cell before it connects the element
|
|
759
|
+
config?: BroConfig;
|
|
760
|
+
col?: BroCol<Order>;
|
|
761
|
+
row?: Order;
|
|
762
|
+
|
|
763
|
+
connectedCallback(): void {
|
|
764
|
+
const input = document.createElement('input');
|
|
765
|
+
|
|
766
|
+
input.type = 'checkbox';
|
|
767
|
+
input.role = 'switch';
|
|
768
|
+
input.checked = this.getAttribute('checked') === 'true';
|
|
769
|
+
|
|
770
|
+
this.replaceChildren(input);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
` },
|
|
774
|
+
{ file: 'bro-cell-switch_define.ts', language: 'ts', code: `import type {BroCol, BroRow} from '@table-bro/core';
|
|
775
|
+
|
|
776
|
+
import {BroCellSwitchComponent} from './bro-cell-switch';
|
|
777
|
+
|
|
778
|
+
interface Order extends BroRow {
|
|
779
|
+
description: string;
|
|
780
|
+
price: number;
|
|
781
|
+
action: boolean;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
customElements.define('bro-cell-switch', BroCellSwitchComponent);
|
|
785
|
+
|
|
786
|
+
const cols: BroCol<Order>[] = [
|
|
787
|
+
{
|
|
788
|
+
title: 'Description',
|
|
789
|
+
name: 'description'
|
|
790
|
+
}, {
|
|
791
|
+
title: 'Action',
|
|
792
|
+
name: 'action',
|
|
793
|
+
component: {
|
|
794
|
+
selector: 'bro-cell-switch',
|
|
795
|
+
attrs: {
|
|
796
|
+
checked: (_col, row) => row.action
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
];
|
|
801
|
+
` },
|
|
802
|
+
{ file: 'bro-cell-slide-toggle.component.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
|
|
803
|
+
|
|
804
|
+
import {MatSlideToggle} from '@angular/material/slide-toggle';
|
|
805
|
+
import type {BroCellElement, BroCol, BroRow} from '@table-bro/core';
|
|
806
|
+
|
|
807
|
+
interface Order extends BroRow {
|
|
808
|
+
description: string;
|
|
809
|
+
price: number;
|
|
810
|
+
action: boolean;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
@Component({
|
|
814
|
+
selector: 'bro-cell-slide-toggle',
|
|
815
|
+
template: \`
|
|
816
|
+
<mat-slide-toggle [hideIcon]="true" [checked]="row.action"></mat-slide-toggle>\`,
|
|
817
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
818
|
+
imports: [
|
|
819
|
+
MatSlideToggle
|
|
820
|
+
]
|
|
821
|
+
})
|
|
822
|
+
export class BroCellSlideToggleComponent implements BroCellElement<Order> {
|
|
823
|
+
@Input() col!: BroCol<Order>;
|
|
824
|
+
@Input() row!: Order;
|
|
825
|
+
}
|
|
826
|
+
` },
|
|
827
|
+
{ file: 'bro-cell-slide-toggle.component_define.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, inject, Injector, signal} from '@angular/core';
|
|
828
|
+
import {createCustomElement} from '@angular/elements';
|
|
829
|
+
|
|
830
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
831
|
+
import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
832
|
+
|
|
833
|
+
import {BroCellSlideToggleComponent} from './bro-cell-slide-toggle.component';
|
|
834
|
+
|
|
835
|
+
interface Order extends BroRow {
|
|
836
|
+
description: string;
|
|
837
|
+
price: number;
|
|
838
|
+
action: boolean;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@Component({
|
|
842
|
+
selector: 'app-orders',
|
|
843
|
+
template: \`
|
|
844
|
+
<table-bro-ng [cols]="cols()" [rows]="rows()" [conf]="conf()"></table-bro-ng>\`,
|
|
845
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
846
|
+
imports: [
|
|
847
|
+
TableBroNg
|
|
848
|
+
]
|
|
849
|
+
})
|
|
850
|
+
export class OrdersComponent {
|
|
851
|
+
cols = signal<BroCol<Order>[]>([
|
|
852
|
+
{
|
|
853
|
+
title: 'Description',
|
|
854
|
+
name: 'description'
|
|
855
|
+
}, {
|
|
856
|
+
title: 'Action',
|
|
857
|
+
name: 'action',
|
|
858
|
+
component: 'bro-cell-slide-toggle'
|
|
859
|
+
}
|
|
860
|
+
]);
|
|
861
|
+
|
|
862
|
+
rows = signal<Order[] | undefined>(undefined);
|
|
863
|
+
conf = signal<BroConfig>({skin: 'mat'});
|
|
864
|
+
|
|
865
|
+
private injector = inject(Injector);
|
|
866
|
+
|
|
867
|
+
constructor() {
|
|
868
|
+
this.defineCustomElements();
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/** The cells are Custom Elements: the table creates them by their tags, so they are defined before it. */
|
|
872
|
+
private defineCustomElements() {
|
|
873
|
+
if (customElements.get('bro-cell-slide-toggle')) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
customElements.define('bro-cell-slide-toggle', createCustomElement(BroCellSlideToggleComponent, {injector: this.injector}));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
` }
|
|
881
|
+
]
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
path: 'design/shadows',
|
|
885
|
+
name: 'Shadows',
|
|
886
|
+
text: `<!-- table-shadows -->
|
|
887
|
+
Shadows are enabled by default. They can be disabled:
|
|
888
|
+
|
|
889
|
+
<!-- table-shadows code -->
|
|
890
|
+
{{shadows-support.ts}}`,
|
|
891
|
+
files: [
|
|
892
|
+
{ file: 'shadows-support.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
893
|
+
|
|
894
|
+
const config: BroConfig = {
|
|
895
|
+
shadowsEnabled: false
|
|
896
|
+
};
|
|
897
|
+
` }
|
|
898
|
+
]
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
path: 'design/rounded-corners',
|
|
902
|
+
name: 'Rounded corners',
|
|
903
|
+
text: `<!-- table-rounded-corners -->
|
|
904
|
+
The table and the Filter Drawer have the rounded corners of Material 3: 12 px for the table, 16 px for the drawer.
|
|
905
|
+
|
|
906
|
+
Open details split the table into cards, and each card rounds its corners at the spaces between them.
|
|
907
|
+
|
|
908
|
+
<!-- table-rounded-corners-square -->
|
|
909
|
+
##### Square corners
|
|
910
|
+
|
|
911
|
+
A radius of \`0\` keeps the corners square:
|
|
912
|
+
|
|
913
|
+
<!-- table-rounded-corners-square code -->
|
|
914
|
+
Set the CSS variables on the table itself, the element \`‹table-bro›\` or \`tableId\` of the wrapper, and on the container of its Filter Drawer, without a new build of the skin:
|
|
915
|
+
|
|
916
|
+
{{rounded-corners.css}}
|
|
917
|
+
|
|
918
|
+
Or build the skin from its SCSS sources with square corners for all its tables:
|
|
919
|
+
|
|
920
|
+
{{rounded-corners.scss}}`,
|
|
921
|
+
files: [
|
|
922
|
+
{ file: 'rounded-corners.css', language: 'css', code: `#orders,
|
|
923
|
+
table-bro-filter-drawer-container,
|
|
924
|
+
table-bro-filter-drawer-container-ng {
|
|
925
|
+
--bro-corner-radius: 0;
|
|
926
|
+
--bro-filter-drawer-corner-radius: 0;
|
|
927
|
+
}
|
|
928
|
+
` },
|
|
929
|
+
{ file: 'rounded-corners.scss', language: 'scss', code: `// The skin of the project: "table-bro.material", "table-bro.bootstrap" or "table-bro.native"
|
|
930
|
+
@use "table-bro.material" with ($bro-corner-radius: 0, $bro-filter-drawer-corner-radius: 0);
|
|
931
|
+
` }
|
|
932
|
+
]
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
path: 'design/dark-light-mode',
|
|
936
|
+
name: 'Dark/Light mode',
|
|
937
|
+
text: `<!-- table-themes -->
|
|
938
|
+
You can set the value to \`auto\`, and the Table, Bro will follow the system color scheme defined by the \`prefers-color-scheme\` media query.
|
|
939
|
+
|
|
940
|
+
Alternatively, you can set it to \`light\` or \`dark\` to force a specific mode.
|
|
941
|
+
|
|
942
|
+
<!-- table-themes code -->
|
|
943
|
+
{{themes.ts}}`,
|
|
944
|
+
files: [
|
|
945
|
+
{ file: 'themes.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
946
|
+
|
|
947
|
+
const config: BroConfig = {
|
|
948
|
+
theme: 'auto' // 'light' | 'dark'
|
|
949
|
+
};
|
|
950
|
+
` }
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
path: 'design/material-design',
|
|
955
|
+
name: 'Material design',
|
|
956
|
+
text: `<!-- table-material-design -->
|
|
957
|
+
Angular Material skin of Table, Bro supports 100% of Google Material Design [data tables](https://m2.material.io/components/data-tables#specs) specs and integrates all the Angular Material components it depends on.
|
|
958
|
+
|
|
959
|
+
It supports Material 3 (M3) and Angular Material 22: the skin takes the theme of your application, as the components of Angular Material do, and a table in the dark theme takes the dark scheme of the theme.`,
|
|
960
|
+
files: []
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
path: 'design/bootstrap-design',
|
|
964
|
+
name: 'Bootstrap design',
|
|
965
|
+
text: `<!-- table-bootstrap-design -->
|
|
966
|
+
Bootstrap skin of Table, Bro supports 100% of Bootstrap [Tables](https://getbootstrap.com/docs/5.3/content/tables/) specs and integrates all the Bootstrap components it depends on.
|
|
967
|
+
|
|
968
|
+
It supports Bootstrap 5.3.`,
|
|
969
|
+
files: []
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
path: 'details/default-details',
|
|
973
|
+
name: 'Default Details',
|
|
974
|
+
text: `<!-- table-details-default code -->
|
|
975
|
+
{{default-details.ts}}
|
|
976
|
+
|
|
977
|
+
<!-- table-details-default-server -->
|
|
978
|
+
#### Default Details _with server-side data loading_
|
|
979
|
+
|
|
980
|
+
With default \`indeterminate\` progress bar:
|
|
981
|
+
|
|
982
|
+
<!-- table-details-default-server code -->
|
|
983
|
+
{{default-details_server.ts}}
|
|
984
|
+
|
|
985
|
+
<!-- table-details-default-server-determinate -->
|
|
986
|
+
With \`determinate\` progress bar:
|
|
987
|
+
|
|
988
|
+
<!-- table-details-default-server-determinate code -->
|
|
989
|
+
{{default-details_server-determinate.ts}}
|
|
990
|
+
|
|
991
|
+
<!-- emit -->
|
|
992
|
+
Example how to emit _progress status_ from your Details component (e.g. based on your server logic):
|
|
993
|
+
|
|
994
|
+
{{details_server-determinate_emit.ts}}
|
|
995
|
+
|
|
996
|
+
<!-- emit angular -->
|
|
997
|
+
or in case of Details is an Angular-component (registered in your Angular-app as a Custom Element):
|
|
998
|
+
|
|
999
|
+
{{details_server-determinate_emit-angular.ts}}`,
|
|
1000
|
+
files: [
|
|
1001
|
+
{ file: 'default-details.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
1002
|
+
|
|
1003
|
+
const config: BroConfig = {
|
|
1004
|
+
rowDetailsComponent: 'bro-cell-details' // your Details-component as a Custom Element
|
|
1005
|
+
};
|
|
1006
|
+
` },
|
|
1007
|
+
{ file: 'default-details_server.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
1008
|
+
|
|
1009
|
+
const config: BroConfig = {
|
|
1010
|
+
rowDetailsComponent: 'bro-cell-details', // your Details-component as a Custom Element
|
|
1011
|
+
|
|
1012
|
+
rowDetailsServer: true
|
|
1013
|
+
};
|
|
1014
|
+
` },
|
|
1015
|
+
{ file: 'default-details_server-determinate.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
1016
|
+
|
|
1017
|
+
const config: BroConfig = {
|
|
1018
|
+
rowDetailsComponent: 'bro-cell-details', // your Details-component as a Custom Element
|
|
1019
|
+
|
|
1020
|
+
rowDetailsServer: true,
|
|
1021
|
+
rowDetailsProgressMode: 'determinate'
|
|
1022
|
+
};
|
|
1023
|
+
` },
|
|
1024
|
+
{ file: 'details_server-determinate_emit.ts', language: 'ts', code: `import {type BroRow, type BroRowDetailsElement, events} from '@table-bro/core';
|
|
1025
|
+
|
|
1026
|
+
interface Order extends BroRow {
|
|
1027
|
+
description: string;
|
|
1028
|
+
price: number;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export class BroCellDetailsComponent extends HTMLElement implements BroRowDetailsElement<Order> {
|
|
1032
|
+
// The table sets the row before it connects the element, and the attribute server with rowDetailsServer
|
|
1033
|
+
row?: Order;
|
|
1034
|
+
|
|
1035
|
+
private progress: number = 0;
|
|
1036
|
+
// The table moves loaded server details from its aux row into the row of the details: the content renders once
|
|
1037
|
+
private rendered: boolean = false;
|
|
1038
|
+
private timerId?: ReturnType<typeof setInterval>;
|
|
1039
|
+
|
|
1040
|
+
connectedCallback(): void {
|
|
1041
|
+
if (this.rendered) {
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (this.getAttribute('server') !== 'true') {
|
|
1046
|
+
this.render();
|
|
1047
|
+
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Emulates the loading of the details: your server logic reports its progress from 0 to 1
|
|
1052
|
+
this.timerId = setInterval(() => {
|
|
1053
|
+
this.progress = Math.min(1, this.progress + 0.2);
|
|
1054
|
+
|
|
1055
|
+
if (this.progress === 1) {
|
|
1056
|
+
clearInterval(this.timerId);
|
|
1057
|
+
this.render();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
this.emitProgress(this.progress);
|
|
1061
|
+
}, 400);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
disconnectedCallback(): void {
|
|
1065
|
+
clearInterval(this.timerId);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private emitProgress(progress: number): void {
|
|
1069
|
+
this.dispatchEvent(new CustomEvent(events.detailsServerLoadedEvent, {detail: progress}));
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
private render(): void {
|
|
1073
|
+
this.textContent = \`Some details here: \${this.row?.description}\`;
|
|
1074
|
+
this.rendered = true;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Somewhere in your app, before the table:
|
|
1079
|
+
customElements.define('bro-cell-details', BroCellDetailsComponent);
|
|
1080
|
+
` },
|
|
1081
|
+
{ file: 'details_server-determinate_emit-angular.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, EventEmitter, inject, Injector, Input, type OnDestroy, type OnInit, Output, signal} from '@angular/core';
|
|
1082
|
+
import {createCustomElement} from '@angular/elements';
|
|
1083
|
+
|
|
1084
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
1085
|
+
import {type BroCol, type BroConfig, type BroRow, type BroRowDetailsElement, events} from '@table-bro/core';
|
|
1086
|
+
|
|
1087
|
+
interface Order extends BroRow {
|
|
1088
|
+
description: string;
|
|
1089
|
+
price: number;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
@Component({
|
|
1093
|
+
selector: 'bro-cell-order-details',
|
|
1094
|
+
template: \`
|
|
1095
|
+
@if (loaded()) {
|
|
1096
|
+
<div>Some details here: {{ row.description }}</div>
|
|
1097
|
+
}\`,
|
|
1098
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
1099
|
+
})
|
|
1100
|
+
export class BroCellOrderDetailsComponent implements OnInit, OnDestroy, BroRowDetailsElement<Order> {
|
|
1101
|
+
@Input() row!: Order;
|
|
1102
|
+
@Input() server?: string;
|
|
1103
|
+
|
|
1104
|
+
// The Custom Element of the component dispatches the output as the event of its name
|
|
1105
|
+
@Output(events.detailsServerLoadedEvent) progressChange = new EventEmitter<number>();
|
|
1106
|
+
|
|
1107
|
+
loaded = signal(false);
|
|
1108
|
+
|
|
1109
|
+
private progress: number = 0;
|
|
1110
|
+
private timerId?: ReturnType<typeof setInterval>;
|
|
1111
|
+
|
|
1112
|
+
ngOnInit() {
|
|
1113
|
+
if (this.server !== 'true') {
|
|
1114
|
+
this.loaded.set(true);
|
|
1115
|
+
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Emulates the loading of the details: your server logic reports its progress from 0 to 1
|
|
1120
|
+
this.timerId = setInterval(() => {
|
|
1121
|
+
this.progress = Math.min(1, this.progress + 0.2);
|
|
1122
|
+
|
|
1123
|
+
if (this.progress < 1) {
|
|
1124
|
+
this.progressChange.emit(this.progress);
|
|
1125
|
+
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
clearInterval(this.timerId);
|
|
1130
|
+
this.loaded.set(true);
|
|
1131
|
+
|
|
1132
|
+
// The next turn of the event loop: the details render their content before the table measures them
|
|
1133
|
+
setTimeout(() => this.progressChange.emit(1));
|
|
1134
|
+
}, 400);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
ngOnDestroy() {
|
|
1138
|
+
clearInterval(this.timerId);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
@Component({
|
|
1143
|
+
selector: 'app-orders',
|
|
1144
|
+
template: \`
|
|
1145
|
+
<table-bro-ng [cols]="cols()" [rows]="rows()" [conf]="conf()"></table-bro-ng>\`,
|
|
1146
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1147
|
+
imports: [
|
|
1148
|
+
TableBroNg
|
|
1149
|
+
]
|
|
1150
|
+
})
|
|
1151
|
+
export class OrdersComponent {
|
|
1152
|
+
cols = signal<BroCol<Order>[]>([
|
|
1153
|
+
{
|
|
1154
|
+
title: 'Description',
|
|
1155
|
+
name: 'description'
|
|
1156
|
+
}
|
|
1157
|
+
]);
|
|
1158
|
+
|
|
1159
|
+
rows = signal<Order[] | undefined>(undefined);
|
|
1160
|
+
|
|
1161
|
+
conf = signal<BroConfig>({
|
|
1162
|
+
skin: 'mat',
|
|
1163
|
+
rowDetailsComponent: 'bro-cell-details', // your Details-component as a Custom Element
|
|
1164
|
+
|
|
1165
|
+
rowDetailsServer: true,
|
|
1166
|
+
rowDetailsProgressMode: 'determinate'
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
constructor() {
|
|
1170
|
+
// The table creates the details by their tag: the Custom Element is defined before it
|
|
1171
|
+
if (!customElements.get('bro-cell-details')) {
|
|
1172
|
+
customElements.define('bro-cell-details', createCustomElement(BroCellOrderDetailsComponent, {injector: inject(Injector)}));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
` }
|
|
1177
|
+
]
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
path: 'filtering/toolbar-with-search',
|
|
1181
|
+
name: 'Toolbar with Search',
|
|
1182
|
+
text: `<!-- table-toolbar-with-search -->
|
|
1183
|
+
The toolbar starts with the search in every skin. Next to it go your buttons and one menu from \`components.toolbarMain.buttons\`: a button sends its \`eventType\` as the action of the \`bro-action\` event, an item of the menu sends its \`value\`. The tables of this site have the **Filters** button and the menu **More**:
|
|
1184
|
+
|
|
1185
|
+
<!-- table-toolbar-with-search code -->
|
|
1186
|
+
{{toolbar-buttons.ts}}
|
|
1187
|
+
|
|
1188
|
+
In the skin mat the menu is an optional element of the skin, which the application adds with \`withToolbarMenu()\`; the skins bs and nv always have it:
|
|
1189
|
+
|
|
1190
|
+
{{toolbar-buttons_app.config.ts}}
|
|
1191
|
+
|
|
1192
|
+
<!-- table-toolbar-with-search-server -->
|
|
1193
|
+
#### Toolbar with Search _with server-side data loading_
|
|
1194
|
+
|
|
1195
|
+
<!-- toolbar-off -->
|
|
1196
|
+
#### Without the toolbar or the header
|
|
1197
|
+
|
|
1198
|
+
\`toolbar: false\` renders the table without its toolbar, with its title, its search and its buttons. \`header: false\` hides the header of the table, the row of the titles of the columns: the header keeps its place in the DOM, and the widths and the sort of the columns stay as they are.
|
|
1199
|
+
|
|
1200
|
+
{{toolbar-off.ts}}`,
|
|
1201
|
+
files: [
|
|
1202
|
+
{ file: 'toolbar-buttons.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
|
1203
|
+
|
|
1204
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
1205
|
+
import {type BroActionDetail, type BroCol, type BroConfig, type BroRow, events} from '@table-bro/core';
|
|
1206
|
+
|
|
1207
|
+
interface Order extends BroRow {
|
|
1208
|
+
description: string;
|
|
1209
|
+
price: number;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
@Component({
|
|
1213
|
+
selector: 'app-orders',
|
|
1214
|
+
template: \`
|
|
1215
|
+
<table-bro-ng [cols]="cols()" [rows]="rows()" [conf]="conf()"
|
|
1216
|
+
(action)="onAction($event)"></table-bro-ng>\`,
|
|
1217
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1218
|
+
imports: [
|
|
1219
|
+
TableBroNg
|
|
1220
|
+
]
|
|
1221
|
+
})
|
|
1222
|
+
export class OrdersComponent {
|
|
1223
|
+
cols = signal<BroCol<Order>[]>([
|
|
1224
|
+
{
|
|
1225
|
+
title: 'Description',
|
|
1226
|
+
name: 'description'
|
|
1227
|
+
}
|
|
1228
|
+
]);
|
|
1229
|
+
|
|
1230
|
+
rows = signal<Order[] | undefined>(undefined);
|
|
1231
|
+
|
|
1232
|
+
conf = signal<BroConfig>({
|
|
1233
|
+
skin: 'mat',
|
|
1234
|
+
components: {
|
|
1235
|
+
toolbarMain: {
|
|
1236
|
+
buttons: [
|
|
1237
|
+
// A button sends its eventType; in the mat skin it is an icon with a tooltip
|
|
1238
|
+
{buttonTitle: 'Filters', eventType: events.filtersToggleEvent, iconHTML: 'filter_list', buttonTooltip: 'Filters'},
|
|
1239
|
+
// A menu: an item sends its value
|
|
1240
|
+
{
|
|
1241
|
+
buttonTitle: 'More',
|
|
1242
|
+
mode: 'navbar-dropdown',
|
|
1243
|
+
buttonTooltip: 'Your custom menu',
|
|
1244
|
+
options: [
|
|
1245
|
+
{value: 'action-1', title: 'Action', iconHTML: 'filter_1'},
|
|
1246
|
+
{value: 'action-2', title: 'Another action', iconHTML: 'filter_2'},
|
|
1247
|
+
{divider: true},
|
|
1248
|
+
{value: 'action-3', title: 'Something else here', iconHTML: 'filter_3'}
|
|
1249
|
+
]
|
|
1250
|
+
}
|
|
1251
|
+
]
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
onAction({action}: BroActionDetail<Order>) {
|
|
1257
|
+
if (action === 'action-1') {
|
|
1258
|
+
// your action
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
` },
|
|
1263
|
+
{ file: 'toolbar-buttons_app.config.ts', language: 'ts', code: `import type {ApplicationConfig} from '@angular/core';
|
|
1264
|
+
|
|
1265
|
+
import {provideTableBroMatSkin, withToolbarMenu} from '@table-bro/angular-material';
|
|
1266
|
+
|
|
1267
|
+
export const appConfig: ApplicationConfig = {
|
|
1268
|
+
providers: [
|
|
1269
|
+
provideTableBroMatSkin(withToolbarMenu())
|
|
1270
|
+
]
|
|
1271
|
+
};
|
|
1272
|
+
` },
|
|
1273
|
+
{ file: 'toolbar-off.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
1274
|
+
|
|
1275
|
+
const config: BroConfig = {
|
|
1276
|
+
skin: 'mat',
|
|
1277
|
+
toolbar: false, // no toolbar
|
|
1278
|
+
header: false // no row of the titles of the columns
|
|
1279
|
+
};
|
|
1280
|
+
` }
|
|
1281
|
+
]
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
path: 'filtering/custom-user-filters',
|
|
1285
|
+
name: 'Custom user filters',
|
|
1286
|
+
text: `<!-- table-custom-user-filters code -->
|
|
1287
|
+
Your controls outside of the table give it their filters, and your rule \`filtersTransform\` filters the rows by them. In Angular the filters go to the input \`filter\` of the table:
|
|
1288
|
+
|
|
1289
|
+
{{custom-user-filters.ts}}
|
|
1290
|
+
|
|
1291
|
+
In Vanilla JS the page dispatches them to the table:
|
|
1292
|
+
|
|
1293
|
+
{{custom-user-filters_vanilla.ts}}
|
|
1294
|
+
|
|
1295
|
+
<!-- table-custom-user-filters-server -->
|
|
1296
|
+
#### Custom user filters _with server-side data loading_`,
|
|
1297
|
+
files: [
|
|
1298
|
+
{ file: 'custom-user-filters.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
|
1299
|
+
|
|
1300
|
+
import {MatSlideToggle, type MatSlideToggleChange} from '@angular/material/slide-toggle';
|
|
1301
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
1302
|
+
import type {BroCol, BroConfig, BroFilters, BroRow} from '@table-bro/core';
|
|
1303
|
+
|
|
1304
|
+
interface Order extends BroRow {
|
|
1305
|
+
description: string;
|
|
1306
|
+
price: number;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
type OrderFilters = BroFilters<Order, {
|
|
1310
|
+
price50Plus?: boolean;
|
|
1311
|
+
}>;
|
|
1312
|
+
|
|
1313
|
+
@Component({
|
|
1314
|
+
selector: 'app-orders',
|
|
1315
|
+
template: \`
|
|
1316
|
+
<mat-slide-toggle (change)="onTogglePrice50Plus($event)">Price $50+</mat-slide-toggle>
|
|
1317
|
+
|
|
1318
|
+
<table-bro-ng [cols]="cols()" [rows]="rows()" [conf]="conf()"
|
|
1319
|
+
[filter]="filter()"></table-bro-ng>\`,
|
|
1320
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1321
|
+
imports: [
|
|
1322
|
+
MatSlideToggle,
|
|
1323
|
+
TableBroNg
|
|
1324
|
+
]
|
|
1325
|
+
})
|
|
1326
|
+
export class OrdersComponent {
|
|
1327
|
+
cols = signal<BroCol<Order>[]>([
|
|
1328
|
+
{
|
|
1329
|
+
title: 'Description',
|
|
1330
|
+
name: 'description'
|
|
1331
|
+
}, {
|
|
1332
|
+
title: 'Price',
|
|
1333
|
+
name: 'price',
|
|
1334
|
+
dataType: 'number',
|
|
1335
|
+
className: 'bro-col_text-right'
|
|
1336
|
+
}
|
|
1337
|
+
]);
|
|
1338
|
+
|
|
1339
|
+
rows = signal<Order[] | undefined>(undefined);
|
|
1340
|
+
|
|
1341
|
+
conf = signal<BroConfig>({
|
|
1342
|
+
skin: 'mat',
|
|
1343
|
+
// Your rule for the rows by the filters of the input filter
|
|
1344
|
+
filtersTransform: (rows: Order[], filters: OrderFilters) => rows.filter(row => !filters.price50Plus || row.price >= 50)
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
filter = signal<OrderFilters | undefined>(undefined);
|
|
1348
|
+
|
|
1349
|
+
onTogglePrice50Plus(event: MatSlideToggleChange) {
|
|
1350
|
+
this.filter.set({price50Plus: event.checked});
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
` },
|
|
1354
|
+
{ file: 'custom-user-filters_vanilla.ts', language: 'ts', code: `import {type BroFilters, type BroRow, dispatchAction, events} from '@table-bro/core';
|
|
1355
|
+
|
|
1356
|
+
interface Order extends BroRow {
|
|
1357
|
+
description: string;
|
|
1358
|
+
price: number;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
type OrderFilters = BroFilters<Order, {
|
|
1362
|
+
price50Plus?: boolean;
|
|
1363
|
+
}>;
|
|
1364
|
+
|
|
1365
|
+
const table = document.querySelector('table-bro')!;
|
|
1366
|
+
const price50Plus = document.querySelector<HTMLInputElement>('#price-50-plus')!;
|
|
1367
|
+
|
|
1368
|
+
price50Plus.addEventListener('change', () => {
|
|
1369
|
+
const filters: OrderFilters = {price50Plus: price50Plus.checked};
|
|
1370
|
+
|
|
1371
|
+
dispatchAction(table, events.filtersFilterEvent, filters);
|
|
1372
|
+
});
|
|
1373
|
+
` }
|
|
1374
|
+
]
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
path: 'filtering/filter-drawer',
|
|
1378
|
+
name: 'Filter Drawer',
|
|
1379
|
+
text: `<!-- table-filter-drawer -->
|
|
1380
|
+
A drawer with your filters slides out next to the table. The filters live outside of the table. With Angular Material, \`‹table-bro-filter-drawer-container-ng›\` renders the table with the inputs of \`‹table-bro-ng›\` and your filters, its content, in a \`mat-drawer\`; the **Filters** button, which you add to the toolbar, opens it. With Bootstrap, Native, and in Vanilla JS, put the table and your filters with \`slot="filters"\` into \`‹table-bro-filter-drawer-container skin="bs"›\`; the **Filters** button opens it too.
|
|
1381
|
+
|
|
1382
|
+
Any button of your page opens the drawer with its \`toggle()\`.
|
|
1383
|
+
|
|
1384
|
+
<!-- table-filter-drawer code -->
|
|
1385
|
+
{{filter-drawer.ts}}
|
|
1386
|
+
|
|
1387
|
+
<!-- table-filter-drawer-server -->
|
|
1388
|
+
#### Filter Drawer _with server-side data loading_
|
|
1389
|
+
|
|
1390
|
+
In the server mode the table adds the filters of the drawer to its query: the \`bro-filters-server-loaded\` event carries a \`BroQuery\` with them in \`filter\`, together with the search, and with the sort.`,
|
|
1391
|
+
files: [
|
|
1392
|
+
{ file: 'filter-drawer.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
|
1393
|
+
|
|
1394
|
+
import {TableBroFilterDrawerContainerNg} from '@table-bro/angular-material';
|
|
1395
|
+
import {type BroCol, type BroConfig, type BroFilters, type BroRow, events, matchesFilterCondition} from '@table-bro/core';
|
|
1396
|
+
|
|
1397
|
+
interface Order extends BroRow {
|
|
1398
|
+
description: string;
|
|
1399
|
+
price: number;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
type OrderFilters = BroFilters<Order, {
|
|
1403
|
+
priceFrom?: number;
|
|
1404
|
+
priceTo?: number;
|
|
1405
|
+
}>;
|
|
1406
|
+
|
|
1407
|
+
// The filters live outside of the table, in mat-drawer next to it. The component renders <table-bro-ng> with its inputs
|
|
1408
|
+
// and outputs; the Filters button, which you add to the toolbar, opens the drawer; any other button calls its toggle().
|
|
1409
|
+
// With the bs and nv skins and in Vanilla JS put the table and your filters with slot="filters" into <table-bro-filter-drawer-container skin="bs">
|
|
1410
|
+
@Component({
|
|
1411
|
+
selector: 'app-orders',
|
|
1412
|
+
template: \`
|
|
1413
|
+
<table-bro-filter-drawer-container-ng [cols]="cols()" [rows]="rows()" [conf]="conf()"
|
|
1414
|
+
[filter]="filter()">
|
|
1415
|
+
<app-order-filters (changeFilter)="filter.set($event)"></app-order-filters>
|
|
1416
|
+
</table-bro-filter-drawer-container-ng>\`,
|
|
1417
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1418
|
+
imports: [
|
|
1419
|
+
TableBroFilterDrawerContainerNg
|
|
1420
|
+
]
|
|
1421
|
+
})
|
|
1422
|
+
export class OrdersComponent {
|
|
1423
|
+
cols = signal<BroCol<Order>[]>([
|
|
1424
|
+
{
|
|
1425
|
+
title: 'Description',
|
|
1426
|
+
name: 'description'
|
|
1427
|
+
}, {
|
|
1428
|
+
title: 'Price',
|
|
1429
|
+
name: 'price',
|
|
1430
|
+
dataType: 'number',
|
|
1431
|
+
className: 'bro-col_text-right'
|
|
1432
|
+
}
|
|
1433
|
+
]);
|
|
1434
|
+
|
|
1435
|
+
rows = signal<Order[] | undefined>(undefined);
|
|
1436
|
+
|
|
1437
|
+
conf = signal<BroConfig>({
|
|
1438
|
+
skin: 'mat',
|
|
1439
|
+
components: {
|
|
1440
|
+
toolbarMain: {
|
|
1441
|
+
buttons: [
|
|
1442
|
+
// The button that opens the drawer; in the mat skin it is an icon with a tooltip
|
|
1443
|
+
{buttonTitle: 'Filters', eventType: events.filtersToggleEvent, iconHTML: 'filter_list', buttonTooltip: 'Filters'}
|
|
1444
|
+
]
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
// Local mode: your rule for the rows. In the server mode the filters come in \`query.filter\` of the load event
|
|
1448
|
+
filtersTransform: (rows: Order[], filters: OrderFilters) => rows.filter(row => {
|
|
1449
|
+
return matchesFilterCondition(row.price, {gte: filters.priceFrom, lte: filters.priceTo});
|
|
1450
|
+
})
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// A cleared filter goes as an empty value: {priceFrom: undefined}
|
|
1454
|
+
filter = signal<OrderFilters | undefined>(undefined);
|
|
1455
|
+
}
|
|
1456
|
+
` }
|
|
1457
|
+
]
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
path: 'filtering/custom-sorting-rules',
|
|
1461
|
+
name: 'Custom sorting rules',
|
|
1462
|
+
text: `<!-- table-custom-sorting-rules -->
|
|
1463
|
+
You can specify a \`sortKey\` for each column to control the sorting behavior.
|
|
1464
|
+
|
|
1465
|
+
For example, the "Date" column can display a formatted date (model's \`date\`), while sorting is performed based on the model's underlying \`timestamp\` field.
|
|
1466
|
+
|
|
1467
|
+
<!-- table-custom-sorting-rules code -->
|
|
1468
|
+
{{custom-sorting-rules.ts}}
|
|
1469
|
+
|
|
1470
|
+
<!-- table-custom-sorting-rules-server -->
|
|
1471
|
+
#### Custom sorting rules _with server-side data loading_`,
|
|
1472
|
+
files: [
|
|
1473
|
+
{ file: 'custom-sorting-rules.ts', language: 'ts', code: `import type {BroCol, BroRow} from '@table-bro/core';
|
|
1474
|
+
|
|
1475
|
+
interface Order extends BroRow {
|
|
1476
|
+
date: string; // the formatted date that the column shows
|
|
1477
|
+
timestamp: number; // the date that the column sorts by
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const cols: BroCol<Order>[] = [
|
|
1481
|
+
{
|
|
1482
|
+
title: 'Date',
|
|
1483
|
+
name: 'date',
|
|
1484
|
+
sortKey: 'timestamp'
|
|
1485
|
+
}
|
|
1486
|
+
];
|
|
1487
|
+
` }
|
|
1488
|
+
]
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
path: 'frameworks',
|
|
1492
|
+
name: 'Frameworks',
|
|
1493
|
+
text: `<!-- intro -->
|
|
1494
|
+
Table, Bro is the Custom Element \`‹table-bro›\`: every framework that renders HTML elements renders it, without a wrapper package. A page gives the table its columns, rows and config by \`configure({cols, rows, conf})\`, listens to its \`bro-action\` events and sets its \`theme\` attribute; each framework does it in its own way. The pages of this section show it in Angular, whose package \`@table-bro/angular\` gives the component \`‹table-bro-ng›\` with inputs and outputs, and in Preact, React, Solid, Svelte and Vue, each with a working page of its sample project in the repository and StackBlitz. How each framework passes data and events to Custom Elements is tested by [Custom Elements Everywhere](https://custom-elements-everywhere.com/).
|
|
1495
|
+
|
|
1496
|
+
<!-- polymer -->
|
|
1497
|
+
#### Polymer and Lit
|
|
1498
|
+
|
|
1499
|
+
Polymer and Lit build Custom Elements themselves, so their templates render \`‹table-bro›\` as any HTML element, without a wrapper: the code of the element calls \`configure()\` of the table and listens to its \`bro-action\` events as of any element ([Polymer, Custom element concepts](https://polymer-library.polymer-project.org/3.0/docs/devguide/custom-elements), [Lit, What is Lit?](https://lit.dev/docs/)).`,
|
|
1500
|
+
files: []
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
path: 'frameworks/angular',
|
|
1504
|
+
name: 'Angular data table',
|
|
1505
|
+
text: `<!-- intro -->
|
|
1506
|
+
Table, Bro has its own package for Angular, \`@table-bro/angular\`: the component \`‹table-bro-ng›\` renders the Custom Element \`‹table-bro›\`, gives it the signal inputs \`cols\`, \`rows\` and \`conf\`, and turns its \`bro-action\` events into the outputs \`action\` and \`query\`. It works with the three skins: Angular Material, Vanilla Bootstrap and Vanilla HTML. The page below is the [working page](https://table-bro.com/samples/angular-material) of the sample project \`samples/angular-material\` of the repository, with Angular 22 and Angular Material; [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/angular-material) opens it in the browser.
|
|
1507
|
+
|
|
1508
|
+
<!-- setup -->
|
|
1509
|
+
#### How do I install Table, Bro in Angular?
|
|
1510
|
+
|
|
1511
|
+
The steps of each skin; the component of the table is the same in all three.
|
|
1512
|
+
|
|
1513
|
+
##### Angular Material
|
|
1514
|
+
|
|
1515
|
+
${setupMat.text}
|
|
1516
|
+
|
|
1517
|
+
##### Vanilla Bootstrap
|
|
1518
|
+
|
|
1519
|
+
${setupNgBs.text}
|
|
1520
|
+
|
|
1521
|
+
##### Vanilla HTML
|
|
1522
|
+
|
|
1523
|
+
${setupNgNv.text}
|
|
1524
|
+
|
|
1525
|
+
#### How do I pass cols, rows and conf?
|
|
1526
|
+
|
|
1527
|
+
\`‹table-bro-ng›\` takes them as inputs, here from signals of the component ([Angular, Accepting data with input properties](https://angular.dev/guide/components/inputs)). A new value of \`rows\` replaces the rows of the table, and \`conf.skin\` names the registered skin. \`src/app/orders.component.ts\`:
|
|
1528
|
+
|
|
1529
|
+
{{src/app/orders.component.ts}}
|
|
1530
|
+
|
|
1531
|
+
#### How do I listen to bro-action?
|
|
1532
|
+
|
|
1533
|
+
The output \`action\` emits the detail of every \`bro-action\` event of the table, a \`BroActionDetail\`: custom cells, the toolbar, the paginator. The output \`query\` emits the queries of a table with server-side data instead ([Angular, Custom events with outputs](https://angular.dev/guide/components/outputs)). In the template: \`(action)="onAction($event)"\`.
|
|
1534
|
+
|
|
1535
|
+
#### How do I switch the theme?
|
|
1536
|
+
|
|
1537
|
+
The input \`theme\` of \`‹table-bro-ng›\` is \`'light'\`, \`'dark'\` or \`'auto'\`, which follows the color scheme of the system; without it the table takes \`config.theme\`, \`'auto'\` by default. The table puts the class \`bro-dark-theme\` or \`bro-light-theme\` on itself. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
1538
|
+
|
|
1539
|
+
#### Questions
|
|
1540
|
+
|
|
1541
|
+
##### Does the template need CUSTOM_ELEMENTS_SCHEMA?
|
|
1542
|
+
|
|
1543
|
+
Not for \`‹table-bro-ng›\`: it is a component of Angular in \`imports\` of the component. A template that renders \`‹table-bro›\` itself needs [CUSTOM_ELEMENTS_SCHEMA](https://angular.dev/api/core/CUSTOM_ELEMENTS_SCHEMA). Angular also turns its own components into Custom Elements ([Angular, Custom Elements](https://angular.dev/guide/elements)).
|
|
1544
|
+
|
|
1545
|
+
##### Does it work with server-side rendering?
|
|
1546
|
+
|
|
1547
|
+
Yes: \`‹table-bro-ng›\` prerenders the table in its loading state during the server-side rendering of Angular, as [Prerendering](https://table-bro.com/docs/ssr/prerendering) shows.
|
|
1548
|
+
|
|
1549
|
+
#### Related
|
|
1550
|
+
|
|
1551
|
+
[Getting started](https://table-bro.com/docs/getting-started), [Custom content in tables using Custom Elements](https://table-bro.com/docs/design/custom-content), [MCP server for AI coding agents](https://table-bro.com/docs/ai/mcp-server), [Preact data table](https://table-bro.com/docs/frameworks/preact), [React data table](https://table-bro.com/docs/frameworks/react), [Solid data table](https://table-bro.com/docs/frameworks/solid), [Svelte data table](https://table-bro.com/docs/frameworks/svelte), [Vue 3 data table](https://table-bro.com/docs/frameworks/vue).`,
|
|
1552
|
+
files: [
|
|
1553
|
+
...setupMat.files,
|
|
1554
|
+
...setupNgBs.files,
|
|
1555
|
+
...setupNgNv.files,
|
|
1556
|
+
{ file: 'src/app/orders.component.ts', language: 'ts', code: `import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
|
1557
|
+
|
|
1558
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
1559
|
+
import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
|
|
1560
|
+
|
|
1561
|
+
interface Order extends BroRow {
|
|
1562
|
+
description: string;
|
|
1563
|
+
price: number;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
@Component({
|
|
1567
|
+
selector: 'app-orders',
|
|
1568
|
+
template: \`
|
|
1569
|
+
<table-bro-ng class="bro-table-shadow-container" tableId="orders"
|
|
1570
|
+
[cols]="cols()" [rows]="rows()" [conf]="conf()"></table-bro-ng>\`,
|
|
1571
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1572
|
+
imports: [
|
|
1573
|
+
TableBroNg
|
|
1574
|
+
]
|
|
1575
|
+
})
|
|
1576
|
+
export class OrdersComponent {
|
|
1577
|
+
cols = signal<BroCol<Order>[]>([
|
|
1578
|
+
{
|
|
1579
|
+
title: 'Description',
|
|
1580
|
+
name: 'description'
|
|
1581
|
+
}, {
|
|
1582
|
+
title: 'Price',
|
|
1583
|
+
name: 'price',
|
|
1584
|
+
dataType: 'number',
|
|
1585
|
+
className: 'bro-col_text-right'
|
|
1586
|
+
}
|
|
1587
|
+
]);
|
|
1588
|
+
|
|
1589
|
+
rows = signal<Order[]>([
|
|
1590
|
+
{
|
|
1591
|
+
description: 'Modern Cotton Chair',
|
|
1592
|
+
price: 100
|
|
1593
|
+
}, {
|
|
1594
|
+
description: 'Awesome Concrete Ball',
|
|
1595
|
+
price: 200
|
|
1596
|
+
}
|
|
1597
|
+
]);
|
|
1598
|
+
|
|
1599
|
+
conf = signal<BroConfig>({
|
|
1600
|
+
skin: 'mat',
|
|
1601
|
+
toolbarTitle: 'Orders'
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
` }
|
|
1605
|
+
]
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
path: 'frameworks/preact',
|
|
1609
|
+
name: 'Preact data table',
|
|
1610
|
+
text: `<!-- intro -->
|
|
1611
|
+
Table, Bro is the Custom Element \`‹table-bro›\`, so a Preact app renders it in JSX as it is. A ref gives the element its columns, rows and config by \`configure()\`, the prop \`onbro-action\` listens to its events, and the \`theme\` prop sets its theme. The page below is the [working page](https://table-bro.com/samples/frameworks/preact) of the sample project \`samples/frameworks/preact\` of the repository, with Preact 11; [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/frameworks/preact) opens it in the browser.
|
|
1612
|
+
|
|
1613
|
+
<!-- setup -->
|
|
1614
|
+
#### How do I install Table, Bro in Preact?
|
|
1615
|
+
|
|
1616
|
+
1. **Install the packages**
|
|
1617
|
+
|
|
1618
|
+
The table engine \`@table-bro/core\` and a skin, \`@table-bro/native\` of plain HTML elements; \`@table-bro/bootstrap\` with \`bootstrap\` gives the skin of Bootstrap 5:
|
|
1619
|
+
|
|
1620
|
+
{{install.sh}}
|
|
1621
|
+
|
|
1622
|
+
2. **Register the element and the skin**
|
|
1623
|
+
|
|
1624
|
+
Once, before the tables: the import of the skin defines \`‹table-bro›\` and registers the skin \`nv\`. The CSS of the core and of the skin come as imports, which [Vite](https://vite.dev/guide/features) and the other bundlers take. \`src/skin.ts\`:
|
|
1625
|
+
|
|
1626
|
+
{{src/skin.ts}}
|
|
1627
|
+
|
|
1628
|
+
3. **Describe the columns, the rows and the config**
|
|
1629
|
+
|
|
1630
|
+
\`conf.skin\` names the registered skin. \`src/orders.ts\`:
|
|
1631
|
+
|
|
1632
|
+
{{src/orders.ts}}
|
|
1633
|
+
|
|
1634
|
+
#### How do I pass cols, rows and conf?
|
|
1635
|
+
|
|
1636
|
+
The columns, the rows and the config go to the table in one call, \`configure({cols, rows, conf})\`, once the element is in the document: a ref gives the element, \`useEffect\` makes the call. Preact adds a listener for an event prop it does not know by the exact name after \`on\`, \`bro-action\` for \`onbro-action\` ([Preact, Web Components](https://preactjs.com/guide/v10/web-components/)); the sample logs the name of each action. \`src/OrdersTable.tsx\`:
|
|
1637
|
+
|
|
1638
|
+
{{src/OrdersTable.tsx}}
|
|
1639
|
+
|
|
1640
|
+
\`"jsxImportSource": "preact"\` in \`tsconfig.json\` of the sample gives the JSX runtime of Preact to TypeScript and to Vite, without a plugin.
|
|
1641
|
+
|
|
1642
|
+
#### How does TypeScript know the element?
|
|
1643
|
+
|
|
1644
|
+
A declaration adds \`‹table-bro›\` to the JSX of Preact, with the methods of \`TableBroHTMLElement\` and the prop \`onbro-action\`. \`src/elements.d.ts\`:
|
|
1645
|
+
|
|
1646
|
+
{{src/elements.d.ts}}
|
|
1647
|
+
|
|
1648
|
+
#### How do I render it?
|
|
1649
|
+
|
|
1650
|
+
\`src/main.tsx\` imports the skin before the tables:
|
|
1651
|
+
|
|
1652
|
+
{{src/main.tsx}}
|
|
1653
|
+
|
|
1654
|
+
#### How do I switch the theme?
|
|
1655
|
+
|
|
1656
|
+
The \`theme\` attribute or property of the table is \`'light'\`, \`'dark'\` or \`'auto'\`, which follows the color scheme of the system; the attribute wins over \`config.theme\`. The sample sets \`'auto'\`, as a table without the attribute does; a variable of the state bound to it switches the theme, and the table puts the class \`bro-dark-theme\` or \`bro-light-theme\` on itself. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
1657
|
+
|
|
1658
|
+
#### Questions
|
|
1659
|
+
|
|
1660
|
+
##### Does Preact need a wrapper for Custom Elements?
|
|
1661
|
+
|
|
1662
|
+
No. Preact passes all 32 tests of [Custom Elements Everywhere](https://custom-elements-everywhere.com/). Its package \`preact-custom-element\` also turns a Preact component into a Custom Element ([Preact, preact-custom-element](https://preactjs.com/guide/v10/preact-custom-element/)).
|
|
1663
|
+
|
|
1664
|
+
##### Can I bind cols and rows as props?
|
|
1665
|
+
|
|
1666
|
+
The table takes its data by \`configure()\`, one call for the columns, the rows and the config. New rows go by \`replaceRows(rows)\` on the same ref.
|
|
1667
|
+
|
|
1668
|
+
##### Does preact/compat change it?
|
|
1669
|
+
|
|
1670
|
+
No: \`‹table-bro›\` is an element of the DOM, the same in Preact and in React through \`preact/compat\`.
|
|
1671
|
+
|
|
1672
|
+
#### Related
|
|
1673
|
+
|
|
1674
|
+
[Getting started](https://table-bro.com/docs/getting-started), [Custom content in tables using Custom Elements](https://table-bro.com/docs/design/custom-content), [MCP server for AI coding agents](https://table-bro.com/docs/ai/mcp-server), [Angular data table](https://table-bro.com/docs/frameworks/angular), [React data table](https://table-bro.com/docs/frameworks/react), [Solid data table](https://table-bro.com/docs/frameworks/solid), [Svelte data table](https://table-bro.com/docs/frameworks/svelte), [Vue 3 data table](https://table-bro.com/docs/frameworks/vue).`,
|
|
1675
|
+
files: [
|
|
1676
|
+
frameworkInstall,
|
|
1677
|
+
frameworkSkin,
|
|
1678
|
+
frameworkOrders,
|
|
1679
|
+
{ file: 'src/elements.d.ts', language: 'ts', code: `// The element <table-bro> in the JSX of Preact, with its attribute theme and its event bro-action
|
|
1680
|
+
import type {HTMLAttributes} from 'preact';
|
|
1681
|
+
|
|
1682
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
1683
|
+
|
|
1684
|
+
declare module 'preact' {
|
|
1685
|
+
namespace JSX {
|
|
1686
|
+
interface IntrinsicElements {
|
|
1687
|
+
'table-bro': HTMLAttributes<TableBroHTMLElement> & {theme?: 'light' | 'dark' | 'auto'; 'onbro-action'?: (event: CustomEvent<BroActionDetail>) => void};
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
` },
|
|
1692
|
+
{ file: 'src/OrdersTable.tsx', language: 'tsx', code: `import {useEffect, useRef} from 'preact/hooks';
|
|
1693
|
+
|
|
1694
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
1695
|
+
|
|
1696
|
+
import {cols, conf, rows} from './orders';
|
|
1697
|
+
|
|
1698
|
+
export function OrdersTable() {
|
|
1699
|
+
const table = useRef<TableBroHTMLElement>(null);
|
|
1700
|
+
|
|
1701
|
+
useEffect(() => {
|
|
1702
|
+
table.current!.configure({cols, rows, conf});
|
|
1703
|
+
}, []);
|
|
1704
|
+
|
|
1705
|
+
return (
|
|
1706
|
+
<div class="bro-table-shadow-container">
|
|
1707
|
+
<table-bro id="orders" ref={table} theme="auto"
|
|
1708
|
+
onbro-action={(event: CustomEvent<BroActionDetail>) => console.log(event.detail.action)}></table-bro>
|
|
1709
|
+
</div>
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
` },
|
|
1713
|
+
{ file: 'src/main.tsx', language: 'tsx', code: `// The skin is registered before the tables
|
|
1714
|
+
import './skin';
|
|
1715
|
+
|
|
1716
|
+
import {render} from 'preact';
|
|
1717
|
+
|
|
1718
|
+
import {OrdersTable} from './OrdersTable';
|
|
1719
|
+
|
|
1720
|
+
render(<OrdersTable />, document.getElementById('app')!);
|
|
1721
|
+
` }
|
|
1722
|
+
]
|
|
1723
|
+
},
|
|
1724
|
+
{
|
|
1725
|
+
path: 'frameworks/react',
|
|
1726
|
+
name: 'React data table',
|
|
1727
|
+
text: `<!-- intro -->
|
|
1728
|
+
Table, Bro is the Custom Element \`‹table-bro›\`, so a React 19 app renders it in JSX as it is, without a wrapper package. A ref gives the element its columns, rows and config by \`configure()\`, an effect adds the listener of its \`bro-action\` events, and the \`theme\` prop sets its theme. The page below is the [working page](https://table-bro.com/samples/frameworks/react) of the sample project \`samples/frameworks/react\` of the repository, with React 19.3; [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/frameworks/react) opens it in the browser.
|
|
1729
|
+
|
|
1730
|
+
<!-- setup -->
|
|
1731
|
+
#### How do I install Table, Bro in React?
|
|
1732
|
+
|
|
1733
|
+
1. **Install the packages**
|
|
1734
|
+
|
|
1735
|
+
The table engine \`@table-bro/core\` and a skin, \`@table-bro/native\` of plain HTML elements; \`@table-bro/bootstrap\` with \`bootstrap\` gives the skin of Bootstrap 5:
|
|
1736
|
+
|
|
1737
|
+
{{install.sh}}
|
|
1738
|
+
|
|
1739
|
+
2. **Register the element and the skin**
|
|
1740
|
+
|
|
1741
|
+
Once, before the tables: the import of the skin defines \`‹table-bro›\` and registers the skin \`nv\`. The CSS of the core and of the skin come as imports, which [Vite](https://vite.dev/guide/features) and the other bundlers take. \`src/skin.ts\`:
|
|
1742
|
+
|
|
1743
|
+
{{src/skin.ts}}
|
|
1744
|
+
|
|
1745
|
+
3. **Describe the columns, the rows and the config**
|
|
1746
|
+
|
|
1747
|
+
\`conf.skin\` names the registered skin. \`src/orders.ts\`:
|
|
1748
|
+
|
|
1749
|
+
{{src/orders.ts}}
|
|
1750
|
+
|
|
1751
|
+
#### How do I pass cols, rows and conf?
|
|
1752
|
+
|
|
1753
|
+
The columns, the rows and the config go to the table in one call, \`configure({cols, rows, conf})\`, once the element is in the document: a ref gives the element, \`useEffect\` makes the call after the first render. The same effect adds the listener of \`bro-action\` and removes it at the end; the sample logs the name of each action. \`src/OrdersTable.tsx\`:
|
|
1754
|
+
|
|
1755
|
+
{{src/OrdersTable.tsx}}
|
|
1756
|
+
|
|
1757
|
+
React 19 sets a prop of a custom element as a property when the element has a property of that name, and as an attribute otherwise ([React 19, Support for Custom Elements](https://react.dev/blog/2024/12/05/react-19)): \`theme\` is a property of \`‹table-bro›\`. \`"jsx": "react-jsx"\` in \`tsconfig.json\` of the sample gives the JSX runtime of React to TypeScript and to Vite.
|
|
1758
|
+
|
|
1759
|
+
#### How does TypeScript know the element?
|
|
1760
|
+
|
|
1761
|
+
JSX of React knows the HTML elements only; a declaration adds \`‹table-bro›\` with the methods of \`TableBroHTMLElement\`. \`src/elements.d.ts\`:
|
|
1762
|
+
|
|
1763
|
+
{{src/elements.d.ts}}
|
|
1764
|
+
|
|
1765
|
+
#### How do I render it?
|
|
1766
|
+
|
|
1767
|
+
\`src/main.tsx\` imports the skin before the tables:
|
|
1768
|
+
|
|
1769
|
+
{{src/main.tsx}}
|
|
1770
|
+
|
|
1771
|
+
#### How do I switch the theme?
|
|
1772
|
+
|
|
1773
|
+
The \`theme\` attribute or property of the table is \`'light'\`, \`'dark'\` or \`'auto'\`, which follows the color scheme of the system; the attribute wins over \`config.theme\`. The sample sets \`'auto'\`, as a table without the attribute does; a variable of the state bound to it switches the theme, and the table puts the class \`bro-dark-theme\` or \`bro-light-theme\` on itself. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
1774
|
+
|
|
1775
|
+
#### Questions
|
|
1776
|
+
|
|
1777
|
+
##### Does React 19 need a wrapper for Custom Elements?
|
|
1778
|
+
|
|
1779
|
+
No. React 19 passes the props of a custom element as properties or attributes ([React, Custom HTML elements](https://react.dev/reference/react-dom/components#custom-html-elements)) and passes all 32 tests of [Custom Elements Everywhere](https://custom-elements-everywhere.com/). Table, Bro takes its data by \`configure()\` through a ref, which works in every version of React.
|
|
1780
|
+
|
|
1781
|
+
##### Can I listen to bro-action without a ref?
|
|
1782
|
+
|
|
1783
|
+
The sample uses \`addEventListener\` in an effect: it works in React 18 and 19 and removes the listener on unmount.
|
|
1784
|
+
|
|
1785
|
+
##### Does it work with Next.js?
|
|
1786
|
+
|
|
1787
|
+
The table renders in the browser: \`configure()\` runs in an effect, so a client component (\`'use client'\`) works, and the server render leaves the element empty.
|
|
1788
|
+
|
|
1789
|
+
#### Related
|
|
1790
|
+
|
|
1791
|
+
[Getting started](https://table-bro.com/docs/getting-started), [Custom content in tables using Custom Elements](https://table-bro.com/docs/design/custom-content), [MCP server for AI coding agents](https://table-bro.com/docs/ai/mcp-server), [Angular data table](https://table-bro.com/docs/frameworks/angular), [Preact data table](https://table-bro.com/docs/frameworks/preact), [Solid data table](https://table-bro.com/docs/frameworks/solid), [Svelte data table](https://table-bro.com/docs/frameworks/svelte), [Vue 3 data table](https://table-bro.com/docs/frameworks/vue).`,
|
|
1792
|
+
files: [
|
|
1793
|
+
frameworkInstall,
|
|
1794
|
+
frameworkSkin,
|
|
1795
|
+
frameworkOrders,
|
|
1796
|
+
{ file: 'src/elements.d.ts', language: 'ts', code: `// The element <table-bro> in the JSX of React, with its attribute theme
|
|
1797
|
+
import type {DetailedHTMLProps, HTMLAttributes} from 'react';
|
|
1798
|
+
|
|
1799
|
+
import type {TableBroHTMLElement} from '@table-bro/core';
|
|
1800
|
+
|
|
1801
|
+
declare module 'react' {
|
|
1802
|
+
namespace JSX {
|
|
1803
|
+
interface IntrinsicElements {
|
|
1804
|
+
'table-bro': DetailedHTMLProps<HTMLAttributes<TableBroHTMLElement>, TableBroHTMLElement> & {theme?: 'light' | 'dark' | 'auto'};
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
` },
|
|
1809
|
+
{ file: 'src/OrdersTable.tsx', language: 'tsx', code: `import {useEffect, useRef} from 'react';
|
|
1810
|
+
|
|
1811
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
1812
|
+
|
|
1813
|
+
import {cols, conf, rows} from './orders';
|
|
1814
|
+
|
|
1815
|
+
export function OrdersTable() {
|
|
1816
|
+
const table = useRef<TableBroHTMLElement>(null);
|
|
1817
|
+
|
|
1818
|
+
useEffect(() => {
|
|
1819
|
+
const element = table.current!;
|
|
1820
|
+
const onAction = (event: CustomEvent<BroActionDetail>) => console.log(event.detail.action);
|
|
1821
|
+
|
|
1822
|
+
element.configure({cols, rows, conf});
|
|
1823
|
+
element.addEventListener('bro-action', onAction);
|
|
1824
|
+
|
|
1825
|
+
return () => element.removeEventListener('bro-action', onAction);
|
|
1826
|
+
}, []);
|
|
1827
|
+
|
|
1828
|
+
return (
|
|
1829
|
+
<div className="bro-table-shadow-container">
|
|
1830
|
+
<table-bro id="orders" ref={table} theme="auto"></table-bro>
|
|
1831
|
+
</div>
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
` },
|
|
1835
|
+
{ file: 'src/main.tsx', language: 'tsx', code: `// The skin is registered before the tables
|
|
1836
|
+
import './skin';
|
|
1837
|
+
|
|
1838
|
+
import {StrictMode} from 'react';
|
|
1839
|
+
import {createRoot} from 'react-dom/client';
|
|
1840
|
+
|
|
1841
|
+
import {OrdersTable} from './OrdersTable';
|
|
1842
|
+
|
|
1843
|
+
createRoot(document.getElementById('app')!).render(
|
|
1844
|
+
<StrictMode>
|
|
1845
|
+
<OrdersTable />
|
|
1846
|
+
</StrictMode>
|
|
1847
|
+
);
|
|
1848
|
+
` }
|
|
1849
|
+
]
|
|
1850
|
+
},
|
|
1851
|
+
{
|
|
1852
|
+
path: 'frameworks/solid',
|
|
1853
|
+
name: 'Solid data table',
|
|
1854
|
+
text: `<!-- intro -->
|
|
1855
|
+
Table, Bro is the Custom Element \`‹table-bro›\`, so a Solid app renders it in JSX as it is. A ref gives the element its columns, rows and config by \`configure()\`, \`on:bro-action\` listens to its events, and the \`theme\` attribute sets its theme. The page below is the [working page](https://table-bro.com/samples/frameworks/solid) of the sample project \`samples/frameworks/solid\` of the repository, with Solid 1.9; [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/frameworks/solid) opens it in the browser.
|
|
1856
|
+
|
|
1857
|
+
<!-- setup -->
|
|
1858
|
+
#### How do I install Table, Bro in Solid?
|
|
1859
|
+
|
|
1860
|
+
1. **Install the packages**
|
|
1861
|
+
|
|
1862
|
+
The table engine \`@table-bro/core\` and a skin, \`@table-bro/native\` of plain HTML elements; \`@table-bro/bootstrap\` with \`bootstrap\` gives the skin of Bootstrap 5:
|
|
1863
|
+
|
|
1864
|
+
{{install.sh}}
|
|
1865
|
+
|
|
1866
|
+
2. **Register the element and the skin**
|
|
1867
|
+
|
|
1868
|
+
Once, before the tables: the import of the skin defines \`‹table-bro›\` and registers the skin \`nv\`. The CSS of the core and of the skin come as imports, which [Vite](https://vite.dev/guide/features) and the other bundlers take. \`src/skin.ts\`:
|
|
1869
|
+
|
|
1870
|
+
{{src/skin.ts}}
|
|
1871
|
+
|
|
1872
|
+
3. **Describe the columns, the rows and the config**
|
|
1873
|
+
|
|
1874
|
+
\`conf.skin\` names the registered skin. \`src/orders.ts\`:
|
|
1875
|
+
|
|
1876
|
+
{{src/orders.ts}}
|
|
1877
|
+
|
|
1878
|
+
#### How do I pass cols, rows and conf?
|
|
1879
|
+
|
|
1880
|
+
The columns, the rows and the config go to the table in one call, \`configure({cols, rows, conf})\`, once the element is mounted: \`ref\` gives the element ([Solid, ref](https://docs.solidjs.com/reference/jsx-attributes/ref)), \`onMount\` makes the call. \`on:bro-action\` adds the listener of the event by its exact name ([Solid, on:*](https://docs.solidjs.com/reference/jsx-attributes/on)); the sample logs the name of each action. \`src/OrdersTable.tsx\`:
|
|
1881
|
+
|
|
1882
|
+
{{src/OrdersTable.tsx}}
|
|
1883
|
+
|
|
1884
|
+
\`vite-plugin-solid\` compiles the JSX of Solid, and \`"jsx": "preserve"\` with \`"jsxImportSource": "solid-js"\` in \`tsconfig.json\` gives its types to TypeScript. \`vite.config.ts\`:
|
|
1885
|
+
|
|
1886
|
+
{{vite.config.ts}}
|
|
1887
|
+
|
|
1888
|
+
\`mergeConfig\` adds \`sample-page.config.ts\` of the sample, the source maps and the readable code of its working page; a project of its own exports \`defineConfig()\` with the plugins alone.
|
|
1889
|
+
|
|
1890
|
+
#### How does TypeScript know the element?
|
|
1891
|
+
|
|
1892
|
+
A declaration adds \`‹table-bro›\` to the JSX of Solid, with the methods of \`TableBroHTMLElement\`, and \`bro-action\` to its events of \`on:\`. \`src/elements.d.ts\`:
|
|
1893
|
+
|
|
1894
|
+
{{src/elements.d.ts}}
|
|
1895
|
+
|
|
1896
|
+
#### How do I render it?
|
|
1897
|
+
|
|
1898
|
+
\`src/main.tsx\` imports the skin before the tables:
|
|
1899
|
+
|
|
1900
|
+
{{src/main.tsx}}
|
|
1901
|
+
|
|
1902
|
+
#### How do I switch the theme?
|
|
1903
|
+
|
|
1904
|
+
The \`theme\` attribute or property of the table is \`'light'\`, \`'dark'\` or \`'auto'\`, which follows the color scheme of the system; the attribute wins over \`config.theme\`. The sample sets \`'auto'\`, as a table without the attribute does; a variable of the state bound to it switches the theme, and the table puts the class \`bro-dark-theme\` or \`bro-light-theme\` on itself. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
1905
|
+
|
|
1906
|
+
#### Questions
|
|
1907
|
+
|
|
1908
|
+
##### Does Solid need a wrapper for Custom Elements?
|
|
1909
|
+
|
|
1910
|
+
No. Solid passes all 32 tests of [Custom Elements Everywhere](https://custom-elements-everywhere.com/); \`on:\` listens to an event by its exact name, as \`bro-action\`. Its package \`solid-element\` also turns a Solid component into a Custom Element ([Solid, solid-element](https://www.npmjs.com/package/solid-element)).
|
|
1911
|
+
|
|
1912
|
+
##### Can I bind cols and rows as props?
|
|
1913
|
+
|
|
1914
|
+
The table takes its data by \`configure()\`, one call for the columns, the rows and the config. New rows go by \`replaceRows(rows)\` on the same ref, as in an effect of a signal of the rows.
|
|
1915
|
+
|
|
1916
|
+
##### Does it work with SolidStart?
|
|
1917
|
+
|
|
1918
|
+
The element needs the browser: \`onMount\` runs only there, so the server render leaves the element empty and the browser fills it.
|
|
1919
|
+
|
|
1920
|
+
#### Related
|
|
1921
|
+
|
|
1922
|
+
[Getting started](https://table-bro.com/docs/getting-started), [Custom content in tables using Custom Elements](https://table-bro.com/docs/design/custom-content), [MCP server for AI coding agents](https://table-bro.com/docs/ai/mcp-server), [Angular data table](https://table-bro.com/docs/frameworks/angular), [Preact data table](https://table-bro.com/docs/frameworks/preact), [React data table](https://table-bro.com/docs/frameworks/react), [Svelte data table](https://table-bro.com/docs/frameworks/svelte), [Vue 3 data table](https://table-bro.com/docs/frameworks/vue).`,
|
|
1923
|
+
files: [
|
|
1924
|
+
frameworkInstall,
|
|
1925
|
+
frameworkSkin,
|
|
1926
|
+
frameworkOrders,
|
|
1927
|
+
{ file: 'vite.config.ts', language: 'ts', code: `import {defineConfig, mergeConfig} from 'vite';
|
|
1928
|
+
import solid from 'vite-plugin-solid';
|
|
1929
|
+
|
|
1930
|
+
import {samplePage} from './sample-page.config.ts';
|
|
1931
|
+
|
|
1932
|
+
export default mergeConfig(samplePage, defineConfig({
|
|
1933
|
+
plugins: [
|
|
1934
|
+
solid()
|
|
1935
|
+
]
|
|
1936
|
+
}));
|
|
1937
|
+
` },
|
|
1938
|
+
{ file: 'src/elements.d.ts', language: 'ts', code: `// The element <table-bro> in the JSX of Solid, with its attribute theme and its event bro-action for on:bro-action
|
|
1939
|
+
import type {JSX as SolidJSX} from 'solid-js';
|
|
1940
|
+
|
|
1941
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
1942
|
+
|
|
1943
|
+
declare module 'solid-js' {
|
|
1944
|
+
namespace JSX {
|
|
1945
|
+
interface IntrinsicElements {
|
|
1946
|
+
'table-bro': SolidJSX.HTMLAttributes<TableBroHTMLElement> & {theme?: 'light' | 'dark' | 'auto'};
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
interface CustomEvents {
|
|
1950
|
+
'bro-action': CustomEvent<BroActionDetail>;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
` },
|
|
1955
|
+
{ file: 'src/OrdersTable.tsx', language: 'tsx', code: `import {onMount} from 'solid-js';
|
|
1956
|
+
|
|
1957
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
1958
|
+
|
|
1959
|
+
import {cols, conf, rows} from './orders';
|
|
1960
|
+
|
|
1961
|
+
export function OrdersTable() {
|
|
1962
|
+
let table!: TableBroHTMLElement;
|
|
1963
|
+
|
|
1964
|
+
onMount(() => table.configure({cols, rows, conf}));
|
|
1965
|
+
|
|
1966
|
+
return (
|
|
1967
|
+
<div class="bro-table-shadow-container">
|
|
1968
|
+
<table-bro id="orders" ref={table} theme="auto"
|
|
1969
|
+
on:bro-action={(event: CustomEvent<BroActionDetail>) => console.log(event.detail.action)}></table-bro>
|
|
1970
|
+
</div>
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
` },
|
|
1974
|
+
{ file: 'src/main.tsx', language: 'tsx', code: `// The skin is registered before the tables
|
|
1975
|
+
import './skin';
|
|
1976
|
+
|
|
1977
|
+
import {render} from 'solid-js/web';
|
|
1978
|
+
|
|
1979
|
+
import {OrdersTable} from './OrdersTable';
|
|
1980
|
+
|
|
1981
|
+
render(() => <OrdersTable />, document.getElementById('app')!);
|
|
1982
|
+
` }
|
|
1983
|
+
]
|
|
1984
|
+
},
|
|
1985
|
+
{
|
|
1986
|
+
path: 'frameworks/svelte',
|
|
1987
|
+
name: 'Svelte data table',
|
|
1988
|
+
text: `<!-- intro -->
|
|
1989
|
+
Table, Bro is the Custom Element \`‹table-bro›\`, so a Svelte 5 app renders it in markup as it is. \`bind:this\` gives the element its columns, rows and config by \`configure()\`, the attribute \`onbro-action\` listens to its events, and the \`theme\` attribute sets its theme. The page below is the [working page](https://table-bro.com/samples/frameworks/svelte) of the sample project \`samples/frameworks/svelte\` of the repository, with Svelte 5.57; [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/frameworks/svelte) opens it in the browser.
|
|
1990
|
+
|
|
1991
|
+
<!-- setup -->
|
|
1992
|
+
#### How do I install Table, Bro in Svelte?
|
|
1993
|
+
|
|
1994
|
+
1. **Install the packages**
|
|
1995
|
+
|
|
1996
|
+
The table engine \`@table-bro/core\` and a skin, \`@table-bro/native\` of plain HTML elements; \`@table-bro/bootstrap\` with \`bootstrap\` gives the skin of Bootstrap 5:
|
|
1997
|
+
|
|
1998
|
+
{{install.sh}}
|
|
1999
|
+
|
|
2000
|
+
2. **Register the element and the skin**
|
|
2001
|
+
|
|
2002
|
+
Once, before the tables: the import of the skin defines \`‹table-bro›\` and registers the skin \`nv\`. The CSS of the core and of the skin come as imports, which [Vite](https://vite.dev/guide/features) and the other bundlers take. \`src/skin.ts\`:
|
|
2003
|
+
|
|
2004
|
+
{{src/skin.ts}}
|
|
2005
|
+
|
|
2006
|
+
3. **Describe the columns, the rows and the config**
|
|
2007
|
+
|
|
2008
|
+
\`conf.skin\` names the registered skin. \`src/orders.ts\`:
|
|
2009
|
+
|
|
2010
|
+
{{src/orders.ts}}
|
|
2011
|
+
|
|
2012
|
+
#### How do I pass cols, rows and conf?
|
|
2013
|
+
|
|
2014
|
+
The columns, the rows and the config go to the table in one call, \`configure({cols, rows, conf})\`, once the element is mounted: \`bind:this\` gives the element ([Svelte, bind:this](https://svelte.dev/docs/svelte/bind)), \`onMount\` makes the call ([Svelte, Lifecycle hooks](https://svelte.dev/docs/svelte/lifecycle-hooks)). An attribute that starts with \`on\` adds a listener of the event of its name, \`bro-action\` for \`onbro-action\` ([Svelte, Events](https://svelte.dev/docs/svelte/basic-markup)); the sample logs the name of each action. \`src/OrdersTable.svelte\`:
|
|
2015
|
+
|
|
2016
|
+
{{src/OrdersTable.svelte}}
|
|
2017
|
+
|
|
2018
|
+
#### How do I render it?
|
|
2019
|
+
|
|
2020
|
+
\`src/main.ts\` imports the skin before the tables. A project of \`npm create vite@latest\` with the Svelte template needs no other setup for \`‹table-bro›\`:
|
|
2021
|
+
|
|
2022
|
+
{{src/main.ts}}
|
|
2023
|
+
|
|
2024
|
+
#### How do I switch the theme?
|
|
2025
|
+
|
|
2026
|
+
The \`theme\` attribute or property of the table is \`'light'\`, \`'dark'\` or \`'auto'\`, which follows the color scheme of the system; the attribute wins over \`config.theme\`. The sample sets \`'auto'\`, as a table without the attribute does; a variable of the state bound to it switches the theme, and the table puts the class \`bro-dark-theme\` or \`bro-light-theme\` on itself. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
2027
|
+
|
|
2028
|
+
#### Questions
|
|
2029
|
+
|
|
2030
|
+
##### Does Svelte need a wrapper for Custom Elements?
|
|
2031
|
+
|
|
2032
|
+
No. Svelte passes all 32 tests of [Custom Elements Everywhere](https://custom-elements-everywhere.com/). Svelte also compiles its own components into Custom Elements ([Svelte, Custom elements](https://svelte.dev/docs/svelte/custom-elements)).
|
|
2033
|
+
|
|
2034
|
+
##### Can I bind cols and rows as props?
|
|
2035
|
+
|
|
2036
|
+
The table takes its data by \`configure()\`, one call for the columns, the rows and the config. New rows go by \`replaceRows(rows)\` on the same element.
|
|
2037
|
+
|
|
2038
|
+
##### Does it work with SvelteKit?
|
|
2039
|
+
|
|
2040
|
+
The element needs the browser: \`onMount\` runs only there, so the server render of SvelteKit leaves the element empty and the browser fills it.
|
|
2041
|
+
|
|
2042
|
+
#### Related
|
|
2043
|
+
|
|
2044
|
+
[Getting started](https://table-bro.com/docs/getting-started), [Custom content in tables using Custom Elements](https://table-bro.com/docs/design/custom-content), [MCP server for AI coding agents](https://table-bro.com/docs/ai/mcp-server), [Angular data table](https://table-bro.com/docs/frameworks/angular), [Preact data table](https://table-bro.com/docs/frameworks/preact), [React data table](https://table-bro.com/docs/frameworks/react), [Solid data table](https://table-bro.com/docs/frameworks/solid), [Vue 3 data table](https://table-bro.com/docs/frameworks/vue).`,
|
|
2045
|
+
files: [
|
|
2046
|
+
frameworkInstall,
|
|
2047
|
+
frameworkSkin,
|
|
2048
|
+
frameworkOrders,
|
|
2049
|
+
{ file: 'src/OrdersTable.svelte', language: 'svelte', code: `<script lang="ts">
|
|
2050
|
+
import {onMount} from 'svelte';
|
|
2051
|
+
|
|
2052
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
2053
|
+
|
|
2054
|
+
import {cols, conf, rows} from './orders';
|
|
2055
|
+
|
|
2056
|
+
let table: TableBroHTMLElement;
|
|
2057
|
+
|
|
2058
|
+
onMount(() => table.configure({cols, rows, conf}));
|
|
2059
|
+
</script>
|
|
2060
|
+
|
|
2061
|
+
<div class="bro-table-shadow-container">
|
|
2062
|
+
<table-bro id="orders" bind:this={table} theme="auto"
|
|
2063
|
+
onbro-action={(event: CustomEvent<BroActionDetail>) => console.log(event.detail.action)}></table-bro>
|
|
2064
|
+
</div>
|
|
2065
|
+
` },
|
|
2066
|
+
{ file: 'src/main.ts', language: 'ts', code: `// The skin is registered before the tables
|
|
2067
|
+
import './skin';
|
|
2068
|
+
|
|
2069
|
+
import {mount} from 'svelte';
|
|
2070
|
+
|
|
2071
|
+
import OrdersTable from './OrdersTable.svelte';
|
|
2072
|
+
|
|
2073
|
+
mount(OrdersTable, {target: document.getElementById('app')!});
|
|
2074
|
+
` }
|
|
2075
|
+
]
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
path: 'frameworks/vue',
|
|
2079
|
+
name: 'Vue 3 data table',
|
|
2080
|
+
text: `<!-- intro -->
|
|
2081
|
+
Table, Bro is the Custom Element \`‹table-bro›\`, so a Vue 3 app renders it in a template once the compiler knows the tag: \`compilerOptions.isCustomElement\` of \`@vitejs/plugin-vue\`. A template ref gives the element its columns, rows and config by \`configure()\`, \`@bro-action\` listens to its events, and the \`theme\` attribute sets its theme. The page below is the [working page](https://table-bro.com/samples/frameworks/vue) of the sample project \`samples/frameworks/vue\` of the repository, with Vue 3.5; [StackBlitz](https://stackblitz.com/github/table-bro/table-bro/tree/main/samples/frameworks/vue) opens it in the browser.
|
|
2082
|
+
|
|
2083
|
+
<!-- setup -->
|
|
2084
|
+
#### How do I install Table, Bro in Vue?
|
|
2085
|
+
|
|
2086
|
+
1. **Install the packages**
|
|
2087
|
+
|
|
2088
|
+
The table engine \`@table-bro/core\` and a skin, \`@table-bro/native\` of plain HTML elements; \`@table-bro/bootstrap\` with \`bootstrap\` gives the skin of Bootstrap 5:
|
|
2089
|
+
|
|
2090
|
+
{{install.sh}}
|
|
2091
|
+
|
|
2092
|
+
2. **Register the element and the skin**
|
|
2093
|
+
|
|
2094
|
+
Once, before the tables: the import of the skin defines \`‹table-bro›\` and registers the skin \`nv\`. The CSS of the core and of the skin come as imports, which [Vite](https://vite.dev/guide/features) and the other bundlers take. \`src/skin.ts\`:
|
|
2095
|
+
|
|
2096
|
+
{{src/skin.ts}}
|
|
2097
|
+
|
|
2098
|
+
3. **Describe the columns, the rows and the config**
|
|
2099
|
+
|
|
2100
|
+
\`conf.skin\` names the registered skin. \`src/orders.ts\`:
|
|
2101
|
+
|
|
2102
|
+
{{src/orders.ts}}
|
|
2103
|
+
|
|
2104
|
+
#### How do I tell Vue that table-bro is a Custom Element?
|
|
2105
|
+
|
|
2106
|
+
Without it, Vue resolves \`‹table-bro›\` as a component of the app and warns that it fails. \`isCustomElement\` of the template compiler skips the tags of Table, Bro ([Vue, Using Custom Elements in Vue](https://vuejs.org/guide/extras/web-components.html)). \`vite.config.ts\`:
|
|
2107
|
+
|
|
2108
|
+
{{vite.config.ts}}
|
|
2109
|
+
|
|
2110
|
+
\`mergeConfig\` adds \`sample-page.config.ts\` of the sample, the source maps and the readable code of its working page; a project of its own exports \`defineConfig()\` with the plugins alone.
|
|
2111
|
+
|
|
2112
|
+
#### How do I pass cols, rows and conf?
|
|
2113
|
+
|
|
2114
|
+
The columns, the rows and the config go to the table in one call, \`configure({cols, rows, conf})\`, once the element is mounted: \`useTemplateRef\` gives the element, \`onMounted\` makes the call. \`@bro-action\` adds the listener of the event, which logs the name of each action, and \`theme\` sets the theme; a binding \`:theme\` goes to the property \`theme\` of the element, since Vue sets a binding of a custom element as a property when the element has it. \`src/OrdersTable.vue\`:
|
|
2115
|
+
|
|
2116
|
+
{{src/OrdersTable.vue}}
|
|
2117
|
+
|
|
2118
|
+
#### How do I render it?
|
|
2119
|
+
|
|
2120
|
+
\`src/main.ts\` imports the skin before the tables:
|
|
2121
|
+
|
|
2122
|
+
{{src/main.ts}}
|
|
2123
|
+
|
|
2124
|
+
#### How do I switch the theme?
|
|
2125
|
+
|
|
2126
|
+
The \`theme\` attribute or property of the table is \`'light'\`, \`'dark'\` or \`'auto'\`, which follows the color scheme of the system; the attribute wins over \`config.theme\`. The sample sets \`'auto'\`, as a table without the attribute does; a variable of the state bound to it switches the theme, and the table puts the class \`bro-dark-theme\` or \`bro-light-theme\` on itself. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
2127
|
+
|
|
2128
|
+
#### Questions
|
|
2129
|
+
|
|
2130
|
+
##### Does Vue need a wrapper for Custom Elements?
|
|
2131
|
+
|
|
2132
|
+
No. Vue passes all 32 tests of [Custom Elements Everywhere](https://custom-elements-everywhere.com/); it only needs \`isCustomElement\` for the tags of Table, Bro.
|
|
2133
|
+
|
|
2134
|
+
##### Can I bind cols and rows as props?
|
|
2135
|
+
|
|
2136
|
+
The table takes its data by \`configure()\`, one call for the columns, the rows and the config. New rows go by \`replaceRows(rows)\` on the same ref.
|
|
2137
|
+
|
|
2138
|
+
##### Does it work with Nuxt?
|
|
2139
|
+
|
|
2140
|
+
The element needs the browser: render the component on the client, as \`‹ClientOnly›\` does; \`isCustomElement\` goes to \`vue.compilerOptions\` of \`nuxt.config.ts\`.
|
|
2141
|
+
|
|
2142
|
+
#### Related
|
|
2143
|
+
|
|
2144
|
+
[Getting started](https://table-bro.com/docs/getting-started), [Custom content in tables using Custom Elements](https://table-bro.com/docs/design/custom-content), [MCP server for AI coding agents](https://table-bro.com/docs/ai/mcp-server), [Angular data table](https://table-bro.com/docs/frameworks/angular), [Preact data table](https://table-bro.com/docs/frameworks/preact), [React data table](https://table-bro.com/docs/frameworks/react), [Solid data table](https://table-bro.com/docs/frameworks/solid), [Svelte data table](https://table-bro.com/docs/frameworks/svelte).`,
|
|
2145
|
+
files: [
|
|
2146
|
+
frameworkInstall,
|
|
2147
|
+
frameworkSkin,
|
|
2148
|
+
frameworkOrders,
|
|
2149
|
+
{ file: 'vite.config.ts', language: 'ts', code: `import vue from '@vitejs/plugin-vue';
|
|
2150
|
+
import {defineConfig, mergeConfig} from 'vite';
|
|
2151
|
+
|
|
2152
|
+
import {samplePage} from './sample-page.config.ts';
|
|
2153
|
+
|
|
2154
|
+
export default mergeConfig(samplePage, defineConfig({
|
|
2155
|
+
plugins: [
|
|
2156
|
+
// Vue compiles <table-bro> of its templates as a Custom Element, not as a component of Vue
|
|
2157
|
+
vue({template: {compilerOptions: {isCustomElement: tag => tag.startsWith('table-bro')}}})
|
|
2158
|
+
]
|
|
2159
|
+
}));
|
|
2160
|
+
` },
|
|
2161
|
+
{ file: 'src/OrdersTable.vue', language: 'vue', code: `<script setup lang="ts">
|
|
2162
|
+
import {onMounted, useTemplateRef} from 'vue';
|
|
2163
|
+
|
|
2164
|
+
import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';
|
|
2165
|
+
|
|
2166
|
+
import {cols, conf, rows} from './orders';
|
|
2167
|
+
|
|
2168
|
+
const table = useTemplateRef<TableBroHTMLElement>('table');
|
|
2169
|
+
|
|
2170
|
+
onMounted(() => table.value!.configure({cols, rows, conf}));
|
|
2171
|
+
|
|
2172
|
+
function onAction(event: CustomEvent<BroActionDetail>) {
|
|
2173
|
+
console.log(event.detail.action);
|
|
2174
|
+
}
|
|
2175
|
+
</script>
|
|
2176
|
+
|
|
2177
|
+
<template>
|
|
2178
|
+
<div class="bro-table-shadow-container">
|
|
2179
|
+
<table-bro id="orders" ref="table" theme="auto" @bro-action="onAction"></table-bro>
|
|
2180
|
+
</div>
|
|
2181
|
+
</template>
|
|
2182
|
+
` },
|
|
2183
|
+
{ file: 'src/main.ts', language: 'ts', code: `// The skin is registered before the tables
|
|
2184
|
+
import './skin';
|
|
2185
|
+
|
|
2186
|
+
import {createApp} from 'vue';
|
|
2187
|
+
|
|
2188
|
+
import OrdersTable from './OrdersTable.vue';
|
|
2189
|
+
|
|
2190
|
+
createApp(OrdersTable).mount('#app');
|
|
2191
|
+
` }
|
|
2192
|
+
]
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
path: 'ssr/prerendering',
|
|
2196
|
+
name: 'Prerendering',
|
|
2197
|
+
text: `<!-- intro -->
|
|
2198
|
+
A page that a server renders shows the table before any script runs. The prerender renders the table in its loading state: the toolbar, the column headers, the loading row and the paginator, in the markup of its skin. The CSS of the skin sizes it, so the table takes its place on the page at once, and the page has no layout shift when the table renders in the browser. There the table renders with its rows in the same element, and the loading animation goes on instead of starting over.
|
|
2199
|
+
|
|
2200
|
+
<!-- angular -->
|
|
2201
|
+
#### Angular SSR _since 1.2.0_
|
|
2202
|
+
|
|
2203
|
+
\`‹table-bro-ng›\` prerenders the table itself during the server-side rendering of Angular, in the skins \`mat\`, \`bs\` and \`nv\`: the component of the table stays as it is in the browser. The application needs only the server render of Angular ([Angular, Server-side and hybrid rendering](https://angular.dev/guide/ssr)): the steps below are the ones of the project \`angular-ssr\` of the [installation check](https://github.com/table-bro/table-bro/blob/main/scripts/check-install.mjs) of Table, Bro, which prerenders a page with a table at the build and hydrates it in the browser.
|
|
2204
|
+
|
|
2205
|
+
1. **Install the server render of Angular**
|
|
2206
|
+
|
|
2207
|
+
{{install-ssr.sh}}
|
|
2208
|
+
|
|
2209
|
+
2. **Build the entry of the server**
|
|
2210
|
+
|
|
2211
|
+
To \`"options"\` of the build in \`angular.json\`: the entry of the server, and the prerender of the pages at the build.
|
|
2212
|
+
|
|
2213
|
+
{{angular.json}}
|
|
2214
|
+
|
|
2215
|
+
3. **Start the application on the server**
|
|
2216
|
+
|
|
2217
|
+
\`src/main.server.ts\` starts the application with its config and the server render of its routes:
|
|
2218
|
+
|
|
2219
|
+
{{src/main.server.ts}}
|
|
2220
|
+
|
|
2221
|
+
4. **Hydrate the page in the browser**
|
|
2222
|
+
|
|
2223
|
+
\`provideClientHydration()\` joins the providers of the application, next to the skin:
|
|
2224
|
+
|
|
2225
|
+
{{src/app.config.ts}}
|
|
2226
|
+
|
|
2227
|
+
5. **Keep the table as it is**
|
|
2228
|
+
|
|
2229
|
+
{{src/app.ts}}
|
|
2230
|
+
|
|
2231
|
+
The skin \`mat\` renders its elements on the server with the Angular components that \`provideTableBroMatSkin()\` provides. The skins \`bs\` and \`nv\` render their Custom Elements there when the server render loads the package of the skin: import \`@table-bro/bootstrap\` or \`@table-bro/native\` in a file that the server bundle has too, as \`app.config.ts\` or the component of the table. The import works without a DOM: it registers the skin and defines no elements. A skin that only \`main.ts\` registers is a skin of the browser: \`‹table-bro-ng›\` stays empty on the server, and the table renders in the browser.
|
|
2232
|
+
|
|
2233
|
+
In the browser the first table of \`‹table-bro-ng›\` renders in the prerendered \`‹table-bro›\`, and the hydration of Angular does not check it.
|
|
2234
|
+
|
|
2235
|
+
<!-- function -->
|
|
2236
|
+
#### prerenderTableBro() _since 1.2.0_
|
|
2237
|
+
|
|
2238
|
+
Without Angular, \`prerenderTableBro(element, {cols, conf})\` of \`@table-bro/core\` renders the table into a \`‹table-bro›\` element of a server DOM, a DOM without a browser. It takes the columns and the config of \`configure()\`; the rows wait for the browser. The table creates its nodes in the document of the element, so the server needs no global \`document\`, and the imports of the core and of the skins \`bs\` and \`nv\` work without a DOM. The element goes into its document before the call, so that the elements of the skin render their markup as they connect:
|
|
2239
|
+
|
|
2240
|
+
{{src/prerender.ts}}
|
|
2241
|
+
|
|
2242
|
+
In the browser, \`configure()\` on the same element renders the table with its rows in place of the prerender:
|
|
2243
|
+
|
|
2244
|
+
{{src/browser.ts}}
|
|
2245
|
+
|
|
2246
|
+
The columns, the rows and the config come from \`src/orders.ts\`:
|
|
2247
|
+
|
|
2248
|
+
{{src/orders.ts}}
|
|
2249
|
+
|
|
2250
|
+
<!-- limits -->
|
|
2251
|
+
#### Limits
|
|
2252
|
+
|
|
2253
|
+
1. **The loading state only**
|
|
2254
|
+
|
|
2255
|
+
The prerender renders the table without rows, in its loading state: the rows, the sort and the pages come with the table of the browser.
|
|
2256
|
+
|
|
2257
|
+
2. **The theme auto**
|
|
2258
|
+
|
|
2259
|
+
The server does not know the color scheme of the system of the user. With the theme \`auto\` the prerendered table takes no class of a theme and shows the theme of the page; the table of the browser then follows the system. More in [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode).
|
|
2260
|
+
|
|
2261
|
+
3. **The elements of the skin**
|
|
2262
|
+
|
|
2263
|
+
The elements of a skin render on the server only when the skin defines them in a registry of custom elements, \`BroSkinConfig.defineElements(registry)\`, as \`bs\` and \`nv\` do, and the window of the server DOM has that registry, \`customElements\`. Without them the toolbar, the column headers and the paginator stay the empty tags of the skin with their attributes until the browser renders the table. \`‹table-bro-ng›\` gives the server DOM of Angular a registry for the time of the prerender, and renders the elements of \`mat\` with its Angular components.
|
|
2264
|
+
|
|
2265
|
+
4. **Critical CSS**
|
|
2266
|
+
|
|
2267
|
+
Angular inlines the critical CSS of a prerendered page with Beasties, which drops the selectors that start with a pseudo-class. The CSS of the core and of the skins keeps the layout of a prerendered table there: the sizes of a skin come also by \`table-bro:where([data-bro-prerender].<skin>)\`, so the prerendered table keeps its size before the stylesheets of the page load.
|
|
2268
|
+
|
|
2269
|
+
<!-- related -->
|
|
2270
|
+
#### Related
|
|
2271
|
+
|
|
2272
|
+
[Angular data table](https://table-bro.com/docs/frameworks/angular), [Animated table states](https://table-bro.com/docs/basics/table-states), [Dark/Light mode](https://table-bro.com/docs/design/dark-light-mode), [Fixed row height](https://table-bro.com/docs/basics/fixed-row-height).`,
|
|
2273
|
+
files: [
|
|
2274
|
+
{ file: 'install-ssr.sh', language: 'bash', code: `npm install @angular/platform-server @angular/ssr
|
|
2275
|
+
` },
|
|
2276
|
+
{ file: 'angular.json', language: 'json', code: `"server": "src/main.server.ts",
|
|
2277
|
+
"outputMode": "static"
|
|
2278
|
+
` },
|
|
2279
|
+
{ file: 'src/main.server.ts', language: 'ts', code: `import {mergeApplicationConfig} from '@angular/core';
|
|
2280
|
+
import {bootstrapApplication, type BootstrapContext} from '@angular/platform-browser';
|
|
2281
|
+
import {provideServerRendering, RenderMode, withRoutes} from '@angular/ssr';
|
|
2282
|
+
|
|
2283
|
+
import {App} from './app';
|
|
2284
|
+
import {appConfig} from './app.config';
|
|
2285
|
+
|
|
2286
|
+
const serverConfig = mergeApplicationConfig(appConfig, {providers: [provideServerRendering(withRoutes([{path: '**', renderMode: RenderMode.Prerender}]))]});
|
|
2287
|
+
|
|
2288
|
+
export default (context: BootstrapContext) => bootstrapApplication(App, serverConfig, context);
|
|
2289
|
+
` },
|
|
2290
|
+
{ file: 'src/app.config.ts', language: 'ts', code: `import {type ApplicationConfig, provideZonelessChangeDetection} from '@angular/core';
|
|
2291
|
+
import {provideClientHydration} from '@angular/platform-browser';
|
|
2292
|
+
|
|
2293
|
+
import {provideTableBroMatSkin} from '@table-bro/angular-material';
|
|
2294
|
+
|
|
2295
|
+
export const appConfig: ApplicationConfig = {
|
|
2296
|
+
providers: [
|
|
2297
|
+
provideZonelessChangeDetection(),
|
|
2298
|
+
provideTableBroMatSkin(),
|
|
2299
|
+
provideClientHydration()
|
|
2300
|
+
]
|
|
2301
|
+
};
|
|
2302
|
+
` },
|
|
2303
|
+
{ file: 'src/app.ts', language: 'ts', code: `import {Component} from '@angular/core';
|
|
2304
|
+
|
|
2305
|
+
import {TableBroNg} from '@table-bro/angular';
|
|
2306
|
+
import type {BroCol, BroConfig} from '@table-bro/core';
|
|
2307
|
+
|
|
2308
|
+
interface Price {
|
|
2309
|
+
name: string;
|
|
2310
|
+
price: number;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// The template of the browser: the server render of Angular prerenders the table in its loading state
|
|
2314
|
+
@Component({
|
|
2315
|
+
selector: 'app-root',
|
|
2316
|
+
template: '<table-bro-ng [cols]="cols" [rows]="rows" [conf]="conf"></table-bro-ng>',
|
|
2317
|
+
imports: [
|
|
2318
|
+
TableBroNg
|
|
2319
|
+
]
|
|
2320
|
+
})
|
|
2321
|
+
export class App {
|
|
2322
|
+
cols: BroCol<Price>[] = [
|
|
2323
|
+
{
|
|
2324
|
+
title: 'Name',
|
|
2325
|
+
name: 'name'
|
|
2326
|
+
}, {
|
|
2327
|
+
title: 'Price',
|
|
2328
|
+
name: 'price',
|
|
2329
|
+
dataType: 'number'
|
|
2330
|
+
}
|
|
2331
|
+
];
|
|
2332
|
+
|
|
2333
|
+
rows: Price[] = [
|
|
2334
|
+
{
|
|
2335
|
+
name: 'b',
|
|
2336
|
+
price: 3
|
|
2337
|
+
}, {
|
|
2338
|
+
name: 'a',
|
|
2339
|
+
price: 1
|
|
2340
|
+
}, {
|
|
2341
|
+
name: 'c',
|
|
2342
|
+
price: 2
|
|
2343
|
+
}
|
|
2344
|
+
];
|
|
2345
|
+
|
|
2346
|
+
conf: BroConfig<Price> = {skin: 'mat'};
|
|
2347
|
+
}
|
|
2348
|
+
` },
|
|
2349
|
+
{ file: 'src/prerender.ts', language: 'ts', code: `import {prerenderTableBro} from '@table-bro/core';
|
|
2350
|
+
// The import registers the skin nv; without a DOM it defines no elements
|
|
2351
|
+
import '@table-bro/native';
|
|
2352
|
+
|
|
2353
|
+
import {cols, conf} from './orders';
|
|
2354
|
+
|
|
2355
|
+
/**
|
|
2356
|
+
* Renders the table of the orders in its loading state into a page of a server DOM. The element is in the document
|
|
2357
|
+
* first, so the elements of the skin connect and render their markup.
|
|
2358
|
+
* @param page - the document of the page in the server DOM
|
|
2359
|
+
*/
|
|
2360
|
+
export function prerenderOrders(page: Document): void {
|
|
2361
|
+
const table = page.createElement('table-bro');
|
|
2362
|
+
|
|
2363
|
+
page.body.append(table);
|
|
2364
|
+
prerenderTableBro(table, {cols, conf});
|
|
2365
|
+
}
|
|
2366
|
+
` },
|
|
2367
|
+
{ file: 'src/browser.ts', language: 'ts', code: `import '@table-bro/native';
|
|
2368
|
+
|
|
2369
|
+
import {cols, conf, rows} from './orders';
|
|
2370
|
+
|
|
2371
|
+
// The prerendered table of the page: configure() renders the table with its rows in its place
|
|
2372
|
+
document.querySelector('table-bro')!.configure({cols, rows, conf});
|
|
2373
|
+
` },
|
|
2374
|
+
frameworkOrders
|
|
2375
|
+
]
|
|
2376
|
+
},
|
|
2377
|
+
{
|
|
2378
|
+
path: 'accessibility/localization',
|
|
2379
|
+
name: 'Texts and localization',
|
|
2380
|
+
text: `<!-- table-localization -->
|
|
2381
|
+
\`config.texts\` gives the words of the table in the language of your application: the toolbar, its search line, the filter drawer, the paginator, the progress bar, the messages of the states and the column of the row numbers. A text that the config does not set keeps its English default. The code under the table gives every text with its default value, as the table below shows them: change the words for the language of your application.
|
|
2382
|
+
|
|
2383
|
+
<!-- table-localization code -->
|
|
2384
|
+
{{texts.ts}}
|
|
2385
|
+
|
|
2386
|
+
<!-- fields -->
|
|
2387
|
+
#### Texts by the part of the table _since 1.2.0_
|
|
2388
|
+
|
|
2389
|
+
The texts of the parts that the core renders come first, flat and named as the options of the config: \`rowCounterTitle\`, \`rowCounterLabel\` and \`rowDetailsLoadingMessage\`. The texts of a component of the skin go into the group named as the component, in the order of the components of the skin: \`filterDrawer\`, \`paginator\`, \`progressBar\`, \`state\`, \`toolbarMain\` and \`toolbarSearch\`. The labels of \`paginator\` have the names and the order of \`MatPaginatorIntl\` of Angular Material, from \`itemsPerPageLabel\` to \`getRangeLabel\`.
|
|
2390
|
+
|
|
2391
|
+
| Field | Default | Skins | Since |
|
|
2392
|
+
| --- | --- | --- | --- |
|
|
2393
|
+
| \`rowCounterTitle\` | No | mat, bs, nv | 1.2.0 |
|
|
2394
|
+
| \`rowCounterLabel\` | Row number | mat, bs, nv | 1.2.0 |
|
|
2395
|
+
| \`rowDetailsLoadingMessage\` | Loading details | mat, bs, nv | 1.2.0 |
|
|
2396
|
+
| \`filterDrawer.title\` | Filters | mat, bs, nv | 1.2.0 |
|
|
2397
|
+
| \`filterDrawer.close\` | Close | mat, bs, nv | 1.2.0 |
|
|
2398
|
+
| \`paginator.itemsPerPageLabel\` | Items per page: | bs, nv | 1.2.0 |
|
|
2399
|
+
| \`paginator.nextPageLabel\` | Next page | bs, nv | 1.2.0 |
|
|
2400
|
+
| \`paginator.previousPageLabel\` | Previous page | bs, nv | 1.2.0 |
|
|
2401
|
+
| \`paginator.firstPageLabel\` | First page | bs, nv | 1.2.0 |
|
|
2402
|
+
| \`paginator.lastPageLabel\` | Last page | bs, nv | 1.2.0 |
|
|
2403
|
+
| \`paginator.getRangeLabel(page, pageSize, length)\` | 1 – 5 of 20 | bs, nv | 1.2.0 |
|
|
2404
|
+
| \`paginator.label\` | Pagination | mat, bs, nv | 1.2.0 |
|
|
2405
|
+
| \`paginator.navigationLabel\` | Table page navigation | bs, nv | 1.2.0 |
|
|
2406
|
+
| \`paginator.firstText\` | First | bs, nv | 1.2.0 |
|
|
2407
|
+
| \`paginator.previousText\` | Previous | bs, nv | 1.2.0 |
|
|
2408
|
+
| \`paginator.nextText\` | Next | bs, nv | 1.2.0 |
|
|
2409
|
+
| \`paginator.lastText\` | Last | bs, nv | 1.2.0 |
|
|
2410
|
+
| \`progressBar.label\` | Loading progress | bs, nv | 1.2.0 |
|
|
2411
|
+
| \`state.loading\` | Loading | mat, bs, nv | 1.2.0 |
|
|
2412
|
+
| \`state['no-data']\` | No data | mat, bs, nv | 1.2.0 |
|
|
2413
|
+
| \`state['not-found']\` | No records found | mat, bs, nv | 1.2.0 |
|
|
2414
|
+
| \`state.fail\` | Failed | mat, bs, nv | 1.2.0 |
|
|
2415
|
+
| \`toolbarMain.search\` | Search | mat, bs, nv | 1.2.0 |
|
|
2416
|
+
| \`toolbarMain.searchRole(title)\` | for Orders, by the title of the toolbar; for rows without a title | bs, nv | 1.2.0 |
|
|
2417
|
+
| \`toolbarMain.buttonRole\` | button | bs, nv | 1.2.0 |
|
|
2418
|
+
| \`toolbarMain.menuRole\` | menu | bs | 1.2.0 |
|
|
2419
|
+
| \`toolbarMain.menuItemRole\` | menu item | bs | 1.2.0 |
|
|
2420
|
+
| \`toolbarSearch.field\` | Search in table | mat, bs, nv | 1.2.0 |
|
|
2421
|
+
| \`toolbarSearch.back\` | Back | mat, bs, nv | 1.2.0 |
|
|
2422
|
+
| \`toolbarSearch.clear\` | Clear | mat, bs, nv | 1.2.0 |
|
|
2423
|
+
|
|
2424
|
+
The flat fields of 1.1.0, as \`texts.itemsPerPage\` for \`texts.paginator.itemsPerPageLabel\` and \`texts.rowCounter\` for \`texts.rowCounterTitle\`, still work and are deprecated: the editor marks them, and 2.0 removes them. A field in a group and a text of the core win over their flat field.
|
|
2425
|
+
|
|
2426
|
+
A field that is \`undefined\`, as \`{toolbarMain: {search: undefined}}\`, changes nothing: the text stays the default one, or the flat field of 1.1.0 below it.
|
|
2427
|
+
|
|
2428
|
+
<!-- states -->
|
|
2429
|
+
#### Messages of the states
|
|
2430
|
+
|
|
2431
|
+
The group \`state\` gives the messages of the states by their names, \`loading\`, \`no-data\`, \`not-found\` and \`fail\`; the icons stay the ones of the skin. The states, their icons and their Lottie animations are on the page [Animated table states](https://table-bro.com/docs/basics/table-states).
|
|
2432
|
+
|
|
2433
|
+
<!-- drawer -->
|
|
2434
|
+
#### Filter drawer
|
|
2435
|
+
|
|
2436
|
+
The filter drawer of every skin takes its title and the name of its close button from \`texts.filterDrawer\` of the config of its table, \`title\` and \`close\`. The container of Bootstrap and Native reads them when it connects and when it opens, so a new \`configure()\` of the table shows at the next opening. The property \`texts\` of \`‹table-bro-filter-drawer-container›\` of 1.1.0 still works and wins over the config of the table; it is deprecated and removed in 2.0.
|
|
2437
|
+
|
|
2438
|
+
<!-- tags -->
|
|
2439
|
+
#### Tags without items _since 1.2.0_
|
|
2440
|
+
|
|
2441
|
+
A cell of tags without items shows No data. The text belongs to the tags of the skin: \`withTags({texts: {empty: 'No data'}})\` gives it, and later \`setTagsTexts({empty})\` of \`@table-bro/bootstrap\` and \`@table-bro/native\`, or the signal \`empty\` of the service \`TableBroMatTagsTexts\` of \`@table-bro/angular-material\`, changes it at any time: the tags on the page take it at once. The deprecated \`texts.noItems\` of a config still works below them.
|
|
2442
|
+
|
|
2443
|
+
{{tags.ts}}
|
|
2444
|
+
|
|
2445
|
+
<!-- paginator -->
|
|
2446
|
+
#### Paginator of Angular Material
|
|
2447
|
+
|
|
2448
|
+
The skin \`mat\` renders the paginator of Angular Material, which takes its texts from \`MatPaginatorIntl\` of the application, as every \`mat-paginator\` does. Of the group \`paginator\`, the skin \`mat\` reads only \`label\`, the name of the paginator. Provide your \`MatPaginatorIntl\` once, in the config of the application:
|
|
2449
|
+
|
|
2450
|
+
{{paginator-intl.ts}}
|
|
2451
|
+
|
|
2452
|
+
{{app.config.ts}}
|
|
2453
|
+
|
|
2454
|
+
<!-- language -->
|
|
2455
|
+
#### Change of the language
|
|
2456
|
+
|
|
2457
|
+
The table takes its texts with its config. A new language is a new config: \`configure({cols, rows, conf})\` with the new \`texts\`, or a new value of the input \`conf\` of \`‹table-bro-ng›\`, renders the table anew. The filter drawer takes the new texts of its table, with Bootstrap and Native at its next opening, the tags take their new text from \`setTagsTexts()\` or \`TableBroMatTagsTexts\`, and the paginator of Angular Material takes the new labels of \`MatPaginatorIntl\` after its \`changes.next()\`.
|
|
2458
|
+
|
|
2459
|
+
{{language.ts}}`,
|
|
2460
|
+
files: [
|
|
2461
|
+
{ file: 'texts.ts', language: 'ts', code: `import type {BroConfig, BroTextsConfig} from '@table-bro/core';
|
|
2462
|
+
|
|
2463
|
+
/** The numbers of the range of the paginator, as 1,234. */
|
|
2464
|
+
const rangeNumber = new Intl.NumberFormat('en-US');
|
|
2465
|
+
|
|
2466
|
+
// Every text of the table with its default value: change the words for the language of your application
|
|
2467
|
+
export const texts: BroTextsConfig = {
|
|
2468
|
+
// Core texts
|
|
2469
|
+
rowCounterTitle: 'No',
|
|
2470
|
+
rowCounterLabel: 'Row number',
|
|
2471
|
+
rowDetailsLoadingMessage: 'Loading details',
|
|
2472
|
+
|
|
2473
|
+
// Skin component texts
|
|
2474
|
+
filterDrawer: {
|
|
2475
|
+
title: 'Filters',
|
|
2476
|
+
close: 'Close'
|
|
2477
|
+
},
|
|
2478
|
+
paginator: {
|
|
2479
|
+
itemsPerPageLabel: 'Items per page:',
|
|
2480
|
+
nextPageLabel: 'Next page',
|
|
2481
|
+
previousPageLabel: 'Previous page',
|
|
2482
|
+
firstPageLabel: 'First page',
|
|
2483
|
+
lastPageLabel: 'Last page',
|
|
2484
|
+
getRangeLabel: (page: number, pageSize: number, length: number) => {
|
|
2485
|
+
if (length === 0 || pageSize === 0) {
|
|
2486
|
+
return \`0 of \${rangeNumber.format(length)}\`;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
length = Math.max(length, 0);
|
|
2490
|
+
|
|
2491
|
+
const start = page * pageSize;
|
|
2492
|
+
// A start after the end of the list keeps the end of its page
|
|
2493
|
+
const end = (start < length) ? Math.min(start + pageSize, length) : start + pageSize;
|
|
2494
|
+
|
|
2495
|
+
return \`\${rangeNumber.format(start + 1)} – \${rangeNumber.format(end)} of \${rangeNumber.format(length)}\`;
|
|
2496
|
+
},
|
|
2497
|
+
label: 'Pagination',
|
|
2498
|
+
navigationLabel: 'Table page navigation',
|
|
2499
|
+
firstText: 'First',
|
|
2500
|
+
previousText: 'Previous',
|
|
2501
|
+
nextText: 'Next',
|
|
2502
|
+
lastText: 'Last'
|
|
2503
|
+
},
|
|
2504
|
+
progressBar: {
|
|
2505
|
+
label: 'Loading progress'
|
|
2506
|
+
},
|
|
2507
|
+
state: {
|
|
2508
|
+
loading: 'Loading',
|
|
2509
|
+
'no-data': 'No data',
|
|
2510
|
+
'not-found': 'No records found',
|
|
2511
|
+
fail: 'Failed'
|
|
2512
|
+
},
|
|
2513
|
+
toolbarMain: {
|
|
2514
|
+
search: 'Search',
|
|
2515
|
+
searchRole: (title: string) => \`for \${title || 'rows'}\`,
|
|
2516
|
+
buttonRole: 'button',
|
|
2517
|
+
menuRole: 'menu',
|
|
2518
|
+
menuItemRole: 'menu item'
|
|
2519
|
+
},
|
|
2520
|
+
toolbarSearch: {
|
|
2521
|
+
field: 'Search in table',
|
|
2522
|
+
back: 'Back',
|
|
2523
|
+
clear: 'Clear'
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
|
|
2527
|
+
export const conf: BroConfig = {
|
|
2528
|
+
skin: 'bs',
|
|
2529
|
+
texts,
|
|
2530
|
+
toolbarTitle: 'Orders'
|
|
2531
|
+
};
|
|
2532
|
+
` },
|
|
2533
|
+
{ file: 'tags.ts', language: 'ts', code: `import {withTags} from '@table-bro/bootstrap';
|
|
2534
|
+
|
|
2535
|
+
// The tags of the skin bs, with the text of a cell without items
|
|
2536
|
+
withTags({texts: {empty: 'No data'}});
|
|
2537
|
+
` },
|
|
2538
|
+
{ file: 'paginator-intl.ts', language: 'ts', code: `import {Injectable} from '@angular/core';
|
|
2539
|
+
|
|
2540
|
+
import {MatPaginatorIntl} from '@angular/material/paginator';
|
|
2541
|
+
|
|
2542
|
+
/** The texts of the paginators of Angular Material with their default values: change the words for the language of your application. */
|
|
2543
|
+
@Injectable()
|
|
2544
|
+
export class AppPaginatorIntl extends MatPaginatorIntl {
|
|
2545
|
+
override itemsPerPageLabel = 'Items per page:';
|
|
2546
|
+
override nextPageLabel = 'Next page';
|
|
2547
|
+
override previousPageLabel = 'Previous page';
|
|
2548
|
+
override firstPageLabel = 'First page';
|
|
2549
|
+
override lastPageLabel = 'Last page';
|
|
2550
|
+
|
|
2551
|
+
override getRangeLabel = (page: number, pageSize: number, length: number): string => {
|
|
2552
|
+
if (length === 0 || pageSize === 0) {
|
|
2553
|
+
return \`0 of \${length}\`;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
length = Math.max(length, 0);
|
|
2557
|
+
|
|
2558
|
+
const start = page * pageSize;
|
|
2559
|
+
// A start after the end of the list keeps the end of its page
|
|
2560
|
+
const end = (start < length) ? Math.min(start + pageSize, length) : start + pageSize;
|
|
2561
|
+
|
|
2562
|
+
return \`\${start + 1} – \${end} of \${length}\`;
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
` },
|
|
2566
|
+
{ file: 'app.config.ts', language: 'ts', code: `import type {ApplicationConfig} from '@angular/core';
|
|
2567
|
+
|
|
2568
|
+
import {MatPaginatorIntl} from '@angular/material/paginator';
|
|
2569
|
+
import {provideTableBroMatSkin, withTags} from '@table-bro/angular-material';
|
|
2570
|
+
|
|
2571
|
+
import {AppPaginatorIntl} from './paginator-intl';
|
|
2572
|
+
|
|
2573
|
+
export const appConfig: ApplicationConfig = {
|
|
2574
|
+
providers: [
|
|
2575
|
+
provideTableBroMatSkin(withTags({texts: {empty: 'No data'}})),
|
|
2576
|
+
{provide: MatPaginatorIntl, useClass: AppPaginatorIntl}
|
|
2577
|
+
]
|
|
2578
|
+
};
|
|
2579
|
+
` },
|
|
2580
|
+
{ file: 'language.ts', language: 'ts', code: `import {setTagsTexts} from '@table-bro/bootstrap';
|
|
2581
|
+
import type {BroCol, BroConfig, BroRow, BroTextsConfig} from '@table-bro/core';
|
|
2582
|
+
|
|
2583
|
+
/**
|
|
2584
|
+
* Shows the table in another language: the new config renders the table anew, with the same columns and rows.
|
|
2585
|
+
* @param cols - the columns of the table
|
|
2586
|
+
* @param rows - the rows of the table
|
|
2587
|
+
* @param conf - the config of the table in its language before
|
|
2588
|
+
* @param texts - the texts of the table in the new language
|
|
2589
|
+
* @param empty - the text of a cell of tags without items in the new language
|
|
2590
|
+
*/
|
|
2591
|
+
export function setLanguage(cols: BroCol[], rows: BroRow[], conf: BroConfig, texts: BroTextsConfig, empty: string): void {
|
|
2592
|
+
document.querySelector('table-bro')!.configure({cols, rows, conf: {...conf, texts}});
|
|
2593
|
+
setTagsTexts({empty});
|
|
2594
|
+
}
|
|
2595
|
+
` }
|
|
2596
|
+
]
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
path: 'accessibility/keyboard-and-focus',
|
|
2600
|
+
name: 'Keyboard and focus',
|
|
2601
|
+
text: `<!-- order -->
|
|
2602
|
+
The table is HTML of the page, so Tab goes through its controls in the order of the DOM: the toolbar, the column headers, the rows of the current page and the paginator.
|
|
2603
|
+
|
|
2604
|
+
| Part | Angular Material | Bootstrap | Native |
|
|
2605
|
+
| --- | --- | --- | --- |
|
|
2606
|
+
| Toolbar | The search button, the buttons of the config and the menu: buttons of Material | The search link, the buttons of the config and the menu: links with the role \`button\` | The search link and the buttons of the config: links with the role \`button\`; the menu is a \`‹select›\` |
|
|
2607
|
+
| Search line | Back, the field, Clear: buttons of Material | Back, the field, Clear: Back and Clear are links with the role \`button\` | Back, the field, Clear: Back and Clear are links with the role \`button\` |
|
|
2608
|
+
| Column headers | The sort headers of Material | A header with a title: \`role="button"\` and \`tabindex="0"\` | A header with a title: \`role="button"\` and \`tabindex="0"\` |
|
|
2609
|
+
| Rows | The controls of the cells and the details button, a button of Material | The controls of the cells and the details button, a \`‹button›\` | The controls of the cells and the details button, a link with the role \`button\` |
|
|
2610
|
+
| Paginator | The paginator of Material | The page size dropdown, then the links First, Previous, Next and Last | The page size \`‹select›\`, then the links First, Previous, Next and Last |
|
|
2611
|
+
|
|
2612
|
+
All loaded rows are in the DOM, but only the rows of the current page take the focus: the controls of the other pages get \`tabindex="-1"\` and take their own \`tabindex\` back on their page. The tags that do not fit their cell take no focus either, until they show. A row or a paginator that the table renders anew, as for new rows, keeps the focus of the keyboard on its control.
|
|
2613
|
+
|
|
2614
|
+
<!-- keys -->
|
|
2615
|
+
#### Keys
|
|
2616
|
+
|
|
2617
|
+
1. **Links with the role button**
|
|
2618
|
+
|
|
2619
|
+
The toolbar links of Bootstrap and Native, Back and Clear of their search line and the details link of Native take Enter, as every link does, and Space, as a button does, instead of scrolling the page.
|
|
2620
|
+
|
|
2621
|
+
2. **Sort headers**
|
|
2622
|
+
|
|
2623
|
+
Enter and Space on a column header sort the column, as a click does: with Bootstrap and Native the header is a control of the skin, with Angular Material the sort header of Material.
|
|
2624
|
+
|
|
2625
|
+
3. **Row details**
|
|
2626
|
+
|
|
2627
|
+
Enter and Space on the details button open and close the details of its row, and the focus stays on the button.
|
|
2628
|
+
|
|
2629
|
+
4. **Search line**
|
|
2630
|
+
|
|
2631
|
+
The search button opens the search line and puts the focus into its field. Escape in the field closes the search line, as Back does, and the focus returns to the search button; Escape stays in the table, so a dialog around the table stays open. Clear empties the field and keeps the focus in it; Clear in an empty field closes the search line.
|
|
2632
|
+
|
|
2633
|
+
5. **Filter Drawer**
|
|
2634
|
+
|
|
2635
|
+
With Bootstrap and Native, the opened drawer takes the focus into your filters, or to its close button when the filters have no control. Escape in the drawer closes it, with Angular Material as in the drawer of Material. With every skin the closed drawer gives the focus back to the button that opened it, when the focus was in the drawer; after a click of the pointer the button shows no focus ring.
|
|
2636
|
+
|
|
2637
|
+
<!-- rings -->
|
|
2638
|
+
#### Focus rings
|
|
2639
|
+
|
|
2640
|
+
Each skin shows the focus of the keyboard in the look of its library:
|
|
2641
|
+
|
|
2642
|
+
1. **Bootstrap**: the focus ring of Bootstrap, from its variables \`--bs-focus-ring-*\`, also on the sort headers, which Bootstrap does not have.
|
|
2643
|
+
|
|
2644
|
+
2. **Native**: the outline of the browser; the skin keeps it one rectangle around the sort headers, the details link and the toolbar links, also in Safari.
|
|
2645
|
+
|
|
2646
|
+
3. **Angular Material**: the focus indication of the components of Material.
|
|
2647
|
+
|
|
2648
|
+
A control in a cell shows its whole focus ring: the cell, which cuts its content at its edges, lets the ring of a control in focus out.
|
|
2649
|
+
|
|
2650
|
+
<!-- safari -->
|
|
2651
|
+
#### Safari
|
|
2652
|
+
|
|
2653
|
+
By default Tab in Safari skips links. The toolbar links of Bootstrap and Native, Back and Clear of their search line, the links of their paginator and the details link of Native take the focus by Option+Tab, or by Tab once **Press Tab to highlight each item on a webpage** is on in the settings of Safari, Advanced.`,
|
|
2654
|
+
files: []
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
path: 'accessibility/screen-readers',
|
|
2658
|
+
name: 'Screen readers',
|
|
2659
|
+
text: `<!-- table -->
|
|
2660
|
+
The table renders its header and its body as two HTML tables; for screen readers they are one table: the element with \`role="table"\` holds them, its name is \`toolbarTitle\`, its rows have \`role="row"\`, and the header cells with a title are column headers. The table tells the number of all its rows by \`aria-rowcount\`, also of the rows of the server that are not loaded, the place of a row by \`aria-rowindex\`, the sort of a column by \`aria-sort\` on its header, and its loading and refreshing by \`aria-busy\`. Screen readers meet only the rows of the current page: the rows of the other pages are \`aria-hidden\`.
|
|
2661
|
+
|
|
2662
|
+
<!-- toolbar -->
|
|
2663
|
+
#### Roles and names of the toolbar
|
|
2664
|
+
|
|
2665
|
+
With Bootstrap and Native the controls of the toolbar are links with the role \`button\`, as Bootstrap gives it to a link that goes nowhere. A link says what it is with visually hidden words after its title, in the class \`bro-visually-hidden\` of the core, for screen readers and search engines, which read the text of a link:
|
|
2666
|
+
|
|
2667
|
+
| Field | Words | Text of the link | Skins |
|
|
2668
|
+
| --- | --- | --- | --- |
|
|
2669
|
+
| \`texts.toolbarMain.searchRole(title)\` | for Orders, by \`toolbarTitle\`; for rows without a title | Search for Orders | bs, nv |
|
|
2670
|
+
| \`texts.toolbarMain.buttonRole\` | button | Filters button | bs, nv |
|
|
2671
|
+
| \`texts.toolbarMain.menuRole\` | menu | More menu | bs |
|
|
2672
|
+
| \`texts.toolbarMain.menuItemRole\` | menu item | Print menu item | bs |
|
|
2673
|
+
|
|
2674
|
+
The word \`button\` is for search engines only: it is \`aria-hidden\`, since a screen reader says the role of the link itself. The items of the menu of Bootstrap stay links, as in the markup of Bootstrap; the menu of Native is a \`‹select›\` named by its title. An empty string adds no words:
|
|
2675
|
+
|
|
2676
|
+
{{texts-roles.ts}}
|
|
2677
|
+
|
|
2678
|
+
With Angular Material the controls of the toolbar are icon buttons of Material, named by \`aria-label\`: the search button by \`texts.toolbarMain.search\`, a button of the config and the menu by their \`buttonTitle\`.
|
|
2679
|
+
|
|
2680
|
+
<!-- live -->
|
|
2681
|
+
#### Live region
|
|
2682
|
+
|
|
2683
|
+
After the table the host has its live region, \`‹div class="bro-live-status bro-visually-hidden" role="status" aria-atomic="true"›\`, whose text screen readers say:
|
|
2684
|
+
|
|
2685
|
+
1. **The message of a state** when the table shows it: Loading, No data, No records found or Failed, from \`texts.state\` or from the skin. The icon of a state is \`aria-hidden\`: the state is named by its message. Once rows replace the state, the live region is empty.
|
|
2686
|
+
|
|
2687
|
+
2. **\`texts.rowDetailsLoadingMessage\`**, Loading details, while row details load from the server, until the details of every row load or leave.
|
|
2688
|
+
|
|
2689
|
+
The range of the paginator of Bootstrap and Native, as 6 – 10 of 20, is \`aria-live="polite"\`. The messages of the states are on the page [Animated table states](https://table-bro.com/docs/basics/table-states), and every text of the table on the page [Texts and localization](https://table-bro.com/docs/accessibility/localization).
|
|
2690
|
+
|
|
2691
|
+
<!-- names -->
|
|
2692
|
+
#### Names of the paginator, the row numbers and the details
|
|
2693
|
+
|
|
2694
|
+
1. **Paginator**
|
|
2695
|
+
|
|
2696
|
+
The paginator of every skin is a group named by \`texts.paginator.label\`, Pagination. With Bootstrap and Native its links are in a \`‹nav›\` named by \`texts.paginator.navigationLabel\`, Table page navigation; the links are named First page, Previous page, Next page and Last page, a disabled link has \`aria-disabled="true"\`, and the page sizes are named Items per page:. The paginator of Angular Material takes its names from \`MatPaginatorIntl\` of the application.
|
|
2697
|
+
|
|
2698
|
+
2. **Column of the row numbers**
|
|
2699
|
+
|
|
2700
|
+
The title of the column is short, No, and a screen reader would read it as a word: its header is named by \`texts.rowCounterLabel\`, Row number.
|
|
2701
|
+
|
|
2702
|
+
3. **Row details**
|
|
2703
|
+
|
|
2704
|
+
The details button says whether the details of its row are open by \`aria-expanded\` and points at them by \`aria-controls\`.
|
|
2705
|
+
|
|
2706
|
+
4. **Filter Drawer and progress bar**
|
|
2707
|
+
|
|
2708
|
+
With Bootstrap and Native, the filters of the drawer are a region named by \`texts.filterDrawer.title\`, Filters, unless they have a name of their own, and the button that opens the drawer tells its state by \`aria-expanded\` and \`aria-controls\`. The progress bar of Bootstrap and Native is named by \`texts.progressBar.label\`, Loading progress.`,
|
|
2709
|
+
files: [
|
|
2710
|
+
{ file: 'texts-roles.ts', language: 'ts', code: `import type {BroConfig} from '@table-bro/core';
|
|
2711
|
+
|
|
2712
|
+
export const conf: BroConfig = {
|
|
2713
|
+
skin: 'bs',
|
|
2714
|
+
toolbarTitle: 'Orders',
|
|
2715
|
+
texts: {
|
|
2716
|
+
toolbarMain: {
|
|
2717
|
+
// Search for Orders stays; the links of the buttons get no word after their titles
|
|
2718
|
+
buttonRole: ''
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
};
|
|
2722
|
+
` }
|
|
2723
|
+
]
|
|
2724
|
+
}
|
|
2725
|
+
];
|
|
2726
|
+
/** The text of a page as Markdown: without the marks of its parts, with its files of code in code blocks. */
|
|
2727
|
+
export function docPageMarkdown(page) {
|
|
2728
|
+
return page.text
|
|
2729
|
+
.replace(/^<!-- .+ -->\n(\n)?/gm, '')
|
|
2730
|
+
.replace(/^( *)\{\{(.+)\}\}$/gm, (line, indent, file) => {
|
|
2731
|
+
const code = page.files.find(item => item.file === file);
|
|
2732
|
+
return `${indent}${file}:\n\n${indent}\`\`\`${code.language}\n${code.code.trimEnd().replace(/^(?=.)/gm, indent)}\n${indent}\`\`\``;
|
|
2733
|
+
});
|
|
2734
|
+
}
|