@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.45 → 2.0.0-next.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.storybook/ComponentPreview.tsx +26 -0
  2. package/.storybook/PreviewTemplate.tsx +99 -0
  3. package/.storybook/action-handler.ts +81 -0
  4. package/.storybook/channels.ts +44 -0
  5. package/.storybook/main.ts +313 -0
  6. package/.storybook/manager.ts +71 -0
  7. package/.storybook/openbridgeTheme.ts +197 -0
  8. package/.storybook/preview-head.html +15 -0
  9. package/.storybook/preview.tsx +129 -0
  10. package/.storybook/vitest.setup.ts +23 -0
  11. package/bundle/openbridge-webcomponents.bundle.js +14869 -14889
  12. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  13. package/custom-elements.json +81 -82
  14. package/dist/components/badge/badge.css.js +15 -15
  15. package/dist/components/icon-button/icon-button.d.ts +4 -0
  16. package/dist/components/icon-button/icon-button.d.ts.map +1 -1
  17. package/dist/components/icon-button/icon-button.js.map +1 -1
  18. package/dist/navigation-instruments/compass/compass.d.ts +3 -3
  19. package/dist/navigation-instruments/compass/compass.d.ts.map +1 -1
  20. package/dist/navigation-instruments/compass/compass.js +3 -3
  21. package/dist/navigation-instruments/compass/compass.js.map +1 -1
  22. package/dist/navigation-instruments/compass-indicator/compass-indicator.d.ts +12 -0
  23. package/dist/navigation-instruments/compass-indicator/compass-indicator.d.ts.map +1 -1
  24. package/dist/navigation-instruments/compass-indicator/compass-indicator.js +9 -1
  25. package/dist/navigation-instruments/compass-indicator/compass-indicator.js.map +1 -1
  26. package/dist/navigation-instruments/velocity-projection-plot/velocity-projection-plot.d.ts +4 -4
  27. package/dist/navigation-instruments/velocity-projection-plot/velocity-projection-plot.d.ts.map +1 -1
  28. package/dist/navigation-instruments/velocity-projection-plot/velocity-projection-plot.js +15 -14
  29. package/dist/navigation-instruments/velocity-projection-plot/velocity-projection-plot.js.map +1 -1
  30. package/dist/navigation-instruments/watch/environment.d.ts +39 -1
  31. package/dist/navigation-instruments/watch/environment.d.ts.map +1 -1
  32. package/dist/navigation-instruments/watch/environment.js +71 -88
  33. package/dist/navigation-instruments/watch/environment.js.map +1 -1
  34. package/dist/navigation-instruments/watch/watch.d.ts +1 -1
  35. package/dist/navigation-instruments/watch/watch.d.ts.map +1 -1
  36. package/dist/navigation-instruments/watch/watch.js +4 -4
  37. package/dist/navigation-instruments/watch/watch.js.map +1 -1
  38. package/dist/navigation-instruments/wind/wind.d.ts +1 -1
  39. package/dist/navigation-instruments/wind/wind.d.ts.map +1 -1
  40. package/dist/navigation-instruments/wind/wind.js +3 -3
  41. package/dist/navigation-instruments/wind/wind.js.map +1 -1
  42. package/dist/navigation-instruments/wind-indicator/wind-indicator.d.ts +63 -9
  43. package/dist/navigation-instruments/wind-indicator/wind-indicator.d.ts.map +1 -1
  44. package/dist/navigation-instruments/wind-indicator/wind-indicator.js +57 -1995
  45. package/dist/navigation-instruments/wind-indicator/wind-indicator.js.map +1 -1
  46. package/dist/navigation-instruments/wind-propulsion/wind-propulsion.d.ts +1 -1
  47. package/dist/navigation-instruments/wind-propulsion/wind-propulsion.d.ts.map +1 -1
  48. package/dist/navigation-instruments/wind-propulsion/wind-propulsion.js +4 -4
  49. package/dist/navigation-instruments/wind-propulsion/wind-propulsion.js.map +1 -1
  50. package/eslint.config.mjs +589 -0
  51. package/fix-imports.mjs +185 -0
  52. package/fix-js-extensions.mjs +44 -0
  53. package/lit-localize.json +15 -0
  54. package/new-component.ts +148 -0
  55. package/package.json +56 -3
  56. package/postcss.config.mjs +195 -0
  57. package/script/check-css-mixins.ts +191 -0
  58. package/script/check-css-variables.ts +280 -0
  59. package/script/convert-icons.ts +202 -0
  60. package/script/convert-vessel-svg-to-ts.ts +110 -0
  61. package/script/docgen/README.md +95 -0
  62. package/script/docgen/docs-gen.ts +225 -0
  63. package/script/docgen/prompt-system.txt +187 -0
  64. package/script/download-alert-icons.ts +193 -0
  65. package/script/download-icons.ts +314 -0
  66. package/script/figmavariables.json +139 -0
  67. package/script/generate-bundle-entry.ts +77 -0
  68. package/script/prepare-full-bundle.ts +105 -0
  69. package/script/sort-custom-element-manifest.ts +67 -0
  70. package/src/components/badge/badge.css +15 -15
  71. package/src/components/badge/badge.stories.ts +2 -0
  72. package/src/components/icon-button/icon-button.ts +4 -0
  73. package/src/navigation-instruments/compass/compass.stories.ts +5 -2
  74. package/src/navigation-instruments/compass/compass.ts +4 -4
  75. package/src/navigation-instruments/compass-indicator/compass-indicator.stories.ts +34 -1
  76. package/src/navigation-instruments/compass-indicator/compass-indicator.ts +19 -1
  77. package/src/navigation-instruments/velocity-projection-plot/velocity-projection-plot.stories.ts +13 -13
  78. package/src/navigation-instruments/velocity-projection-plot/velocity-projection-plot.ts +15 -11
  79. package/src/navigation-instruments/watch/environment.ts +120 -87
  80. package/src/navigation-instruments/watch/watch.stories.ts +2 -2
  81. package/src/navigation-instruments/watch/watch.ts +3 -3
  82. package/src/navigation-instruments/wind/wind.stories.ts +4 -3
  83. package/src/navigation-instruments/wind/wind.ts +2 -2
  84. package/src/navigation-instruments/wind-indicator/wind-indicator.stories.ts +22 -12
  85. package/src/navigation-instruments/wind-indicator/wind-indicator.ts +91 -46
  86. package/src/navigation-instruments/wind-propulsion/wind-propulsion.stories.ts +3 -3
  87. package/src/navigation-instruments/wind-propulsion/wind-propulsion.ts +3 -3
  88. package/tsconfig.json +38 -0
  89. package/vite.config.ts +107 -0
  90. package/vitest.browser.config.ts +14 -0
  91. package/vitest.config.ts +51 -0
  92. package/xliff/es-419.xlf +111 -0
  93. package/xliff/fi-FI.xlf +139 -0
  94. package/dist/NotoSans.ttf +0 -0
  95. 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`);
@@ -4,7 +4,7 @@
4
4
  align-items: center;
5
5
  justify-content: center;
6
6
  border-style: solid;
7
- border-radius: var(--app-components-badge-border-radius);
7
+ border-radius: var(--ui-components-badge-border-radius);
8
8
  padding: calc(var(--ui-components-badge-padding) - 1px);
9
9
  border: 1px solid var(--alert-alarm-outline-color);
10
10
  background: var(--alert-alarm-color);
@@ -18,8 +18,8 @@
18
18
  }
19
19
 
20
20
  .icon {
21
- width: var(--app-components-badge-icon-size);
22
- height: var(--app-components-badge-icon-size);
21
+ width: var(--ui-components-badge-icon-size);
22
+ height: var(--ui-components-badge-icon-size);
23
23
  display: flex;
24
24
  align-items: center;
25
25
  justify-content: center;
@@ -33,18 +33,18 @@
33
33
  flex-direction: column;
34
34
  justify-content: center;
35
35
  align-items: center;
36
- padding: 0 var(--app-components-badge-label-spacing) 1px
37
- var(--app-components-badge-label-spacing);
38
- height: var(--app-components-badge-icon-size);
39
- min-width: var(--app-components-badge-icon-size);
36
+ padding: 0 var(--ui-components-badge-label-spacing) 1px
37
+ var(--ui-components-badge-label-spacing);
38
+ height: var(--ui-components-badge-icon-size);
39
+ min-width: var(--ui-components-badge-icon-size);
40
40
  }
41
41
 
42
42
  &.size-large {
43
43
  display: inline-flex;
44
44
  width: fit-content;
45
45
  height: 24px;
46
- min-width: var(--app-components-badge-min-size-large);
47
- min-height: var(--app-components-badge-min-size-large);
46
+ min-width: var(--ui-components-badge-min-size-large);
47
+ min-height: var(--ui-components-badge-min-size-large);
48
48
 
49
49
  padding: calc(var(--ui-components-badge-padding) - 1px);
50
50
  justify-content: center;
@@ -60,8 +60,8 @@
60
60
  }
61
61
 
62
62
  & .icon {
63
- width: var(--app-components-badge-icon-size-large);
64
- height: var(--app-components-badge-icon-size-large);
63
+ width: var(--ui-components-badge-icon-size-large);
64
+ height: var(--ui-components-badge-icon-size-large);
65
65
  display: flex;
66
66
  align-items: center;
67
67
  justify-content: center;
@@ -132,8 +132,8 @@
132
132
  border-color: var(--flat-enabled-background-color);
133
133
 
134
134
  .icon {
135
- min-width: var(--app-components-badge-icon-size);
136
- min-height: var(--app-components-badge-icon-size);
135
+ min-width: var(--ui-components-badge-icon-size);
136
+ min-height: var(--ui-components-badge-icon-size);
137
137
  }
138
138
 
139
139
  .icon.type-alarm {
@@ -187,8 +187,8 @@
187
187
  }
188
188
 
189
189
  & .icon.type-outline {
190
- width: var(--app-components-badge-icon-size-flat);
191
- height: var(--app-components-badge-icon-size-flat);
190
+ width: var(--ui-components-badge-icon-size-flat);
191
+ height: var(--ui-components-badge-icon-size-flat);
192
192
  }
193
193
 
194
194
  .icon.type-outline + .number {
@@ -106,6 +106,7 @@ export const NoNumber: Story = {
106
106
  render(args) {
107
107
  return html`<obc-badge
