@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.120 → 2.0.0-next.121

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.
@@ -73262,7 +73262,7 @@
73262
73262
  "name": "ObcIntegrationButton",
73263
73263
  "slots": [
73264
73264
  {
73265
- "description": "Icon before label (shown when `hasLeadingIcon` is true)",
73265
+ "description": "Icon before label (shown when `hasLeadingIcon` is true); rendered at the large icon size when `hasStatus` is true",
73266
73266
  "name": "leading-icon"
73267
73267
  },
73268
73268
  {
@@ -106,9 +106,13 @@ const compentStyle = css`* {
106
106
  "liga" off,
107
107
  "clig" off,
108
108
  "ss04" on;
109
+ --leading-icon-size: var(
110
+ --app-components-integration-system-navigation-item-horizontal-icon-size
111
+ );
109
112
  anchor-name: --touch-target;
110
113
  display: flex;
111
114
  align-items: center;
115
+ text-align: left;
112
116
  gap: 4px;
113
117
  flex: 1;
114
118
  min-height: var(
@@ -263,22 +267,25 @@ const compentStyle = css`* {
263
267
  color: var(--integration-on-normal-neutral-color);
264
268
  }
265
269
 
266
- .touch-target .icon.leading {
267
- width: var(
268
- --app-components-integration-system-navigation-item-horizontal-icon-size
269
- );
270
- height: var(
271
- --app-components-integration-system-navigation-item-horizontal-icon-size
270
+ .touch-target.has-status {
271
+ --leading-icon-size: var(
272
+ --app-components-integration-system-navigation-item-horizontal-icon-size-large
272
273
  );
273
- min-width: var(
274
- --app-components-integration-system-navigation-item-horizontal-icon-size
275
- );
276
- flex: 0 0
277
- var(
278
- --app-components-integration-system-navigation-item-horizontal-icon-size
279
- );
280
274
  }
281
275
 
276
+ .touch-target .icon.leading {
277
+ width: var(--leading-icon-size);
278
+ height: var(--leading-icon-size);
279
+ min-width: var(--leading-icon-size);
280
+ flex: 0 0 var(--leading-icon-size);
281
+ }
282
+
283
+ :is(.touch-target .icon.leading) ::slotted(*) {
284
+ display: block;
285
+ width: 100%;
286
+ height: 100%;
287
+ }
288
+
282
289
  .touch-target .icon.trailing {
283
290
  width: 28px;
284
291
  height: 24px;
@@ -1 +1 @@
1
- {"version":3,"file":"integration-button.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"integration-button.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -16,7 +16,7 @@ export declare enum IntegrationButtonType {
16
16
  /**
17
17
  * `<obc-integration-button>` – A button component for integration systems.
18
18
  *
19
- * @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true)
19
+ * @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true); rendered at the large icon size when `hasStatus` is true
20
20
  * @slot trailing-icon - Icon after label (shown when `hasTrailingIcon` is true)
21
21
  * @slot trailing-icon2 - Icon after label (shown when `hasTrailingIcon2` is true)
22
22
  * @slot label - Label text
@@ -46,7 +46,7 @@ let ObcIntegrationButton = class extends LitElement {
46
46
  selected: this.selected,
47
47
  activated: this.activated,
48
48
  disabled: this.disabled,
49
- "has-description": this.hasStatus,
49
+ "has-status": this.hasStatus,
50
50
  ["variant-" + this.variant]: true,
51
51
  ["type-" + this.type]: true
52
52
  };
@@ -1 +1 @@
1
- {"version":3,"file":"integration-button.js","sources":["../../../src/integration-systems/integration-button/integration-button.ts"],"sourcesContent":["import {LitElement, html, nothing, unsafeCSS} from 'lit';\nimport {customElement} from '../../decorator.js';\nimport compentStyle from './integration-button.css?inline';\nimport {property} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\n\nexport interface IntegrationButtonReadout {\n label: string;\n value: string;\n unit: string;\n}\n\nexport enum IntegrationButtonVariant {\n normal = 'normal',\n flat = 'flat',\n}\n\nexport enum IntegrationButtonType {\n hug = 'hug',\n regular = 'regular',\n rich = 'rich',\n}\n\n/**\n * `<obc-integration-button>` – A button component for integration systems.\n *\n * @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true)\n * @slot trailing-icon - Icon after label (shown when `hasTrailingIcon` is true)\n * @slot trailing-icon2 - Icon after label (shown when `hasTrailingIcon2` is true)\n * @slot label - Label text\n * @slot status - Status/description text (shown when `hasStatus` is true)\n * @slot info-label - Info label text\n * @slot info-status - Info status text\n * @slot integration-vessel-menu - Integration vessel menu to be shown when button is in activated state\n *\n * @fires click - Fired when the internal button is activated.\n *\n * @experimental\n */\n@customElement('obc-integration-button')\nexport class ObcIntegrationButton extends LitElement {\n /** Shows the `trailing-icon` slot. */\n @property({type: Boolean}) hasTrailingIcon = false;\n /**\n * Shows the `trailing-icon2` slot.\n * @availableWhen hasTrailingIcon==true\n */\n @property({type: Boolean}) hasTrailingIcon2 = false;\n /** Shows the `leading-icon` slot. */\n @property({type: Boolean}) hasLeadingIcon = false;\n /** Shows the `status` slot. */\n @property({type: Boolean}) hasStatus = false;\n /** List of readout items shown in the rich type. */\n @property({type: Array, attribute: false})\n readouts: IntegrationButtonReadout[] = [];\n /** Disables the internal button. */\n @property({type: Boolean}) disabled = false;\n /** Applies active state styling while a selection is pending. */\n @property({type: Boolean}) activated = false;\n /** Applies selected state styling. */\n @property({type: Boolean}) selected = false;\n /** Shows a bottom divider under the button. */\n @property({type: Boolean}) dividerBottom = false;\n /** Shows a right divider to separate from adjacent buttons. */\n @property({type: Boolean}) dividerRight = false;\n /** Visual variant (`normal` or `flat`). */\n @property({type: String}) variant: IntegrationButtonVariant =\n IntegrationButtonVariant.normal;\n /** Layout type (`hug`, `regular`, or `rich`). */\n @property({type: String}) type: IntegrationButtonType =\n IntegrationButtonType.regular;\n\n private getButtonClasses() {\n return {\n 'touch-target': true,\n selected: this.selected,\n activated: this.activated,\n disabled: this.disabled,\n 'has-description': this.hasStatus,\n ['variant-' + this.variant]: true,\n ['type-' + this.type]: true,\n };\n }\n\n private onIntegrationVesselMenuClick(event: Event) {\n event.stopPropagation();\n }\n\n renderRich() {\n return html`\n <div class=\"button-horizontal-divider-container\">\n <div class=\"button-vertical-divider-container\">\n <button\n class=${classMap(this.getButtonClasses())}\n ?disabled=${this.disabled}\n >\n <div class=\"content-container\">\n <div class=\"main-container\">\n ${this.hasLeadingIcon\n ? html`<div class=\"icon leading\">\n <slot name=\"leading-icon\"></slot>\n </div>`\n : nothing}\n <div class=\"text-container\">\n <div class=\"label\"><slot name=\"label\"></slot></div>\n ${this.hasStatus\n ? html`<div class=\"status\">\n <slot name=\"status\"></slot>\n </div>`\n : nothing}\n </div>\n ${this.hasTrailingIcon\n ? html`<div class=\"icon-container\">\n ${this.hasTrailingIcon2\n ? html`<slot\n name=\"trailing-icon2\"\n class=\"icon trailing\"\n ></slot>`\n : nothing}\n <slot name=\"trailing-icon\" class=\"icon trailing\"></slot>\n </div>`\n : nothing}\n </div>\n <div class=\"info-container\">\n <div class=\"info-item\">\n <slot class=\"info-label\" name=\"info-label\"></slot>\n <slot class=\"info-status\" name=\"info-status\"></slot>\n </div>\n ${this.readouts.map(\n (readout) => html`\n <div class=\"info-readout-item\">\n <div class=\"info-item-label\">${readout.label}</div>\n <div class=\"info-item-value\">${readout.value}</div>\n <div class=\"info-item-unit\">${readout.unit}</div>\n </div>\n `\n )}\n </div>\n </div>\n </button>\n ${this.dividerRight\n ? html`<div class=\"divider-right\"></div>`\n : html`<div\n class=\"divider-right\"\n style=\"visibility: hidden;\"\n ></div>`}\n </div>\n ${this.dividerBottom\n ? html`<div class=\"divider-bottom\"></div>`\n : nothing}\n </div>\n <div\n class=${classMap({\n 'integration-vessel-menu-container': true,\n show: this.activated,\n })}\n @click=${this.onIntegrationVesselMenuClick}\n >\n <slot name=\"integration-vessel-menu\"></slot>\n </div>\n `;\n }\n\n renderRegular() {\n return html`\n <div class=\"button-vertical-divider-container\">\n <button\n class=${classMap(this.getButtonClasses())}\n ?disabled=${this.disabled}\n >\n <div class=\"content-container\">\n ${this.hasLeadingIcon\n ? html`<div class=\"icon leading\">\n <slot name=\"leading-icon\"></slot>\n </div>`\n : nothing}\n <div class=\"text-container\">\n <div class=\"label\"><slot name=\"label\"></slot></div>\n ${this.hasStatus\n ? html`<div class=\"status\"><slot name=\"status\"></slot></div>`\n : nothing}\n </div>\n ${this.hasTrailingIcon\n ? html`<div class=\"icon-container\">\n ${this.hasTrailingIcon2\n ? html`<slot\n name=\"trailing-icon2\"\n class=\"icon trailing\"\n ></slot>`\n : nothing}\n <slot name=\"trailing-icon\" class=\"icon trailing\"></slot>\n </div>`\n : nothing}\n </div>\n </button>\n ${this.dividerRight\n ? html`<div class=\"divider-right\"></div>`\n : html`<div class=\"divider-right\" style=\"visibility: hidden;\"></div>`}\n </div>\n <div\n class=${classMap({\n 'integration-vessel-menu-container': true,\n show: this.activated,\n })}\n @click=${this.onIntegrationVesselMenuClick}\n >\n <slot name=\"integration-vessel-menu\"></slot>\n </div>\n `;\n }\n\n renderHug() {\n return html`\n <div class=\"button-vertical-divider-container\">\n <button\n class=${classMap(this.getButtonClasses())}\n ?disabled=${this.disabled}\n >\n <div class=\"content-container\">\n ${this.hasLeadingIcon\n ? html`<div class=\"icon leading\">\n <slot name=\"leading-icon\"></slot>\n </div>`\n : nothing}\n <div class=\"text-container\">\n <div class=\"label\"><slot name=\"label\"></slot></div>\n ${this.hasStatus\n ? html`<div class=\"status\"><slot name=\"status\"></slot></div>`\n : nothing}\n </div>\n ${this.hasTrailingIcon\n ? html`<div class=\"icon-container\">\n ${this.hasTrailingIcon2\n ? html`<slot\n name=\"trailing-icon2\"\n class=\"icon trailing\"\n ></slot>`\n : nothing}\n <slot name=\"trailing-icon\" class=\"icon trailing\"></slot>\n </div>`\n : nothing}\n </div>\n </button>\n ${this.dividerRight\n ? html`<div class=\"divider-right\"></div>`\n : html`<div class=\"divider-right\" style=\"visibility: hidden;\"></div>`}\n </div>\n <div\n class=${classMap({\n 'integration-vessel-menu-container': true,\n show: this.activated,\n })}\n @click=${this.onIntegrationVesselMenuClick}\n >\n <slot name=\"integration-vessel-menu\"></slot>\n </div>\n `;\n }\n\n override render() {\n switch (this.type) {\n case IntegrationButtonType.hug: {\n return this.renderHug();\n }\n case IntegrationButtonType.rich: {\n return this.renderRich();\n }\n case IntegrationButtonType.regular:\n default: {\n return this.renderRegular();\n }\n }\n }\n\n static override styles = unsafeCSS(compentStyle);\n\n override updated() {\n let dividerHeight = 40;\n if (this.type === IntegrationButtonType.hug) {\n dividerHeight = 32;\n }\n if (this.type === IntegrationButtonType.rich) {\n dividerHeight = 64;\n }\n this.style.setProperty(\n '--integration-button-divider-height',\n `${dividerHeight}px`\n );\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-integration-button': ObcIntegrationButton;\n }\n}\n"],"names":["IntegrationButtonVariant","IntegrationButtonType"],"mappings":"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"}
1
+ {"version":3,"file":"integration-button.js","sources":["../../../src/integration-systems/integration-button/integration-button.ts"],"sourcesContent":["import {LitElement, html, nothing, unsafeCSS} from 'lit';\nimport {customElement} from '../../decorator.js';\nimport compentStyle from './integration-button.css?inline';\nimport {property} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\n\nexport interface IntegrationButtonReadout {\n label: string;\n value: string;\n unit: string;\n}\n\nexport enum IntegrationButtonVariant {\n normal = 'normal',\n flat = 'flat',\n}\n\nexport enum IntegrationButtonType {\n hug = 'hug',\n regular = 'regular',\n rich = 'rich',\n}\n\n/**\n * `<obc-integration-button>` – A button component for integration systems.\n *\n * @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true); rendered at the large icon size when `hasStatus` is true\n * @slot trailing-icon - Icon after label (shown when `hasTrailingIcon` is true)\n * @slot trailing-icon2 - Icon after label (shown when `hasTrailingIcon2` is true)\n * @slot label - Label text\n * @slot status - Status/description text (shown when `hasStatus` is true)\n * @slot info-label - Info label text\n * @slot info-status - Info status text\n * @slot integration-vessel-menu - Integration vessel menu to be shown when button is in activated state\n *\n * @fires click - Fired when the internal button is activated.\n *\n * @experimental\n */\n@customElement('obc-integration-button')\nexport class ObcIntegrationButton extends LitElement {\n /** Shows the `trailing-icon` slot. */\n @property({type: Boolean}) hasTrailingIcon = false;\n /**\n * Shows the `trailing-icon2` slot.\n * @availableWhen hasTrailingIcon==true\n */\n @property({type: Boolean}) hasTrailingIcon2 = false;\n /** Shows the `leading-icon` slot. */\n @property({type: Boolean}) hasLeadingIcon = false;\n /** Shows the `status` slot. */\n @property({type: Boolean}) hasStatus = false;\n /** List of readout items shown in the rich type. */\n @property({type: Array, attribute: false})\n readouts: IntegrationButtonReadout[] = [];\n /** Disables the internal button. */\n @property({type: Boolean}) disabled = false;\n /** Applies active state styling while a selection is pending. */\n @property({type: Boolean}) activated = false;\n /** Applies selected state styling. */\n @property({type: Boolean}) selected = false;\n /** Shows a bottom divider under the button. */\n @property({type: Boolean}) dividerBottom = false;\n /** Shows a right divider to separate from adjacent buttons. */\n @property({type: Boolean}) dividerRight = false;\n /** Visual variant (`normal` or `flat`). */\n @property({type: String}) variant: IntegrationButtonVariant =\n IntegrationButtonVariant.normal;\n /** Layout type (`hug`, `regular`, or `rich`). */\n @property({type: String}) type: IntegrationButtonType =\n IntegrationButtonType.regular;\n\n private getButtonClasses() {\n return {\n 'touch-target': true,\n selected: this.selected,\n activated: this.activated,\n disabled: this.disabled,\n 'has-status': this.hasStatus,\n ['variant-' + this.variant]: true,\n ['type-' + this.type]: true,\n };\n }\n\n private onIntegrationVesselMenuClick(event: Event) {\n event.stopPropagation();\n }\n\n renderRich() {\n return html`\n <div class=\"button-horizontal-divider-container\">\n <div class=\"button-vertical-divider-container\">\n <button\n class=${classMap(this.getButtonClasses())}\n ?disabled=${this.disabled}\n >\n <div class=\"content-container\">\n <div class=\"main-container\">\n ${this.hasLeadingIcon\n ? html`<div class=\"icon leading\">\n <slot name=\"leading-icon\"></slot>\n </div>`\n : nothing}\n <div class=\"text-container\">\n <div class=\"label\"><slot name=\"label\"></slot></div>\n ${this.hasStatus\n ? html`<div class=\"status\">\n <slot name=\"status\"></slot>\n </div>`\n : nothing}\n </div>\n ${this.hasTrailingIcon\n ? html`<div class=\"icon-container\">\n ${this.hasTrailingIcon2\n ? html`<slot\n name=\"trailing-icon2\"\n class=\"icon trailing\"\n ></slot>`\n : nothing}\n <slot name=\"trailing-icon\" class=\"icon trailing\"></slot>\n </div>`\n : nothing}\n </div>\n <div class=\"info-container\">\n <div class=\"info-item\">\n <slot class=\"info-label\" name=\"info-label\"></slot>\n <slot class=\"info-status\" name=\"info-status\"></slot>\n </div>\n ${this.readouts.map(\n (readout) => html`\n <div class=\"info-readout-item\">\n <div class=\"info-item-label\">${readout.label}</div>\n <div class=\"info-item-value\">${readout.value}</div>\n <div class=\"info-item-unit\">${readout.unit}</div>\n </div>\n `\n )}\n </div>\n </div>\n </button>\n ${this.dividerRight\n ? html`<div class=\"divider-right\"></div>`\n : html`<div\n class=\"divider-right\"\n style=\"visibility: hidden;\"\n ></div>`}\n </div>\n ${this.dividerBottom\n ? html`<div class=\"divider-bottom\"></div>`\n : nothing}\n </div>\n <div\n class=${classMap({\n 'integration-vessel-menu-container': true,\n show: this.activated,\n })}\n @click=${this.onIntegrationVesselMenuClick}\n >\n <slot name=\"integration-vessel-menu\"></slot>\n </div>\n `;\n }\n\n renderRegular() {\n return html`\n <div class=\"button-vertical-divider-container\">\n <button\n class=${classMap(this.getButtonClasses())}\n ?disabled=${this.disabled}\n >\n <div class=\"content-container\">\n ${this.hasLeadingIcon\n ? html`<div class=\"icon leading\">\n <slot name=\"leading-icon\"></slot>\n </div>`\n : nothing}\n <div class=\"text-container\">\n <div class=\"label\"><slot name=\"label\"></slot></div>\n ${this.hasStatus\n ? html`<div class=\"status\"><slot name=\"status\"></slot></div>`\n : nothing}\n </div>\n ${this.hasTrailingIcon\n ? html`<div class=\"icon-container\">\n ${this.hasTrailingIcon2\n ? html`<slot\n name=\"trailing-icon2\"\n class=\"icon trailing\"\n ></slot>`\n : nothing}\n <slot name=\"trailing-icon\" class=\"icon trailing\"></slot>\n </div>`\n : nothing}\n </div>\n </button>\n ${this.dividerRight\n ? html`<div class=\"divider-right\"></div>`\n : html`<div class=\"divider-right\" style=\"visibility: hidden;\"></div>`}\n </div>\n <div\n class=${classMap({\n 'integration-vessel-menu-container': true,\n show: this.activated,\n })}\n @click=${this.onIntegrationVesselMenuClick}\n >\n <slot name=\"integration-vessel-menu\"></slot>\n </div>\n `;\n }\n\n renderHug() {\n return html`\n <div class=\"button-vertical-divider-container\">\n <button\n class=${classMap(this.getButtonClasses())}\n ?disabled=${this.disabled}\n >\n <div class=\"content-container\">\n ${this.hasLeadingIcon\n ? html`<div class=\"icon leading\">\n <slot name=\"leading-icon\"></slot>\n </div>`\n : nothing}\n <div class=\"text-container\">\n <div class=\"label\"><slot name=\"label\"></slot></div>\n ${this.hasStatus\n ? html`<div class=\"status\"><slot name=\"status\"></slot></div>`\n : nothing}\n </div>\n ${this.hasTrailingIcon\n ? html`<div class=\"icon-container\">\n ${this.hasTrailingIcon2\n ? html`<slot\n name=\"trailing-icon2\"\n class=\"icon trailing\"\n ></slot>`\n : nothing}\n <slot name=\"trailing-icon\" class=\"icon trailing\"></slot>\n </div>`\n : nothing}\n </div>\n </button>\n ${this.dividerRight\n ? html`<div class=\"divider-right\"></div>`\n : html`<div class=\"divider-right\" style=\"visibility: hidden;\"></div>`}\n </div>\n <div\n class=${classMap({\n 'integration-vessel-menu-container': true,\n show: this.activated,\n })}\n @click=${this.onIntegrationVesselMenuClick}\n >\n <slot name=\"integration-vessel-menu\"></slot>\n </div>\n `;\n }\n\n override render() {\n switch (this.type) {\n case IntegrationButtonType.hug: {\n return this.renderHug();\n }\n case IntegrationButtonType.rich: {\n return this.renderRich();\n }\n case IntegrationButtonType.regular:\n default: {\n return this.renderRegular();\n }\n }\n }\n\n static override styles = unsafeCSS(compentStyle);\n\n override updated() {\n let dividerHeight = 40;\n if (this.type === IntegrationButtonType.hug) {\n dividerHeight = 32;\n }\n if (this.type === IntegrationButtonType.rich) {\n dividerHeight = 64;\n }\n this.style.setProperty(\n '--integration-button-divider-height',\n `${dividerHeight}px`\n );\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-integration-button': ObcIntegrationButton;\n }\n}\n"],"names":["IntegrationButtonVariant","IntegrationButtonType"],"mappings":"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"}
@@ -279,6 +279,7 @@ const compentStyle = css`* {
279
279
  flex-direction: column;
280
280
  align-items: flex-start;
281
281
  flex-grow: 1;
282
+ text-align: left;
282
283
  }
283
284
 
284
285
  :is(.wrapper option:not(.fleet-option)) .text-container .label {
@@ -1 +1 @@
1
- {"version":3,"file":"integration-dropdown-button.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"integration-dropdown-button.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oicl/openbridge-webcomponents-full-bundle",
3
- "version": "2.0.0-next.120",
3
+ "version": "2.0.0-next.121",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -140,7 +140,9 @@
140
140
  "typecheck": "tsc --noEmit",
141
141
  "format": "prettier \"**/*.{cjs,html,js,json,md,ts,css}\" --write",
142
142
  "format:check": "prettier \"**/*.{cjs,html,js,json,md,ts,css}\" --check",
143
- "lint": "npm run lint:mixins && npm run lint:variables && npm run lint:icons && npm run lint:slots && npm run lint:lit-analyzer && npm run lint:eslint",
143
+ "lint": "npm run lint:mixins && npm run lint:variables && npm run lint:icons && npm run lint:slots && npm run lint:agents && npm run lint:lit-analyzer && npm run lint:eslint",
144
+ "agents:sync": "tsx ./script/sync-agent-docs.ts",
145
+ "lint:agents": "tsx ./script/sync-agent-docs.ts --check",
144
146
  "lint:mixins": "tsx ./script/check-css-mixins.ts",
145
147
  "lint:slots": "tsx ./script/check-slot-event-docs.ts",
146
148
  "lint:variables": "tsx ./script/check-css-variables.ts",
@@ -0,0 +1,189 @@
1
+ import type {AgentDoc} from './frontmatter.js';
2
+
3
+ export const ROUTING_MARKER = 'agents:routing';
4
+
5
+ const SYNC_CMD = 'npm run agents:sync -w packages/openbridge-webcomponents';
6
+
7
+ /**
8
+ * Rewrites sibling links so they still resolve from an adapter directory.
9
+ *
10
+ * Canonical docs link to each other by bare filename — `[jsdoc.md](jsdoc.md)` —
11
+ * which is correct inside `docs/agents/` and broken once the body is copied
12
+ * into `.github/instructions/` or `.cursor/rules/`, where the sibling is named
13
+ * `jsdoc.instructions.md` / `jsdoc.mdc` or does not exist at all.
14
+ *
15
+ * They are repointed at the canonical file rather than at the neighbouring
16
+ * adapter: the canonical doc is the thing a reader should land on, and one
17
+ * rewrite then works for every adapter. `.github/instructions/`, `.cursor/rules/`
18
+ * and `docs/agents/` are all two levels below the repository root, so links
19
+ * already written as `../../…` need no adjustment.
20
+ *
21
+ * Only names in `siblings` are touched, so an unrelated bare `.md` link is left
22
+ * alone instead of being silently repointed at a file that does not exist.
23
+ */
24
+ export function rewriteSiblingLinks(
25
+ body: string,
26
+ siblings: ReadonlySet<string>
27
+ ): string {
28
+ return body.replace(
29
+ /\]\((?!\.\.\/|\/|https?:)([A-Za-z0-9._-]+)\.md(#[^)]*)?\)/g,
30
+ (match, name: string, hash: string | undefined) =>
31
+ siblings.has(name)
32
+ ? `](../../docs/agents/${name}.md${hash ?? ''})`
33
+ : match
34
+ );
35
+ }
36
+
37
+ function banner(source: string): string {
38
+ return [
39
+ '<!-- GENERATED FILE — DO NOT EDIT.',
40
+ ` Source: ${source}`,
41
+ ` Regenerate: ${SYNC_CMD} -->`,
42
+ ].join('\n');
43
+ }
44
+
45
+ /**
46
+ * Copilot path-scoped instruction file: `applyTo` frontmatter plus the full
47
+ * body. The body is copied rather than referenced because a pointer would
48
+ * defeat the glob auto-attachment this file exists to provide.
49
+ */
50
+ export function renderInstructionsFile(
51
+ doc: AgentDoc,
52
+ siblings: ReadonlySet<string> = new Set()
53
+ ): string {
54
+ return [
55
+ '---',
56
+ `applyTo: "${doc.globs.join(',')}"`,
57
+ '---',
58
+ '',
59
+ banner(doc.sourcePath),
60
+ '',
61
+ rewriteSiblingLinks(doc.body, siblings).replace(/\n*$/, '\n'),
62
+ ].join('\n');
63
+ }
64
+
65
+ /**
66
+ * Cursor rule: `globs` + `alwaysApply: false` is Cursor's "Apply to Specific
67
+ * Files" type — the direct equivalent of Copilot's `applyTo`.
68
+ *
69
+ * `description` is deliberately omitted. Cursor's four rule types are distinct
70
+ * frontmatter combinations, and adding a description alongside globs selects
71
+ * "Apply Intelligently" (the agent decides) instead of deterministic
72
+ * attachment. The description still reaches the reader: the body opens with the
73
+ * document's own heading.
74
+ *
75
+ * Negative globs are dropped. Cursor documents `globs` as a comma-separated
76
+ * list and says nothing about `!` negation, so a pattern it cannot parse could
77
+ * take the whole line with it. The only doc affected is `jsdoc.md`, whose
78
+ * exclusions are generated directories a developer rarely opens by hand.
79
+ */
80
+ export function renderCursorRule(
81
+ doc: AgentDoc,
82
+ siblings: ReadonlySet<string> = new Set()
83
+ ): string {
84
+ const globs = doc.globs.filter((g) => !g.startsWith('!'));
85
+ return [
86
+ '---',
87
+ `globs: ${globs.join(',')}`,
88
+ 'alwaysApply: false',
89
+ '---',
90
+ '',
91
+ banner(doc.sourcePath),
92
+ '',
93
+ rewriteSiblingLinks(doc.body, siblings).replace(/\n*$/, '\n'),
94
+ ].join('\n');
95
+ }
96
+
97
+ /** Shared routing table (no markers) for AGENTS.md and copilot-instructions.md. */
98
+ export function renderRoutingTable(docs: AgentDoc[]): string {
99
+ const rows = [...docs]
100
+ .sort((a, b) => a.name.localeCompare(b.name))
101
+ .map((doc) => {
102
+ const scope = doc.globs.map((g) => `\`${g}\``).join('<br>');
103
+ return `| [${doc.name}](${doc.sourcePath}) | ${scope} | ${doc.description} |`;
104
+ });
105
+ return [
106
+ '| Doc | Scope (globs) | Description |',
107
+ '| --- | --- | --- |',
108
+ ...rows,
109
+ ].join('\n');
110
+ }
111
+
112
+ /**
113
+ * Repo-wide Copilot entry point. Unlike the path-scoped files this one carries
114
+ * no bodies — it has no globs of its own, so a pointer costs nothing here.
115
+ */
116
+ export function renderCopilotInstructions(docs: AgentDoc[]): string {
117
+ return [
118
+ banner('docs/agents/*.md'),
119
+ '',
120
+ '# GitHub Copilot Custom Instructions',
121
+ '',
122
+ '> **Canonical instructions live in [`AGENTS.md`](../AGENTS.md)**, and the',
123
+ '> path-scoped rules in [`docs/agents/`](../docs/agents/). This file exists',
124
+ '> because Copilot reads `.github/copilot-instructions.md` by convention.',
125
+ '',
126
+ 'This repository contains the Openbridge Web Components library, a collection',
127
+ 'of maritime navigation and automation UI components built with Lit and',
128
+ 'TypeScript.',
129
+ '',
130
+ '- **Start here →** [`AGENTS.md`](../AGENTS.md).',
131
+ '- Path-scoped rules are auto-attached from `.github/instructions/`, which is',
132
+ ' generated from `docs/agents/`. Read the canonical file, not the copy.',
133
+ '- Ask for clarification (e.g. a list of questions) before implementing',
134
+ ' significant changes.',
135
+ '',
136
+ '## Available Instruction Files',
137
+ '',
138
+ renderRoutingTable(docs),
139
+ '',
140
+ ].join('\n');
141
+ }
142
+
143
+ /**
144
+ * Claude Code entry point. Generated locally; stays gitignored.
145
+ *
146
+ * A pure pointer by design. Claude Code reads `CLAUDE.md` by convention, so the
147
+ * file exists to route it to the canonical instructions — it deliberately holds
148
+ * no rules of its own. Anything that applies to the whole team belongs in
149
+ * `AGENTS.md`; anything path-scoped belongs in `docs/agents/`. Neither is
150
+ * tool-specific, so neither belongs here.
151
+ */
152
+ export function renderClaudeMd(): string {
153
+ return [
154
+ banner('the agents:sync generator'),
155
+ '',
156
+ '# CLAUDE.md',
157
+ '',
158
+ 'All repository instructions live in **[AGENTS.md](AGENTS.md)** — read and',
159
+ 'follow it, including the path-scoped rules in',
160
+ '[`docs/agents/`](docs/agents/) that it routes to.',
161
+ '',
162
+ 'This file intentionally adds nothing of its own. It exists because Claude',
163
+ 'Code reads `CLAUDE.md` by convention; the instructions are tool-neutral and',
164
+ 'shared with every other agent.',
165
+ '',
166
+ ].join('\n');
167
+ }
168
+
169
+ /** Replaces the text between `<!-- marker:start -->` and `<!-- marker:end -->`. */
170
+ export function replaceMarkedBlock(
171
+ content: string,
172
+ marker: string,
173
+ replacement: string
174
+ ): string {
175
+ const start = `<!-- ${marker}:start -->`;
176
+ const end = `<!-- ${marker}:end -->`;
177
+ const startIdx = content.indexOf(start);
178
+ const endIdx = content.indexOf(end);
179
+ if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) {
180
+ throw new Error(`markers "${start}" / "${end}" not found`);
181
+ }
182
+ return (
183
+ content.slice(0, startIdx + start.length) +
184
+ '\n' +
185
+ replacement +
186
+ '\n' +
187
+ content.slice(endIdx)
188
+ );
189
+ }
@@ -0,0 +1,118 @@
1
+ import path from 'node:path';
2
+
3
+ /** One canonical agent-instruction document under `docs/agents/`. */
4
+ export interface AgentDoc {
5
+ name: string;
6
+ description: string;
7
+ globs: string[];
8
+ body: string;
9
+ sourcePath: string;
10
+ }
11
+
12
+ const SCALAR_KEYS = ['name', 'description'] as const;
13
+ const LIST_KEYS = ['globs'] as const;
14
+
15
+ function unquote(value: string): string {
16
+ const trimmed = value.trim();
17
+ if (
18
+ (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length > 1) ||
19
+ (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 1)
20
+ ) {
21
+ return trimmed.slice(1, -1);
22
+ }
23
+ return trimmed;
24
+ }
25
+
26
+ /**
27
+ * Parses the strict frontmatter subset used by `docs/agents/*.md`.
28
+ *
29
+ * Deliberately not a YAML parser: it accepts `key: value` scalars and
30
+ * ` - item` lists for a fixed key set and throws on anything else, so a
31
+ * valid-but-unsupported construct can never be silently mis-emitted.
32
+ */
33
+ export function parseAgentDoc(raw: string, sourcePath: string): AgentDoc {
34
+ const lines = raw.split('\n');
35
+ if (lines[0].trim() !== '---') {
36
+ throw new Error(`${sourcePath}: must start with a "---" frontmatter block`);
37
+ }
38
+ const end = lines.indexOf('---', 1);
39
+ if (end === -1) {
40
+ throw new Error(`${sourcePath}: frontmatter block is never closed`);
41
+ }
42
+
43
+ const scalars = new Map<string, string>();
44
+ const lists = new Map<string, string[]>();
45
+ let currentList: string[] | null = null;
46
+
47
+ for (let i = 1; i < end; i++) {
48
+ const line = lines[i];
49
+ const lineNo = i + 1;
50
+ if (line.trim() === '') continue;
51
+
52
+ if (line.startsWith(' - ') || line.startsWith('- ')) {
53
+ if (!currentList) {
54
+ throw new Error(
55
+ `${sourcePath}: list item with no key on line ${lineNo}`
56
+ );
57
+ }
58
+ currentList.push(unquote(line.slice(line.indexOf('- ') + 2)));
59
+ continue;
60
+ }
61
+
62
+ const match = /^([A-Za-z][A-Za-z0-9_]*):(.*)$/.exec(line);
63
+ if (!match) {
64
+ throw new Error(`${sourcePath}: cannot parse line ${lineNo}: ${line}`);
65
+ }
66
+ const [, key, rest] = match;
67
+
68
+ if ((SCALAR_KEYS as readonly string[]).includes(key)) {
69
+ scalars.set(key, unquote(rest));
70
+ currentList = null;
71
+ } else if ((LIST_KEYS as readonly string[]).includes(key)) {
72
+ if (rest.trim() !== '') {
73
+ throw new Error(
74
+ `${sourcePath}: "${key}" must be a block list, not an inline value (line ${lineNo})`
75
+ );
76
+ }
77
+ currentList = [];
78
+ lists.set(key, currentList);
79
+ } else {
80
+ throw new Error(
81
+ `${sourcePath}: unsupported key "${key}" on line ${lineNo}`
82
+ );
83
+ }
84
+ }
85
+
86
+ for (const key of SCALAR_KEYS) {
87
+ if (!scalars.get(key)) {
88
+ throw new Error(
89
+ `${sourcePath}: "${key}" is required and must be non-empty`
90
+ );
91
+ }
92
+ }
93
+ const globs = lists.get('globs') ?? [];
94
+ if (globs.length === 0) {
95
+ throw new Error(`${sourcePath}: "globs" must list at least one pattern`);
96
+ }
97
+
98
+ const name = scalars.get('name')!;
99
+ const expected = path.basename(sourcePath, '.md');
100
+ if (name !== expected) {
101
+ throw new Error(
102
+ `${sourcePath}: name "${name}" does not match filename "${expected}"`
103
+ );
104
+ }
105
+
106
+ const body = lines
107
+ .slice(end + 1)
108
+ .join('\n')
109
+ .replace(/^\n+/, '');
110
+
111
+ return {
112
+ name,
113
+ description: scalars.get('description')!,
114
+ globs,
115
+ body,
116
+ sourcePath,
117
+ };
118
+ }
@@ -56,7 +56,7 @@
56
56
  * Dynamic slot names (`<slot name="${expr}">` or `<slot name="pre-${id}-post">`)
57
57
  * are skipped for the missing-slot check because their concrete names cannot be
58
58
  * known statically; document them with a placeholder tag such as
59
- * `@slot tab-<id>-icon` (see .cursor/rules/comments.mdc § Structured-tag rules).
59
+ * `@slot tab-<id>-icon` (see docs/agents/jsdoc.md § Structured-tag rules).
60
60
  *
61
61
  * Usage:
62
62
  * ```bash
@@ -413,7 +413,7 @@ async function run(): Promise<void> {
413
413
  printByFile(errors, (m) => console.error(m));
414
414
  console.error(
415
415
  `\n❌ Slot & event documentation audit failed. Add the missing @slot/@fires ` +
416
- `tags (or remove the phantom ones). See .cursor/rules/comments.mdc.`
416
+ `tags (or remove the phantom ones). See docs/agents/jsdoc.md.`
417
417
  );
418
418
  process.exitCode = 1;
419
419
  return;