@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.48 → 2.0.0-next.50
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/.storybook/ComponentPreview.tsx +26 -0
- package/.storybook/PreviewTemplate.tsx +99 -0
- package/.storybook/action-handler.ts +81 -0
- package/.storybook/channels.ts +44 -0
- package/.storybook/main.ts +313 -0
- package/.storybook/manager.ts +71 -0
- package/.storybook/openbridgeTheme.ts +197 -0
- package/.storybook/preview-head.html +15 -0
- package/.storybook/preview.tsx +129 -0
- package/.storybook/vitest.setup.ts +23 -0
- package/bundle/openbridge-webcomponents.bundle.js +196 -38
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +16 -1
- package/dist/automation/automation-tank/automation-tank.css.js +166 -28
- package/dist/automation/automation-tank/automation-tank.css.js.map +1 -1
- package/dist/automation/automation-tank/automation-tank.d.ts +32 -0
- package/dist/automation/automation-tank/automation-tank.d.ts.map +1 -1
- package/dist/automation/automation-tank/automation-tank.js +28 -0
- package/dist/automation/automation-tank/automation-tank.js.map +1 -1
- package/dist/components/icon-button/icon-button.d.ts +4 -0
- package/dist/components/icon-button/icon-button.d.ts.map +1 -1
- package/dist/components/icon-button/icon-button.js.map +1 -1
- package/dist/components/message-menu-item/message-menu-item.css.js +2 -10
- package/dist/components/message-menu-item/message-menu-item.css.js.map +1 -1
- package/eslint.config.mjs +589 -0
- package/fix-imports.mjs +185 -0
- package/fix-js-extensions.mjs +44 -0
- package/lit-localize.json +15 -0
- package/new-component.ts +148 -0
- package/package.json +55 -3
- package/postcss.config.mjs +195 -0
- package/script/check-css-mixins.ts +191 -0
- package/script/check-css-variables.ts +280 -0
- package/script/convert-icons.ts +202 -0
- package/script/convert-vessel-svg-to-ts.ts +110 -0
- package/script/docgen/README.md +95 -0
- package/script/docgen/docs-gen.ts +225 -0
- package/script/docgen/prompt-system.txt +187 -0
- package/script/download-alert-icons.ts +193 -0
- package/script/download-icons.ts +314 -0
- package/script/figmavariables.json +139 -0
- package/script/generate-bundle-entry.ts +77 -0
- package/script/prepare-full-bundle.ts +105 -0
- package/script/sort-custom-element-manifest.ts +67 -0
- package/src/automation/automation-tank/automation-tank.css +119 -21
- package/src/automation/automation-tank/automation-tank.stories.ts +38 -0
- package/src/automation/automation-tank/automation-tank.ts +64 -0
- package/src/components/icon-button/icon-button.ts +4 -0
- package/src/components/message-menu-item/message-menu-item.css +2 -5
- package/vite.config.ts +107 -0
- package/vitest.browser.config.ts +14 -0
- package/vitest.config.ts +51 -0
- package/xliff/es-419.xlf +111 -0
- package/xliff/fi-FI.xlf +139 -0
- package/dist/NotoSans.ttf +0 -0
- package/dist/oicl.svg +0 -4
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import {globSync} from 'glob';
|
|
4
|
+
|
|
5
|
+
const BUNDLE_FILE = 'bundle.ts';
|
|
6
|
+
|
|
7
|
+
function findComponentFiles(
|
|
8
|
+
directory: string,
|
|
9
|
+
excludeFiles: string[] = []
|
|
10
|
+
): string[] {
|
|
11
|
+
const components: string[] = [];
|
|
12
|
+
const fullPath = path.join(process.cwd(), directory);
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(fullPath)) {
|
|
15
|
+
return components;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const items = globSync(`${fullPath}/**/*.ts`, {
|
|
19
|
+
ignore: [
|
|
20
|
+
'**/*.stories.ts',
|
|
21
|
+
'**/*.spec.ts',
|
|
22
|
+
'**/*.test.ts',
|
|
23
|
+
'**/_test-utils.ts',
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
const rootPath = path.join(process.cwd());
|
|
27
|
+
|
|
28
|
+
for (const item of items) {
|
|
29
|
+
if (
|
|
30
|
+
item.endsWith('.ts') &&
|
|
31
|
+
!item.endsWith('.stories.ts') &&
|
|
32
|
+
!excludeFiles.includes(item)
|
|
33
|
+
) {
|
|
34
|
+
const filename = item.replace('.ts', '');
|
|
35
|
+
const relativePath = filename.replace(rootPath, '');
|
|
36
|
+
|
|
37
|
+
components.push(relativePath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return components.sort((a, b) => a.localeCompare(b));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function generateBundleFile() {
|
|
45
|
+
console.log(`Generating ${BUNDLE_FILE}...`);
|
|
46
|
+
|
|
47
|
+
let content = `// Bundle entry file - imports all web components for standalone browser usage
|
|
48
|
+
// Auto-generated by script/generate-bundle-entry.ts - do not edit manually
|
|
49
|
+
|
|
50
|
+
// Types
|
|
51
|
+
export * from './src/types.js';
|
|
52
|
+
|
|
53
|
+
`;
|
|
54
|
+
const folders = fs
|
|
55
|
+
.readdirSync('src')
|
|
56
|
+
.map((dir) => path.join('src', dir))
|
|
57
|
+
.filter((dir) => fs.statSync(dir).isDirectory());
|
|
58
|
+
|
|
59
|
+
for (const folder of folders) {
|
|
60
|
+
console.log(`Adding components from ${folder}...`);
|
|
61
|
+
const components = findComponentFiles(folder);
|
|
62
|
+
|
|
63
|
+
if (components.length > 0) {
|
|
64
|
+
content += `// ${folder}\n`;
|
|
65
|
+
for (const comp of components) {
|
|
66
|
+
content += `import '.${comp}.js';\n`;
|
|
67
|
+
}
|
|
68
|
+
content += '\n';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(BUNDLE_FILE, content, 'utf-8');
|
|
73
|
+
|
|
74
|
+
console.log(`✓ Generated ${BUNDLE_FILE} with all component imports`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
generateBundleFile();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
const scriptPath = path.resolve(process.argv[1]);
|
|
5
|
+
const scriptDirectory = path.dirname(scriptPath);
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(scriptDirectory, '..');
|
|
8
|
+
const sourcePackagePath = path.join(packageRoot, 'package.json');
|
|
9
|
+
const fullBundlePackageRoot = path.join(packageRoot, '.full-bundle-publish');
|
|
10
|
+
const sourcePackage = JSON.parse(fs.readFileSync(sourcePackagePath, 'utf-8'));
|
|
11
|
+
const releaseVersion = process.argv[2] ?? sourcePackage.version;
|
|
12
|
+
const {scripts: _scripts, ...sourcePackageWithoutScripts} = sourcePackage;
|
|
13
|
+
|
|
14
|
+
const nonWorkingScripts = new Set([
|
|
15
|
+
'wrappers',
|
|
16
|
+
'build:full',
|
|
17
|
+
'wrappers:clean',
|
|
18
|
+
'wrappers:build',
|
|
19
|
+
'wrappers:generate',
|
|
20
|
+
'wrappers:post-fix',
|
|
21
|
+
'test-storybook:docker',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const scripts = Object.fromEntries(
|
|
25
|
+
Object.entries(_scripts).filter(([name]) => !nonWorkingScripts.has(name))
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const fullBundlePackage = {
|
|
29
|
+
...sourcePackageWithoutScripts,
|
|
30
|
+
name: '@oicl/openbridge-webcomponents-full-bundle',
|
|
31
|
+
version: releaseVersion,
|
|
32
|
+
scripts,
|
|
33
|
+
files: [
|
|
34
|
+
'dist',
|
|
35
|
+
'!dist/AR-test-image.png',
|
|
36
|
+
'!dist/companylogo-day.png',
|
|
37
|
+
'bundle/openbridge-webcomponents.bundle.js',
|
|
38
|
+
'bundle/openbridge-webcomponents.bundle.js.map',
|
|
39
|
+
'.storybook',
|
|
40
|
+
'script',
|
|
41
|
+
'xliff',
|
|
42
|
+
'src',
|
|
43
|
+
'docs',
|
|
44
|
+
'custom-elements.json',
|
|
45
|
+
'tsconfig.json',
|
|
46
|
+
'eslint.config.mjs',
|
|
47
|
+
'vite.config.ts',
|
|
48
|
+
'postcss.config.mjs',
|
|
49
|
+
'lit-localize.json',
|
|
50
|
+
'new-component.ts',
|
|
51
|
+
'fix-imports.mjs',
|
|
52
|
+
'fix-js-extensions.mjs',
|
|
53
|
+
'vitest.browser.config.ts',
|
|
54
|
+
'vitest.config.ts',
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
fs.rmSync(fullBundlePackageRoot, {recursive: true, force: true});
|
|
59
|
+
fs.mkdirSync(fullBundlePackageRoot, {recursive: true});
|
|
60
|
+
|
|
61
|
+
for (const dirName of [
|
|
62
|
+
'dist',
|
|
63
|
+
'bundle',
|
|
64
|
+
'src',
|
|
65
|
+
'docs',
|
|
66
|
+
'xliff',
|
|
67
|
+
'script',
|
|
68
|
+
'.storybook',
|
|
69
|
+
]) {
|
|
70
|
+
fs.cpSync(
|
|
71
|
+
path.join(packageRoot, dirName),
|
|
72
|
+
path.join(fullBundlePackageRoot, dirName),
|
|
73
|
+
{
|
|
74
|
+
recursive: true,
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const file of [
|
|
80
|
+
'custom-elements.json',
|
|
81
|
+
'tsconfig.json',
|
|
82
|
+
'vite.config.ts',
|
|
83
|
+
'eslint.config.mjs',
|
|
84
|
+
'postcss.config.mjs',
|
|
85
|
+
'lit-localize.json',
|
|
86
|
+
'new-component.ts',
|
|
87
|
+
'fix-imports.mjs',
|
|
88
|
+
'fix-js-extensions.mjs',
|
|
89
|
+
'vitest.browser.config.ts',
|
|
90
|
+
'vitest.config.ts',
|
|
91
|
+
]) {
|
|
92
|
+
fs.copyFileSync(
|
|
93
|
+
path.join(packageRoot, file),
|
|
94
|
+
path.join(fullBundlePackageRoot, file)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(fullBundlePackageRoot, 'package.json'),
|
|
100
|
+
JSON.stringify(fullBundlePackage, null, 2) + '\n'
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
console.log(
|
|
104
|
+
`✓ Prepared full-bundle publish directory: ${fullBundlePackageRoot}`
|
|
105
|
+
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
interface Declaration {
|
|
5
|
+
kind: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Module {
|
|
11
|
+
kind: string;
|
|
12
|
+
path: string;
|
|
13
|
+
declarations?: Declaration[];
|
|
14
|
+
exports?: unknown[];
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CustomElementsJson {
|
|
19
|
+
schemaVersion: string;
|
|
20
|
+
readme: string;
|
|
21
|
+
modules: Module[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const filePath = path.join(process.cwd(), 'custom-elements.json');
|
|
25
|
+
|
|
26
|
+
const data: CustomElementsJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
27
|
+
|
|
28
|
+
const filteredModules = data.modules
|
|
29
|
+
.map((module): Module | null => {
|
|
30
|
+
if (!module.declarations || !Array.isArray(module.declarations)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const filteredDeclarations = module.declarations.filter(
|
|
35
|
+
(declaration: Declaration) => {
|
|
36
|
+
return declaration.name && declaration.name.startsWith('Obc');
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (filteredDeclarations.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
...module,
|
|
46
|
+
declarations: filteredDeclarations,
|
|
47
|
+
};
|
|
48
|
+
})
|
|
49
|
+
.filter((module): module is Module => module !== null);
|
|
50
|
+
|
|
51
|
+
filteredModules.sort((a, b) => {
|
|
52
|
+
const pathA = a.path || '';
|
|
53
|
+
const pathB = b.path || '';
|
|
54
|
+
return pathA.localeCompare(pathB);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const result: CustomElementsJson = {
|
|
58
|
+
...data,
|
|
59
|
+
modules: filteredModules,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(filePath, JSON.stringify(result, null, 2) + '\n', 'utf8');
|
|
63
|
+
|
|
64
|
+
console.log(
|
|
65
|
+
`Filtered and sorted ${filteredModules.length} modules with Obc declarations`
|
|
66
|
+
);
|
|
67
|
+
console.log(`Original file had ${data.modules.length} modules`);
|
|
@@ -212,14 +212,6 @@
|
|
|
212
212
|
:host([static]) .halo > .tag {
|
|
213
213
|
flex: 0 0 auto;
|
|
214
214
|
}
|
|
215
|
-
/* Override the global `min-height: 24px` on `.badges` so empty badge rows
|
|
216
|
-
* don't reserve space in compact/static — the spec says "if there are no
|
|
217
|
-
* badges it would grow upward". The slot-driven `?hidden` attribute on
|
|
218
|
-
* empty cells makes them `display: none` (see below). */
|
|
219
|
-
:host([compact]) .halo > .badges,
|
|
220
|
-
:host([static]) .halo > .badges {
|
|
221
|
-
min-height: 0;
|
|
222
|
-
}
|
|
223
215
|
/* `hidden` attribute support: needed because the cells set an explicit
|
|
224
216
|
* display value (`flex` / `block`), which would otherwise override
|
|
225
217
|
* `hidden`'s default `display: none`. Removing the cell from the flex
|
|
@@ -331,7 +323,7 @@
|
|
|
331
323
|
grid-row: 1;
|
|
332
324
|
grid-column: 2;
|
|
333
325
|
justify-self: end;
|
|
334
|
-
align-self:
|
|
326
|
+
align-self: start;
|
|
335
327
|
}
|
|
336
328
|
.tank-frame:not(.orientation-horizontal) .grid > .readout {
|
|
337
329
|
grid-row: 2;
|
|
@@ -357,7 +349,7 @@
|
|
|
357
349
|
grid-row: 1;
|
|
358
350
|
grid-column: 2;
|
|
359
351
|
justify-self: end;
|
|
360
|
-
align-self:
|
|
352
|
+
align-self: start;
|
|
361
353
|
}
|
|
362
354
|
.tank-frame.orientation-horizontal .grid > .bar-container {
|
|
363
355
|
grid-row: 2;
|
|
@@ -367,7 +359,6 @@
|
|
|
367
359
|
grid-row: 2;
|
|
368
360
|
grid-column: 2;
|
|
369
361
|
align-self: stretch;
|
|
370
|
-
padding-left: 8px;
|
|
371
362
|
min-width: 96px;
|
|
372
363
|
}
|
|
373
364
|
|
|
@@ -375,7 +366,7 @@
|
|
|
375
366
|
.badges {
|
|
376
367
|
display: flex;
|
|
377
368
|
justify-content: end;
|
|
378
|
-
|
|
369
|
+
gap: 4px;
|
|
379
370
|
pointer-events: auto;
|
|
380
371
|
}
|
|
381
372
|
|
|
@@ -383,7 +374,7 @@
|
|
|
383
374
|
display: block;
|
|
384
375
|
pointer-events: auto;
|
|
385
376
|
@mixin font-instrument-label;
|
|
386
|
-
color: var(--
|
|
377
|
+
color: var(--element-neutral-color);
|
|
387
378
|
/* Tag text must never break across lines — it is a fixed identifier
|
|
388
379
|
* (e.g. `FUEL`, `#0000`) that loses meaning if hyphenated or wrapped. */
|
|
389
380
|
white-space: nowrap;
|
|
@@ -403,11 +394,31 @@
|
|
|
403
394
|
/* Keep `90%` together — the parent flex prevents flex-item wrapping but
|
|
404
395
|
* not text wrapping inside `.percent` (number + `<span>%</span>`). */
|
|
405
396
|
white-space: nowrap;
|
|
406
|
-
|
|
407
|
-
|
|
397
|
+
color: var(--element-neutral-color);
|
|
398
|
+
|
|
399
|
+
.percent {
|
|
400
|
+
@mixin font-instrument-value-regular-neutral;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.percent-symbol {
|
|
404
|
+
@mixin font-instrument-unit;
|
|
405
|
+
margin-left: 1px;
|
|
406
|
+
}
|
|
408
407
|
}
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
|
|
409
|
+
/* Static readout: max-value + unit (no percent, no trend icon). Tighter
|
|
410
|
+
* 2px gap and small-neutral value sizing per design spec. Color is
|
|
411
|
+
* inherited from `.readout-compact`. */
|
|
412
|
+
.readout-static {
|
|
413
|
+
gap: 2px;
|
|
414
|
+
|
|
415
|
+
slot[name="max-value"] {
|
|
416
|
+
@mixin font-instrument-value-small-neutral;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
slot[name="unit"] {
|
|
420
|
+
@mixin font-instrument-unit;
|
|
421
|
+
}
|
|
411
422
|
}
|
|
412
423
|
|
|
413
424
|
.header {
|
|
@@ -415,18 +426,23 @@
|
|
|
415
426
|
align-items: center;
|
|
416
427
|
justify-content: flex-end;
|
|
417
428
|
gap: 4px;
|
|
418
|
-
/* TODO: we will need such divider in the future for the 'rich' option */
|
|
419
|
-
/* border-bottom: 1px solid var(--border-outline-color); */
|
|
420
|
-
padding-bottom: 2px;
|
|
421
429
|
|
|
422
430
|
.percent {
|
|
423
431
|
@mixin font-instrument-value-enhanced-neutral;
|
|
432
|
+
color: var(--element-neutral-color);
|
|
433
|
+
padding-bottom: var(
|
|
434
|
+
--global-typography-instrument-value-large-padding-bottom
|
|
435
|
+
);
|
|
424
436
|
/* Keep the number and the trailing `%` symbol on one line. */
|
|
425
437
|
white-space: nowrap;
|
|
426
438
|
}
|
|
427
439
|
|
|
428
440
|
.percent-symbol {
|
|
429
|
-
@mixin font-
|
|
441
|
+
@mixin font-instrument-unit;
|
|
442
|
+
/* `inline-block` so `padding-bottom` actually applies (ignored on
|
|
443
|
+
* plain inline text). */
|
|
444
|
+
display: inline-block;
|
|
445
|
+
padding-bottom: var(--global-typography-instrument-unit-padding-bottom);
|
|
430
446
|
}
|
|
431
447
|
}
|
|
432
448
|
|
|
@@ -444,18 +460,100 @@
|
|
|
444
460
|
|
|
445
461
|
.current-value {
|
|
446
462
|
@mixin font-instrument-value-regular-active;
|
|
463
|
+
padding-bottom: var(
|
|
464
|
+
--global-typography-instrument-value-regular-padding-bottom
|
|
465
|
+
);
|
|
447
466
|
}
|
|
448
467
|
|
|
449
468
|
.divider {
|
|
450
469
|
padding-left: 4px;
|
|
451
470
|
padding-right: 4px;
|
|
471
|
+
padding-bottom: var(--global-typography-instrument-unit-padding-bottom);
|
|
452
472
|
}
|
|
453
473
|
|
|
454
474
|
.max-value {
|
|
455
475
|
@mixin font-instrument-value-regular-neutral;
|
|
476
|
+
padding-bottom: var(
|
|
477
|
+
--global-typography-instrument-value-regular-padding-bottom
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.unit {
|
|
482
|
+
padding-bottom: var(--global-typography-instrument-unit-padding-bottom);
|
|
456
483
|
}
|
|
457
484
|
}
|
|
458
485
|
|
|
486
|
+
.rich-divider {
|
|
487
|
+
border-top: 1px solid var(--border-divider-color);
|
|
488
|
+
margin-top: 8px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.rich {
|
|
492
|
+
display: grid;
|
|
493
|
+
/* label takes remaining space; value (right-aligned) and suffix
|
|
494
|
+
* (left-aligned) form fixed content columns so digit right edges and
|
|
495
|
+
* suffix left edges align vertically across all rows. */
|
|
496
|
+
grid-template-columns: 1fr auto auto;
|
|
497
|
+
align-items: baseline;
|
|
498
|
+
row-gap: 4px;
|
|
499
|
+
padding-top: 8px;
|
|
500
|
+
white-space: nowrap;
|
|
501
|
+
@mixin font-instrument-unit;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.rich-row {
|
|
505
|
+
display: grid;
|
|
506
|
+
grid-template-columns: subgrid;
|
|
507
|
+
grid-column: 1 / -1;
|
|
508
|
+
align-items: baseline;
|
|
509
|
+
padding-left: var(
|
|
510
|
+
--instrument-components-readout-new-list-item-regular-container-padding-horizontal
|
|
511
|
+
);
|
|
512
|
+
padding-right: var(
|
|
513
|
+
--instrument-components-readout-new-list-item-regular-container-padding-horizontal
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.rich-label {
|
|
518
|
+
@mixin font-instrument-label;
|
|
519
|
+
color: var(--element-inactive-color);
|
|
520
|
+
padding-right: 8px;
|
|
521
|
+
text-align: left;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.rich-value {
|
|
525
|
+
@mixin font-instrument-value-regular-neutral;
|
|
526
|
+
color: var(--element-neutral-color);
|
|
527
|
+
font-variant-numeric: tabular-nums;
|
|
528
|
+
text-align: right;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.rich-suffix {
|
|
532
|
+
display: inline-flex;
|
|
533
|
+
align-items: baseline;
|
|
534
|
+
/* Default 2px gap between value and unit when there is no degree/percent
|
|
535
|
+
* glyph. When a glyph is present it sits flush against the value and the
|
|
536
|
+
* unit follows flush after the glyph (see `.rich-glyph + .rich-unit`). */
|
|
537
|
+
padding-left: 2px;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.rich-suffix:has(.rich-glyph) {
|
|
541
|
+
padding-left: 0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.rich-glyph {
|
|
545
|
+
@mixin font-instrument-value-regular-neutral;
|
|
546
|
+
color: var(--element-neutral-color);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.rich-unit {
|
|
550
|
+
color: var(--element-inactive-color);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.rich-glyph + .rich-unit {
|
|
554
|
+
padding-left: 0;
|
|
555
|
+
}
|
|
556
|
+
|
|
459
557
|
/* The chart cell is a plain sizing/clipping box. Both child renderers —
|
|
460
558
|
* `obc-gauge-trend` (graph / graph-and-bar modes) and `obc-bar-vertical`
|
|
461
559
|
* (bar mode) — paint their own background, border and border-radius. */
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
TankPositioning,
|
|
7
7
|
TankTrend,
|
|
8
8
|
TankType,
|
|
9
|
+
type TankReadoutItem,
|
|
9
10
|
} from './automation-tank.js';
|
|
10
11
|
import './automation-tank.js';
|
|
11
12
|
import {html} from 'lit';
|
|
@@ -56,6 +57,12 @@ const SAMPLE_ADVICE: LinearAdvice[] = [
|
|
|
56
57
|
{min: 7500, max: 9000, type: AdviceType.advice, hinted: false},
|
|
57
58
|
];
|
|
58
59
|
|
|
60
|
+
const SAMPLE_READOUT: TankReadoutItem[] = [
|
|
61
|
+
{label: 'Temperature', value: 45, hasDegree: true, unit: 'C'},
|
|
62
|
+
{label: 'Pressure', value: 45, unit: 'Pa'},
|
|
63
|
+
{label: 'Flow speed', value: 45, unit: 'm/s'},
|
|
64
|
+
];
|
|
65
|
+
|
|
59
66
|
const renderTank = (args: StoryArgs) => html`
|
|
60
67
|
<obc-automation-tank
|
|
61
68
|
.value=${args.value}
|
|
@@ -74,6 +81,7 @@ const renderTank = (args: StoryArgs) => html`
|
|
|
74
81
|
.hasGraphIcon=${args.hasGraphIcon}
|
|
75
82
|
.showTrendSymbol=${args.showTrendSymbol}
|
|
76
83
|
.percentFractionDigits=${args.percentFractionDigits}
|
|
84
|
+
.readout=${args.readout}
|
|
77
85
|
?alert=${args.alert}
|
|
78
86
|
.alertFrameType=${args.alertFrameType}
|
|
79
87
|
.alertFrameThickness=${args.alertFrameThickness}
|
|
@@ -110,6 +118,7 @@ const meta: Meta<StoryArgs> = {
|
|
|
110
118
|
hasGraphIcon: false,
|
|
111
119
|
showTrendSymbol: true,
|
|
112
120
|
percentFractionDigits: 0,
|
|
121
|
+
readout: [],
|
|
113
122
|
alert: false,
|
|
114
123
|
alertFrameType: ObcAlertFrameType.SmallSideFlip,
|
|
115
124
|
alertFrameThickness: ObcAlertFrameThickness.Small,
|
|
@@ -177,6 +186,11 @@ const meta: Meta<StoryArgs> = {
|
|
|
177
186
|
description:
|
|
178
187
|
'Advice overlay bands. `min`/`max` are in the same units as `max`. Toggle visibility with `hasAdvice`. Works in all three `chartMode` variants — `bar` overlays advice pills on the static bar, `graph` and `graph-and-bar` forward them to the embedded `obc-gauge-trend`.',
|
|
179
188
|
},
|
|
189
|
+
readout: {
|
|
190
|
+
control: {type: 'object'},
|
|
191
|
+
description:
|
|
192
|
+
'Rich detail rows shown below the main percent/value block in the regular (non-compact, non-static) layout, separated by a divider. Each row: `{label, value, hasDegree?, hasPercentage?, unit}`. Empty array — nothing is rendered. Values use `percentFractionDigits` for formatting. Override the whole block via `slot="rich"`.',
|
|
193
|
+
},
|
|
180
194
|
badgeControl: {
|
|
181
195
|
options: Object.values(AutomationButtonBadgeControl),
|
|
182
196
|
control: {type: 'select'},
|
|
@@ -355,6 +369,23 @@ export const BatteryWithGraphIcon: Story = {
|
|
|
355
369
|
},
|
|
356
370
|
};
|
|
357
371
|
|
|
372
|
+
export const Rich: Story = {
|
|
373
|
+
args: {
|
|
374
|
+
type: TankType.atmospheric,
|
|
375
|
+
chartMode: TankChartMode.graphAndBar,
|
|
376
|
+
readout: SAMPLE_READOUT,
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export const HorizontalRich: Story = {
|
|
381
|
+
args: {
|
|
382
|
+
type: TankType.atmospheric,
|
|
383
|
+
orientation: TankOrientation.horizontal,
|
|
384
|
+
chartMode: TankChartMode.graphAndBar,
|
|
385
|
+
readout: SAMPLE_READOUT,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
358
389
|
/**
|
|
359
390
|
* Demonstrates fractional precision in the non-compact readout. The percent
|
|
360
391
|
* readout is controlled by the `percentFractionDigits` property (here `1`,
|
|
@@ -367,6 +398,10 @@ export const BatteryWithGraphIcon: Story = {
|
|
|
367
398
|
* The compact / static layouts only show the percent (no absolute value)
|
|
368
399
|
* and always render it as an integer to keep their fixed-width footprint
|
|
369
400
|
* stable, so fraction-digit control only applies to the non-compact layout.
|
|
401
|
+
*
|
|
402
|
+
* `percentFractionDigits` is also applied uniformly to every row of the
|
|
403
|
+
* `readout` rich list (see the `Rich` story) — all rows share one digit
|
|
404
|
+
* count so the right-aligned numeric column stays visually consistent.
|
|
370
405
|
*/
|
|
371
406
|
export const WithFractionDigits: Story = {
|
|
372
407
|
args: {
|
|
@@ -396,6 +431,7 @@ export const WithFractionDigits: Story = {
|
|
|
396
431
|
.showTrendSymbol=${args.showTrendSymbol}
|
|
397
432
|
.priority=${args.priority}
|
|
398
433
|
.percentFractionDigits=${args.percentFractionDigits}
|
|
434
|
+
.readout=${args.readout}
|
|
399
435
|
>
|
|
400
436
|
<span slot="current-value">${args.value.toFixed(2)}</span>
|
|
401
437
|
<span slot="max-value">${args.max.toFixed(2)}</span>
|
|
@@ -434,6 +470,7 @@ export const WithAlertAlarm: Story = {
|
|
|
434
470
|
.hasGraphIcon=${args.hasGraphIcon}
|
|
435
471
|
.showTrendSymbol=${args.showTrendSymbol}
|
|
436
472
|
.percentFractionDigits=${args.percentFractionDigits}
|
|
473
|
+
.readout=${args.readout}
|
|
437
474
|
?alert=${args.alert}
|
|
438
475
|
.alertFrameType=${args.alertFrameType}
|
|
439
476
|
.alertFrameThickness=${args.alertFrameThickness}
|
|
@@ -534,6 +571,7 @@ export const Responsive: Story = {
|
|
|
534
571
|
.hasGraphIcon=${args.hasGraphIcon}
|
|
535
572
|
.showTrendSymbol=${args.showTrendSymbol}
|
|
536
573
|
.percentFractionDigits=${args.percentFractionDigits}
|
|
574
|
+
.readout=${args.readout}
|
|
537
575
|
?alert=${args.alert}
|
|
538
576
|
.alertFrameType=${args.alertFrameType}
|
|
539
577
|
.alertFrameThickness=${args.alertFrameThickness}
|
|
@@ -94,6 +94,25 @@ export enum TankChartMode {
|
|
|
94
94
|
graphAndBar = 'graph-and-bar',
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* A single detail row rendered in the tank's `readout` rich list (below the
|
|
99
|
+
* main percent / value block, separated by a divider). Each row is rendered
|
|
100
|
+
* as `<label>` on the left and `<value><degree?><percent?><unit>` on the
|
|
101
|
+
* right, all on a single line.
|
|
102
|
+
*/
|
|
103
|
+
export interface TankReadoutItem {
|
|
104
|
+
/** Left-aligned label text (e.g. `Temperature`). */
|
|
105
|
+
label: string;
|
|
106
|
+
/** Numeric value, formatted with the tank's `percentFractionDigits`. */
|
|
107
|
+
value: number;
|
|
108
|
+
/** Append a `°` glyph directly after the value with no gap. */
|
|
109
|
+
hasDegree?: boolean;
|
|
110
|
+
/** Append a `%` glyph directly after the value with no gap. */
|
|
111
|
+
hasPercentage?: boolean;
|
|
112
|
+
/** Unit text (e.g. `C`, `Pa`, `m/s`). Rendered after the value/glyph. */
|
|
113
|
+
unit: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
97
116
|
/**
|
|
98
117
|
*
|
|
99
118
|
*
|
|
@@ -221,6 +240,21 @@ export class ObcAutomationTank extends LitElement {
|
|
|
221
240
|
*/
|
|
222
241
|
@property({type: Number}) percentFractionDigits: number = 0;
|
|
223
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Rich detail rows shown below the main percent/value block in the regular
|
|
245
|
+
* (non-compact, non-static) layout, separated by a divider. When empty (the
|
|
246
|
+
* default), nothing is rendered — neither the divider nor the list. In
|
|
247
|
+
* vertical orientation the chart cell shrinks to make room; in horizontal
|
|
248
|
+
* orientation the readout column already has reserved whitespace so the
|
|
249
|
+
* chart is unaffected.
|
|
250
|
+
*
|
|
251
|
+
* Values are formatted using `percentFractionDigits`. Consumers that need
|
|
252
|
+
* full control over the markup can replace the entire fallback by slotting
|
|
253
|
+
* arbitrary content into `slot="rich"` (note: this is a different name from
|
|
254
|
+
* the existing `slot="readout"` which replaces the whole readout block).
|
|
255
|
+
*/
|
|
256
|
+
@property({type: Array, attribute: false}) readout: TankReadoutItem[] = [];
|
|
257
|
+
|
|
224
258
|
/**
|
|
225
259
|
* Enum-driven badges rendered inside the `badges` cell. Mirrors the API
|
|
226
260
|
* introduced for `ObcAbstractAutomationButton` in PR #839 (#829). When set
|
|
@@ -703,6 +737,36 @@ export class ObcAutomationTank extends LitElement {
|
|
|
703
737
|
<slot class="unit" name="unit">m<sup>3</sup></slot>
|
|
704
738
|
</div>
|
|
705
739
|
</div>
|
|
740
|
+
<slot name="rich">
|
|
741
|
+
${this.readout.length > 0
|
|
742
|
+
? html`
|
|
743
|
+
<div class="rich-divider"></div>
|
|
744
|
+
<div class="rich">
|
|
745
|
+
${this.readout.map(
|
|
746
|
+
(row) => html`
|
|
747
|
+
<div class="rich-row">
|
|
748
|
+
<span class="rich-label">${row.label}</span>
|
|
749
|
+
<span class="rich-value"
|
|
750
|
+
>${row.value.toFixed(
|
|
751
|
+
this.percentFractionDigits
|
|
752
|
+
)}</span
|
|
753
|
+
>
|
|
754
|
+
<span class="rich-suffix"
|
|
755
|
+
>${row.hasDegree
|
|
756
|
+
? html`<span class="rich-glyph">°</span>`
|
|
757
|
+
: nothing}${row.hasPercentage
|
|
758
|
+
? html`<span class="rich-glyph">%</span>`
|
|
759
|
+
: nothing}<span class="rich-unit"
|
|
760
|
+
>${row.unit}</span
|
|
761
|
+
></span
|
|
762
|
+
>
|
|
763
|
+
</div>
|
|
764
|
+
`
|
|
765
|
+
)}
|
|
766
|
+
</div>
|
|
767
|
+
`
|
|
768
|
+
: nothing}
|
|
769
|
+
</slot>
|
|
706
770
|
</slot>
|
|
707
771
|
</div>
|
|
708
772
|
`;
|
|
@@ -58,6 +58,9 @@ export enum IconButtonVariant {
|
|
|
58
58
|
* | (default) | Always | The icon to display (e.g., `<obi-search>`) |
|
|
59
59
|
* | label | If `hasLabel` is set | Optional label text below the icon |
|
|
60
60
|
*
|
|
61
|
+
* ## Events
|
|
62
|
+
* - Emits a standard `click` event (`onClick` handler in framework wrappers) when activated.
|
|
63
|
+
*
|
|
61
64
|
* ## Best Practices
|
|
62
65
|
* - Ensure icons are clear and universally recognizable.
|
|
63
66
|
* - For accessibility, provide an `aria-label` or descriptive label for the button's action.
|
|
@@ -77,6 +80,7 @@ export enum IconButtonVariant {
|
|
|
77
80
|
*
|
|
78
81
|
* @slot - Icon slot (default): Place an icon such as <obi-search> here.
|
|
79
82
|
* @slot label - Optional label shown below the icon when `hasLabel` is true.
|
|
83
|
+
* @fires click - Fired when the button is clicked (if not disabled).
|
|
80
84
|
*/
|
|
81
85
|
@customElement('obc-icon-button')
|
|
82
86
|
export class ObcIconButton extends LitElement {
|