108
108
  .showNumber=${args.showNumber}
109
+ .showIcon=${args.showIcon}
109
110
  size=${args.size}
110
111
  type=${args.type}
111
112
  variant=${args.variant}
@@ -126,6 +127,7 @@ export const NoNumberLarge: Story = {
126
127
  render(args) {
127
128
  return html`<obc-badge
128
129
  .showNumber=${args.showNumber}
130
+ .showIcon=${args.showIcon}
129
131
  size=${args.size}
130
132
  type=${args.type}
131
133
  variant=${args.variant}
@@ -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 {
@@ -30,7 +30,7 @@ const meta: Meta<typeof ObcCompass> = {
30
30
  },
31
31
  ],
32
32
  headingSetpoint: 311,
33
- windSpeed: 4,
33
+ currentWindSpeedKnots: 20,
34
34
  windFromDirection: 45,
35
35
  currentSpeed: 3,
36
36
  currentFromDirection: 60,
@@ -51,7 +51,10 @@ const meta: Meta<typeof ObcCompass> = {
51
51
  heading: {control: {type: 'range', min: 0, max: 360, step: 1}},
52
52
  courseOverGround: {control: {type: 'range', min: 0, max: 360, step: 1}},
53
53
  headingSetpoint: {control: {type: 'range', min: 0, max: 360, step: 1}},
54
- windSpeed: {control: {type: 'range', min: 0, max: 14, step: 1}},
54
+ currentWindSpeedKnots: {
55
+ control: {type: 'range', min: 0, max: 75, step: 1},
56
+ description: 'Wind speed in knots.',
57
+ },
55
58
  windFromDirection: {control: {type: 'range', min: 0, max: 360, step: 1}},
56
59
  currentSpeed: {control: {type: 'range', min: 0, max: 4, step: 1}},
57
60
  currentFromDirection: {control: {type: 'range', min: 0, max: 360, step: 1}},
@@ -78,7 +78,7 @@ export enum CompassPriorityElement {
78
78
  * - Use `headingSetpoint` to show a target heading marker.
79
79
  * - Pass `headingAdvices` as an array of `AngleAdvice` objects for
80
80
  * caution/alert zones.
81
- * - Set `windSpeed` / `windFromDirection` and `currentSpeed` /
81
+ * - Set `currentWindSpeedKnots` / `windFromDirection` and `currentSpeed` /
82
82
  * `currentFromDirection` to display environmental indicators.
83
83
  *
84
84
  * ## Example
@@ -102,7 +102,7 @@ export enum CompassPriorityElement {
102
102
  * @property {number} autoAtHeadingSetpointDeadband - The deadband for the heading set point in degrees.
103
103
  * @property {boolean} touching - Indicates if the compass is being touched.
104
104
  * @property {Array<AngleAdvice>} headingAdvices - An array of angle advices for the compass.
105
- * @property {number | null} windSpeed - The wind speed in beaufort scale number.
105
+ * @property {number | null} currentWindSpeedKnots - The wind speed in knots.
106
106
  * @property {number | null} windFromDirection - The direction the wind is coming from in degrees.
107
107
  * @property {number | null} currentSpeed - The current speed, number of arrows.
108
108
  * @property {number | null} currentFromDirection - The direction the current is coming from in degrees.
@@ -133,7 +133,7 @@ export class ObcCompass extends LitElement {
133
133
  @property({type: Boolean}) animateSetpoint: boolean = false;
134
134
  @property({type: Boolean}) touching: boolean = false;
135
135
  @property({type: Array, attribute: false}) headingAdvices: AngleAdvice[] = [];
136
- @property({type: Number}) windSpeed: number | null = null;
136
+ @property({type: Number}) currentWindSpeedKnots: number | null = null;
137
137
  @property({type: Number}) windFromDirection: number | null = null;
138
138
  @property({type: Number}) currentSpeed: number | null = null;
139
139
  @property({type: Number}) currentFromDirection: number | null = null;
@@ -311,7 +311,7 @@ export class ObcCompass extends LitElement {
311
311
  transform: `rotate(${this.heading}deg)`,
312
312
  },
313
313
  ]}
314
- .wind=${this.windSpeed}
314
+ .windKnots=${this.currentWindSpeedKnots}
315
315
  .windFromDirectionDeg=${this.windFromDirection}
316
316
  .windColor=${this.colorFor(CompassPriorityElement.wind)}
317
317
  .current=${this.currentSpeed}
@@ -1,4 +1,5 @@
1
1
  import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {html} from 'lit';
2
3
  import {
3
4
  CompassIndicatorDirection,
4
5
  CompassIndicatorType,
@@ -6,17 +7,26 @@ import {
6
7
  } from './compass-indicator.js';
7
8
  import './compass-indicator.js';
8
9
 
9
- const meta: Meta<typeof ObcCompassIndicator> = {
10
+ const meta: Meta<ObcCompassIndicator> = {
10
11
  title: 'Indicators/Compass Indicator',
11
12
  tags: ['autodocs', '6.0'],
12
13
  component: 'obc-compass-indicator',
13
14
  parameters: {
14
15
  layout: 'centered',
15
16
  },
17
+ render: (args) => html`
18
+ <obc-compass-indicator
19
+ .angle=${args.angle}
20
+ .type=${args.type}
21
+ .direction=${args.direction}
22
+ .northUp=${args.northUp}
23
+ ></obc-compass-indicator>
24
+ `,
16
25
  args: {
17
26
  angle: 315,
18
27
  type: CompassIndicatorType.Regular,
19
28
  direction: CompassIndicatorDirection.Heading,
29
+ northUp: true,
20
30
  },
21
31
  argTypes: {
22
32
  angle: {
@@ -30,6 +40,11 @@ const meta: Meta<typeof ObcCompassIndicator> = {
30
40
  control: 'select',
31
41
  options: Object.values(CompassIndicatorDirection),
32
42
  },
43
+ northUp: {
44
+ control: 'boolean',
45
+ description:
46
+ 'When true the compass face stays north-up and the arrow rotates; when false the arrow stays vertical and the face rotates (heading-up / course-up).',
47
+ },
33
48
  },
34
49
  } satisfies Meta<ObcCompassIndicator>;
35
50
 
@@ -87,3 +102,21 @@ export const LabeledNorth: Story = {
87
102
  angle: 315,
88
103
  },
89
104
  };
105
+
106
+ export const RegularHeadingHeadingUp: Story = {
107
+ args: {
108
+ type: CompassIndicatorType.Regular,
109
+ direction: CompassIndicatorDirection.Heading,
110
+ angle: 45,
111
+ northUp: false,
112
+ },
113
+ };
114
+
115
+ export const RegularCourseCourseUp: Story = {
116
+ args: {
117
+ type: CompassIndicatorType.Regular,
118
+ direction: CompassIndicatorDirection.Course,
119
+ angle: 45,
120
+ northUp: false,
121
+ },
122
+ };
@@ -39,6 +39,7 @@ export enum CompassIndicatorDirection {
39
39
  * - **Types**: `regular` shows the circular compass indicator; `labeled` shows a framed indicator with a single cardinal label.
40
40
  * - **Directions**: `heading`, `course`, and `north` each use their own visual symbol.
41
41
  * - **Angle-driven output**: `angle` rotates the active symbol and drives the derived label in labeled mode.
42
+ * - **Orientation**: `northUp` keeps the compass face fixed north-up and rotates the arrow; when `false` the face rotates and the arrow stays pointing up (heading-up / course-up).
42
43
  *
43
44
  * ## Usage Guidelines
44
45
  *
@@ -47,6 +48,8 @@ export enum CompassIndicatorDirection {
47
48
  * - Use `direction="north"` for the north marker / bearing-style indicator.
48
49
  * - Switch `type` to `labeled` when the indicator should also communicate the
49
50
  * nearest cardinal direction as text.
51
+ * - Set `northUp` to `false` to render a heading-up / course-up presentation
52
+ * where the arrow stays vertical and the compass face rotates instead.
50
53
  */
51
54
  @customElement('obc-compass-indicator')
52
55
  export class ObcCompassIndicator extends LitElement {
@@ -61,6 +64,17 @@ export class ObcCompassIndicator extends LitElement {
61
64
  @property({type: String})
62
65
  direction: CompassIndicatorDirection = CompassIndicatorDirection.Heading;
63
66
 
67
+ /**
68
+ * When `true` (default) the compass face stays north-up and the arrow
69
+ * rotates by `angle`. When `false` the arrow stays pointing up and the
70
+ * compass face rotates instead (heading-up / course-up presentation).
71
+ *
72
+ * Declared with `attribute: false` because the default is `true`; set it
73
+ * via the JavaScript property (or framework binding) to opt out.
74
+ */
75
+ @property({type: Boolean, attribute: false})
76
+ northUp: boolean = true;
77
+
64
78
  private get normalizedAngle(): number {
65
79
  return normalizeAngle(this.angle);
66
80
  }
@@ -381,7 +395,11 @@ export class ObcCompassIndicator extends LitElement {
381
395
  return this.renderLabeledIndicator(this.direction);
382
396
  }
383
397
 
384
- return this.renderRoundIndicator(this.direction, 0, -this.normalizedAngle);
398
+ return this.renderRoundIndicator(
399
+ this.direction,
400
+ this.northUp ? this.normalizedAngle : 0,
401
+ this.northUp ? 0 : -this.normalizedAngle
402
+ );
385
403
  }
386
404
 
387
405
  static override styles = unsafeCSS(componentStyle);
@@ -38,16 +38,16 @@ const meta: Meta<typeof ObcVelocityProjectionPlot> = {
38
38
  steps: {
39
39
  control: {type: 'range', min: 1, max: 360, step: 1},
40
40
  },
41
- instantWindDirectionDeg: {
41
+ currentWindFromDirection: {
42
42
  control: {type: 'range', min: 0, max: 360, step: 1},
43
43
  },
44
- instantWindSpeedNumber: {
45
- control: {type: 'range', min: 0, max: 12, step: 1},
44
+ currentWindSpeedKnots: {
45
+ control: {type: 'range', min: 0, max: 75, step: 1},
46
46
  },
47
- instantCurrentDirectionDeg: {
47
+ currentFromDirection: {
48
48
  control: {type: 'range', min: 0, max: 360, step: 1},
49
49
  },
50
- instantCurrentSpeedNumber: {
50
+ currentSpeedKnots: {
51
51
  control: {type: 'range', min: 0, max: 4, step: 1},
52
52
  },
53
53
  vesselImage: {
@@ -63,10 +63,10 @@ const meta: Meta<typeof ObcVelocityProjectionPlot> = {
63
63
  console.log(dataPoints);
64
64
  return html`<obc-velocity-projection-plot
65
65
  .dataPoints=${dataPoints}
66
- .instantWindDirectionDeg=${args.instantWindDirectionDeg}
67
- .instantWindSpeedNumber=${args.instantWindSpeedNumber}
68
- .instantCurrentDirectionDeg=${args.instantCurrentDirectionDeg}
69
- .instantCurrentSpeedNumber=${args.instantCurrentSpeedNumber}
66
+ .currentWindFromDirection=${args.currentWindFromDirection}
67
+ .currentWindSpeedKnots=${args.currentWindSpeedKnots}
68
+ .currentFromDirection=${args.currentFromDirection}
69
+ .currentSpeedKnots=${args.currentSpeedKnots}
70
70
  .vesselImage=${args.vesselImage}
71
71
  ></obc-velocity-projection-plot>`;
72
72
  },
@@ -81,10 +81,10 @@ export const Average: Story = {
81
81
 
82
82
  export const Instantaneous: Story = {
83
83
  args: {
84
- instantWindDirectionDeg: 90,
85
- instantWindSpeedNumber: 3,
86
- instantCurrentDirectionDeg: 0,
87
- instantCurrentSpeedNumber: 1,
84
+ currentWindFromDirection: 90,
85
+ currentWindSpeedKnots: 3,
86
+ currentFromDirection: 0,
87
+ currentSpeedKnots: 1,
88
88
  },
89
89
  };
90
90
  function generateDataPoints(
@@ -24,18 +24,22 @@ export class ObcVelocityProjectionPlot extends LitElement {
24
24
  @property({type: Array, attribute: false})
25
25
  dataPoints: VelocityProjectionDatapoint[] = [];
26
26
  @property({type: Number})
27
- instantWindDirectionDeg: number | null = null;
27
+ currentWindFromDirection: number | null = null;
28
28
  @property({type: Number})
29
- instantWindSpeedNumber: number | null = null;
29
+ currentWindSpeedKnots: number | null = null;
30
30
  @property({type: Number})
31
- instantCurrentDirectionDeg: number | null = null;
31
+ currentFromDirection: number | null = null;
32
32
  @property({type: Number})
33
- instantCurrentSpeedNumber: number | null = null;
33
+ currentSpeedKnots: number | null = null;
34
34
  @property({type: String})
35
35
  vesselImage: VesselImage = VesselImage.cargoWindTop;
36
36
  override render() {
37
37
  const width = 320;
38
38
  const viewBox = `-${width / 2} -${width / 2} ${width} ${width}`;
39
+ const currentLevel =
40
+ this.currentSpeedKnots == null
41
+ ? null
42
+ : Math.max(0, Math.min(4, Math.round(this.currentSpeedKnots)));
39
43
 
40
44
  return html`
41
45
  <div class="container">
@@ -49,10 +53,10 @@ export class ObcVelocityProjectionPlot extends LitElement {
49
53
  transform: '',
50
54
  },
51
55
  ]}
52
- .windFromDirectionDeg=${this.instantWindDirectionDeg}
53
- .wind=${this.instantWindSpeedNumber}
54
- .currentFromDirectionDeg=${this.instantCurrentDirectionDeg}
55
- .current=${this.instantCurrentSpeedNumber}
56
+ .windFromDirectionDeg=${this.currentWindFromDirection}
57
+ .windKnots=${this.currentWindSpeedKnots}
58
+ .currentFromDirectionDeg=${this.currentFromDirection}
59
+ .current=${currentLevel}
56
60
  .padding=${70}
57
61
  >
58
62
  </obc-watch>
@@ -62,10 +66,10 @@ export class ObcVelocityProjectionPlot extends LitElement {
62
66
  }
63
67
 
64
68
  private isSelected(dp: VelocityProjectionDatapoint): boolean {
65
- if (this.instantWindDirectionDeg != null) {
69
+ if (this.currentWindFromDirection != null) {
66
70
  return (
67
- dp.startAngleDeg <= this.instantWindDirectionDeg &&
68
- dp.endAngleDeg >= this.instantWindDirectionDeg
71
+ dp.startAngleDeg <= this.currentWindFromDirection &&
72
+ dp.endAngleDeg >= this.currentWindFromDirection
69
73
  );
70
74
  }
71
75
  return true;