@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.118 → 2.0.0-next.119
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/manager.ts +18 -16
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +2 -1
- package/dist/automation/vertical-line/vertical-line.d.ts +6 -0
- package/dist/automation/vertical-line/vertical-line.d.ts.map +1 -1
- package/dist/automation/vertical-line/vertical-line.js.map +1 -1
- package/dist/components/pivot-item-group/pivot-item-group.d.ts +1 -0
- package/dist/components/pivot-item-group/pivot-item-group.d.ts.map +1 -1
- package/dist/components/pivot-item-group/pivot-item-group.js.map +1 -1
- package/eslint.config.mjs +355 -0
- package/package.json +3 -1
- package/script/eslint-rules.test.ts +230 -0
- package/src/ar/building-blocks/poi-graphic-line/poi-graphic-line.stories.ts +1 -1
- package/src/ar/building-blocks/poi-header/poi-header.stories.ts +1 -1
- package/src/ar/building-blocks/poi-line/poi-line.stories.ts +1 -1
- package/src/ar/building-blocks/poi-pointer/poi-pointer.stories.ts +1 -1
- package/src/ar/building-blocks/poi-selection-frame/poi-selection-frame.stories.ts +1 -1
- package/src/ar/building-blocks/ruler-pointer/ruler-pointer.stories.ts +1 -1
- package/src/ar/chart-object-vessel-button/chart-object-vessel-button.stories.ts +1 -1
- package/src/ar/poi/poi-aton.stories.ts +1 -1
- package/src/ar/poi/poi-data.stories.ts +1 -1
- package/src/ar/poi/poi-vessel.stories.ts +1 -1
- package/src/ar/poi/poi.stories.ts +1 -1
- package/src/ar/poi-button/poi-button-aton.stories.ts +1 -1
- package/src/ar/poi-button/poi-button-data.stories.ts +1 -1
- package/src/ar/poi-button/poi-button-vessel.stories.ts +1 -1
- package/src/ar/poi-button/poi-button.stories.ts +1 -1
- package/src/ar/poi-card/poi-card.stories.ts +1 -1
- package/src/ar/poi-card-header/poi-card-header.stories.ts +1 -1
- package/src/ar/poi-controller/poi-controller.stories.ts +1 -1
- package/src/ar/poi-group/poi-group.stories.ts +1 -1
- package/src/ar/poi-layer/poi-layer.stories.ts +1 -1
- package/src/ar/poi-layer-stack/poi-layer-stack.stories.ts +1 -1
- package/src/ar/poi-object/poi-object-aton.stories.ts +1 -1
- package/src/ar/poi-object/poi-object-data.stories.ts +1 -1
- package/src/ar/poi-object/poi-object-vessel.stories.ts +1 -1
- package/src/ar/poi-object/poi-object.stories.ts +1 -1
- package/src/automation/automation-input-modal/automation-input-modal.stories.ts +1 -1
- package/src/automation/automation-readout/automation-readout.stories.ts +1 -1
- package/src/automation/automation-tank/automation-tank.stories.ts +1 -1
- package/src/automation/bus/bus.stories.ts +1 -1
- package/src/automation/corner-line/corner-line.stories.ts +1 -1
- package/src/automation/direction-line/direction-line.stories.ts +1 -1
- package/src/automation/end-point-line/end-point-line.stories.ts +1 -1
- package/src/automation/horizontal-line/horizontal-line.stories.ts +1 -1
- package/src/automation/line-cross/line-cross.stories.ts +1 -1
- package/src/automation/line-overlap/line-overlap.stories.ts +1 -1
- package/src/automation/three-way-line/three-way-line.stories.ts +1 -1
- package/src/automation/vertical-line/vertical-line.stories.ts +1 -1
- package/src/automation/vertical-line/vertical-line.ts +2 -1
- package/src/bars-graphs/area-graph/area-graph.stories.ts +1 -1
- package/src/bars-graphs/donut-chart/donut-chart.stories.ts +1 -1
- package/src/bars-graphs/line-graph/line-graph.stories.ts +1 -1
- package/src/bars-graphs/pie-chart/pie-chart.stories.ts +1 -1
- package/src/bars-graphs/polar-chart/polar-chart.stories.ts +1 -1
- package/src/bars-graphs/radial-bar-chart/radial-bar-chart.stories.ts +1 -1
- package/src/building-blocks/bar-horizontal/bar-horizontal.stories.ts +1 -1
- package/src/building-blocks/bar-vertical/bar-vertical.stories.ts +1 -1
- package/src/building-blocks/chart-line/chart-line-base.stories.ts +1 -1
- package/src/building-blocks/instrument-radial/instrument-radial.stories.ts +1 -1
- package/src/building-blocks/readout-block/readout-block.stories.ts +1 -1
- package/src/components/advice-floating-item/advice-floating-item.stories.ts +1 -1
- package/src/components/advice-menu-item/advice-menu-item.stories.ts +1 -1
- package/src/components/advice-message-item/advice-message-item.stories.ts +1 -1
- package/src/components/alert-frame/alert-frame.stories.ts +1 -1
- package/src/components/attachment-list-item/attachment-list-item.stories.ts +1 -1
- package/src/components/audio-output/audio-output.stories.ts +1 -1
- package/src/components/audio-recording-item/audio-recording-item.stories.ts +1 -1
- package/src/components/automation-button-readout-stack/automation-button-readout-stack.stories.ts +1 -1
- package/src/components/badge/badge.stories.ts +1 -1
- package/src/components/calendar/calendar.stories.ts +1 -1
- package/src/components/command-button/command-button.stories.ts +1 -1
- package/src/components/command-menu/command-menu.stories.ts +1 -1
- package/src/components/communication-table/communication-table.stories.ts +1 -1
- package/src/components/context-menu-input/context-menu-input.stories.ts +1 -1
- package/src/components/date-item/date-item.stories.ts +1 -1
- package/src/components/event-item/event-item.stories.ts +1 -1
- package/src/components/event-list/event-list.stories.ts +1 -1
- package/src/components/form-container/form-container.stories.ts +1 -0
- package/src/components/form-footer-container/form-footer-container.stories.ts +1 -0
- package/src/components/form-group/form-group.stories.ts +1 -0
- package/src/components/form-item/form-item.stories.ts +1 -1
- package/src/components/keyboard-full/keyboard-full.stories.ts +1 -1
- package/src/components/keyboard-numeric/keyboard-numeric.stories.ts +1 -1
- package/src/components/menu-button/menu-button.stories.ts +1 -1
- package/src/components/message-menu-item/message-menu-item.stories.ts +1 -1
- package/src/components/notification-badge-button/notification-badge-button.stories.ts +1 -1
- package/src/components/notification-floating-item/notification-floating-item.stories.ts +1 -1
- package/src/components/notification-menu-item/notification-menu-item.stories.ts +1 -1
- package/src/components/notification-message-item/notification-message-item.stories.ts +1 -1
- package/src/components/pivot-item-group/pivot-item-group.ts +1 -0
- package/src/components/rich-button/rich-button.stories.ts +1 -1
- package/src/components/sequence-card/sequence-card.stories.ts +1 -1
- package/src/components/sequence-connector/sequence-connector.stories.ts +1 -1
- package/src/components/sequence-item/sequence-item.stories.ts +1 -0
- package/src/components/sequence-loading-spinner/sequence-loading-spinner.stories.ts +1 -1
- package/src/components/sequence-modal/sequence-modal.stories.ts +1 -1
- package/src/components/sequence-step/sequence-step.stories.ts +1 -0
- package/src/components/sequence-toolbar/sequence-toolbar.stories.ts +1 -0
- package/src/components/slide-button/slide-button.stories.ts +1 -1
- package/src/components/split-button/split-button.stories.ts +1 -1
- package/src/components/status-indicator/status-indicator.stories.ts +1 -1
- package/src/components/table/table.stories.ts +1 -1
- package/src/components/table-header-item/table-header-item.stories.ts +1 -1
- package/src/components/textbox/textbox.stories.ts +1 -1
- package/src/components/title-container/title-container.stories.ts +1 -1
- package/src/components/toggle-button-three-state/toggle-button-three-state.stories.ts +1 -1
- package/src/components/toggletip/toggletip.stories.ts +1 -1
- package/src/components/tree-navigation/tree-navigation.stories.ts +1 -1
- package/src/components/tree-navigation-item/tree-navigation-item.stories.ts +1 -1
- package/src/integration-systems/integration-app-bar/integration-app-bar.stories.ts +1 -1
- package/src/integration-systems/integration-bar/integration-bar.stories.ts +1 -1
- package/src/integration-systems/integration-bar-dropdown/integration-bar-dropdown.stories.ts +1 -1
- package/src/integration-systems/integration-button/integration-button.stories.ts +1 -1
- package/src/integration-systems/integration-dropdown-button/integration-dropdown-button.stories.ts +1 -1
- package/src/integration-systems/integration-fleet-button/integration-fleet-button.stories.ts +1 -1
- package/src/integration-systems/integration-tabs/integration-tabs.stories.ts +1 -1
- package/src/integration-systems/integration-vessel-menu/integration-vessel-menu.stories.ts +1 -1
- package/src/integration-systems/integration-vessel-selector/integration-vessel-selector.stories.ts +1 -1
- package/src/navigation-instruments/azimuth-thruster-labeled/azimuth-thruster-labeled.stories.ts +1 -1
- package/src/navigation-instruments/current/current.stories.ts +1 -1
- package/src/navigation-instruments/draft-trim/draft-trim.stories.ts +1 -1
- package/src/navigation-instruments/gauge-radial-proportional/gauge-radial-proportional.stories.ts +1 -1
- package/src/navigation-instruments/graph-mini/graph-mini.stories.ts +1 -1
- package/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts +1 -1
- package/src/navigation-instruments/instrument-field/instrument-field.stories.ts +1 -1
- package/src/navigation-instruments/pitch-roll-heave/pitch-roll-heave.stories.ts +1 -1
- package/src/navigation-instruments/pitch-roll-yaw/pitch-roll-yaw.stories.ts +1 -1
- package/src/navigation-instruments/position-deviation/position-deviation.stories.ts +1 -1
- package/src/navigation-instruments/readout/readout.stories.ts +1 -1
- package/src/navigation-instruments/readout-list/readout-list.stories.ts +1 -1
- package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +1 -1
- package/src/navigation-instruments/speed-arrows/speed-arrows.stories.ts +1 -1
- package/src/navigation-instruments/speed-directions/speed-directions.stories.ts +1 -1
- package/src/navigation-instruments/top-view-propulsion/top-view-propulsion.stories.ts +1 -1
- package/src/navigation-instruments/velocity-projection-plot/velocity-projection-plot.stories.ts +1 -1
- package/src/navigation-instruments/watch/watch.stories.ts +1 -1
- package/src/navigation-instruments/watch-flat/watch-flat.stories.ts +1 -1
- package/src/navigation-instruments/wind-propulsion/wind-propulsion.stories.ts +1 -1
- package/src/pages/alert-detail-page/alert-detail-page.stories.ts +1 -1
- package/src/pages/alert-list-page-small/alert-list-page-small.stories.ts +1 -1
- package/vitest.browser.config.ts +4 -2
- package/vitest.config.ts +10 -0
package/custom-elements.json
CHANGED
|
@@ -32302,7 +32302,7 @@
|
|
|
32302
32302
|
"declarations": [
|
|
32303
32303
|
{
|
|
32304
32304
|
"kind": "class",
|
|
32305
|
-
"description": "",
|
|
32305
|
+
"description": "Vertical line component\n\nThe vertical line is 24px * length + 1px. +1 px to make sure that connecting lines are overlapping, to hide the gap between them.",
|
|
32306
32306
|
"name": "ObcVerticalLine",
|
|
32307
32307
|
"members": [
|
|
32308
32308
|
{
|
|
@@ -32359,6 +32359,7 @@
|
|
|
32359
32359
|
"name": "LitElement",
|
|
32360
32360
|
"package": "lit"
|
|
32361
32361
|
},
|
|
32362
|
+
"deprecated": "true",
|
|
32362
32363
|
"tagName": "obc-vertical-line",
|
|
32363
32364
|
"customElement": true
|
|
32364
32365
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
2
|
import { LineMedium, LineType } from '../index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Vertical line component
|
|
5
|
+
*
|
|
6
|
+
* The vertical line is 24px * length + 1px. +1 px to make sure that connecting lines are overlapping, to hide the gap between them.
|
|
7
|
+
* @deprecated
|
|
8
|
+
*/
|
|
3
9
|
export declare class ObcVerticalLine extends LitElement {
|
|
4
10
|
medium: LineMedium;
|
|
5
11
|
lineType: LineType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertical-line.d.ts","sourceRoot":"","sources":["../../../src/automation/vertical-line/vertical-line.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAY,MAAM,KAAK,CAAC;AAE1C,OAAO,EAAC,UAAU,EAAa,QAAQ,EAAY,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"vertical-line.d.ts","sourceRoot":"","sources":["../../../src/automation/vertical-line/vertical-line.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAY,MAAM,KAAK,CAAC;AAE1C,OAAO,EAAC,UAAU,EAAa,QAAQ,EAAY,MAAM,aAAa,CAAC;AAGvE;;;;;GAKG;AACH,qBACa,eAAgB,SAAQ,UAAU;IACnB,MAAM,EAAE,UAAU,CAAqB;IACvC,QAAQ,EAAE,QAAQ,CAAkB;IACpC,MAAM,EAAE,MAAM,CAAK;IAEpC,MAAM;IAgEf,OAAgB,MAAM,0BAWpB;CACH;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,eAAe,CAAC;KACtC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertical-line.js","sources":["../../../src/automation/vertical-line/vertical-line.ts"],"sourcesContent":["import {LitElement, html, css} from 'lit';\nimport {property} from 'lit/decorators.js';\nimport {LineMedium, lineColor, LineType, lineWidth} from '../index.js';\nimport {customElement} from '../../decorator.js';\n\n
|
|
1
|
+
{"version":3,"file":"vertical-line.js","sources":["../../../src/automation/vertical-line/vertical-line.ts"],"sourcesContent":["import {LitElement, html, css} from 'lit';\nimport {property} from 'lit/decorators.js';\nimport {LineMedium, lineColor, LineType, lineWidth} from '../index.js';\nimport {customElement} from '../../decorator.js';\n\n/**\n * Vertical line component\n *\n * The vertical line is 24px * length + 1px. +1 px to make sure that connecting lines are overlapping, to hide the gap between them.\n * @deprecated\n */\n@customElement('obc-vertical-line')\nexport class ObcVerticalLine extends LitElement {\n @property({type: String}) medium: LineMedium = LineMedium.normal;\n @property({type: String}) lineType: LineType = LineType.fluid;\n @property({type: Number}) length: number = 1;\n\n override render() {\n if (this.lineType === LineType.connector) {\n const length = this.length * 24;\n return html`<svg\n width=\"24\"\n height=\"${length}\"\n viewBox=\"0 0 24 ${length}\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <line\n y1=\"0\"\n x1=\"12\"\n y2=\"${length}\"\n x2=\"12\"\n stroke-width=\"1\"\n stroke=\"var(--element-neutral-color)\"\n stroke-dasharray=\"4,2\"\n stroke-dashoffset=\"2\"\n />\n </svg> `;\n }\n\n const length = this.length * 24 + 1;\n const color = lineColor(this.medium);\n const width = lineWidth(this.lineType);\n return html`\n <div class=\"wrapper\" style=\"width: 24px; height: ${this.length * 24}px;\">\n <svg\n class=\"line\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 -.5 24 ${length}\"\n width=\"24\"\n height=${length}\n >\n <line\n y1=\"-.5\"\n x1=\"12\"\n y2=\"${length - 0.5}\"\n x2=\"12\"\n stroke-width=${width}\n stroke=\"var(${color.inner})\"\n />\n <line\n y1=\"-.5\"\n x1=${12 - 0.5 - width / 2}\n y2=\"${length - 0.5}\"\n x2=${12 - 0.5 - width / 2}\n stroke-width=\"1\"\n stroke=\"var(${color.outer})\"\n />\n <line\n y1=\"-.5\"\n x1=${12 + 0.5 + width / 2}\n y2=\"${length - 0.5}\"\n x2=${12 + 0.5 + width / 2}\n stroke-width=\"1\"\n stroke=\"var(${color.outer})\"\n />\n </svg>\n </div>\n `;\n }\n\n static override styles = css`\n :host {\n display: block;\n line-height: 0;\n }\n\n svg {\n position: relative;\n top: -0.5px;\n left: -12px;\n }\n `;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-vertical-line': ObcVerticalLine;\n }\n}\n"],"names":["length"],"mappings":";;;;;;;;;;;;;;AAYO,IAAM,kBAAN,cAA8B,WAAW;AAAA,EAAzC,cAAA;AAAA,UAAA,GAAA,SAAA;AACqB,SAAA,SAAqB,WAAW;AAChC,SAAA,WAAqB,SAAS;AAC9B,SAAA,SAAiB;AAAA,EAAA;AAAA,EAElC,SAAS;AAChB,QAAI,KAAK,aAAa,SAAS,WAAW;AACxC,YAAMA,UAAS,KAAK,SAAS;AAC7B,aAAO;AAAA;AAAA,kBAEKA,OAAM;AAAA,0BACEA,OAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAOhBA,OAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQlB;AAEA,UAAM,SAAS,KAAK,SAAS,KAAK;AAClC,UAAM,QAAQ,UAAU,KAAK,MAAM;AACnC,UAAM,QAAQ,UAAU,KAAK,QAAQ;AACrC,WAAO;AAAA,yDAC8C,KAAK,SAAS,EAAE;AAAA;AAAA;AAAA;AAAA,+BAI1C,MAAM;AAAA;AAAA,mBAElB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKP,SAAS,GAAG;AAAA;AAAA,2BAEH,KAAK;AAAA,0BACN,MAAM,KAAK;AAAA;AAAA;AAAA;AAAA,iBAIpB,KAAK,MAAM,QAAQ,CAAC;AAAA,kBACnB,SAAS,GAAG;AAAA,iBACb,KAAK,MAAM,QAAQ,CAAC;AAAA;AAAA,0BAEX,MAAM,KAAK;AAAA;AAAA;AAAA;AAAA,iBAIpB,KAAK,MAAM,QAAQ,CAAC;AAAA,kBACnB,SAAS,GAAG;AAAA,iBACb,KAAK,MAAM,QAAQ,CAAC;AAAA;AAAA,0BAEX,MAAM,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,EAKnC;AAcF;AAjFa,gBAqEK,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AApEC,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GADb,gBACe,WAAA,UAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAFb,gBAEe,WAAA,YAAA,CAAA;AACA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAHb,gBAGe,WAAA,UAAA,CAAA;AAHf,kBAAN,gBAAA;AAAA,EADN,cAAc,mBAAmB;AAAA,GACrB,eAAA;"}
|
|
@@ -46,6 +46,7 @@ import { ObcPivotItemDirection } from '../pivot-item/pivot-item.js';
|
|
|
46
46
|
*
|
|
47
47
|
* @slot - Default slot for one or more `<obc-pivot-item>` elements.
|
|
48
48
|
* @fires {CustomEvent<{selectedValue: string}>} change - Fired when the selected value changes.
|
|
49
|
+
* @stable
|
|
49
50
|
*/
|
|
50
51
|
export declare class ObcPivotItemGroup extends LitElement {
|
|
51
52
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pivot-item-group.d.ts","sourceRoot":"","sources":["../../../src/components/pivot-item-group/pivot-item-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,cAAc,EAAkB,MAAM,KAAK,CAAC;AAKhE,OAAO,EAAC,qBAAqB,EAAe,MAAM,6BAA6B,CAAC;AAEhF
|
|
1
|
+
{"version":3,"file":"pivot-item-group.d.ts","sourceRoot":"","sources":["../../../src/components/pivot-item-group/pivot-item-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,cAAc,EAAkB,MAAM,KAAK,CAAC;AAKhE,OAAO,EAAC,qBAAqB,EAAe,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAC/C;;;;;OAKG;IACuB,SAAS,EAAE,qBAAqB,CACvB;IAEnC;;;;OAIG;IACuB,aAAa,SAAM;IAE7C;;;;;OAKG;IACwB,aAAa,UAAS;IAEjD,OAAO,CAAC,kBAAkB;IAqB1B,OAAO,CAAC,gBAAgB;IAWf,YAAY;IAMZ,OAAO,CAAC,iBAAiB,EAAE,cAAc;IASzC,MAAM;IAef,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,sBAAsB,EAAE,iBAAiB,CAAC;KAC3C;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pivot-item-group.js","sources":["../../../src/components/pivot-item-group/pivot-item-group.ts"],"sourcesContent":["import {LitElement, PropertyValues, html, unsafeCSS} from 'lit';\nimport {customElement} from '../../decorator.js';\nimport componentStyle from './pivot-item-group.css?inline';\nimport {property} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\nimport {ObcPivotItemDirection, ObcPivotItem} from '../pivot-item/pivot-item.js';\n\n/**\n * `<obc-pivot-item-group>` – A container for managing a set of pivot items (tab-like or segmented controls) with single selection.\n *\n * Groups multiple `<obc-pivot-item>` components and handles their selection logic, ensuring only one item is selected at a time (or none, if allowed). Provides a way to visually and functionally organize navigation or filtering controls in either a horizontal or vertical layout.\n *\n * ### Features\n * - **Single selection management:** Only one pivot item can be selected at a time. Optionally allows deselecting all.\n * - **Layout direction:** Supports `horizontal` (default, items in a row) or `vertical` (items stacked in a column) via the `direction` property.\n * - **Deselect option:** When `allowDeselect` is true, clicking the selected item will deselect it (no item selected).\n * - **Syncs child state:** Automatically updates all child `<obc-pivot-item>` components to reflect the current selection and layout direction.\n * - **Change event:** Emits a `change` event whenever the selection changes, with the new value.\n *\n * ### Usage Guidelines\n * Use `obc-pivot-item-group` to present a set of mutually exclusive options, such as navigation tabs, segmented controls, or filter toggles. Ideal for scenarios where users need to switch between views, categories, or modes.\n * For independent toggles, use a different component (e.g., checkboxes or chips).\n *\n * - Set the `direction` property to `vertical` for sidebar or stacked layouts, or leave as `horizontal` for toolbar/tab layouts.\n * - Use `allowDeselect` if you want to permit users to clear their selection entirely.\n * - Place one or more `<obc-pivot-item>` elements as children; each should have a unique `value` property.\n *\n * **TODO(designer):** Clarify if there are recommended use cases or visual constraints for horizontal vs. vertical layouts, and if there are accessibility guidelines for keyboard navigation.\n *\n * ### Slots and Content Structure\n * | Slot Name | Renders When... | Purpose |\n * |-----------|-----------------|---------|\n * | (default) | Always | Place one or more `<obc-pivot-item>` elements here. Each represents a selectable option. |\n *\n * ### Events\n * - `change` – Fired whenever the selected value changes. Event detail: `{selectedValue: string}`.\n *\n * ### Best Practices and Constraints\n * - Ensure each child `<obc-pivot-item>` has a unique `value` property.\n * - Only use `allowDeselect` if having no selection is a valid state for your use case.\n * - For accessibility, ensure the group and items are labeled appropriately (see `<obc-pivot-item>` docs for ARIA guidance).\n *\n * **Example:**\n * ```html\n * <obc-pivot-item-group direction=\"horizontal\" selectedValue=\"a\">\n * <obc-pivot-item value=\"a\">Option A</obc-pivot-item>\n * <obc-pivot-item value=\"b\">Option B</obc-pivot-item>\n * <obc-pivot-item value=\"c\">Option C</obc-pivot-item>\n * </obc-pivot-item-group>\n * ```\n *\n * @slot - Default slot for one or more `<obc-pivot-item>` elements.\n * @fires {CustomEvent<{selectedValue: string}>} change - Fired when the selected value changes.\n */\n@customElement('obc-pivot-item-group')\nexport class ObcPivotItemGroup extends LitElement {\n /**\n * Layout direction for the group: `'horizontal'` (default, items in a row) or `'vertical'` (items stacked).\n * Changing this updates the direction of all child pivot items.\n *\n * @default ObcPivotItemDirection.horizontal\n */\n @property({type: String}) direction: ObcPivotItemDirection =\n ObcPivotItemDirection.horizontal;\n\n /**\n * The value of the currently selected pivot item.\n * Set this to control selection programmatically, or listen for the `change` event to react to user selection.\n * Defaults to empty string (no selection).\n */\n @property({type: String}) selectedValue = '';\n\n /**\n * If true, allows the user to deselect the currently selected item by clicking it again (resulting in no selection).\n * If false (default), one item is always selected unless `selectedValue` is empty.\n *\n * @availableWhen selectedValue!=''\n */\n @property({type: Boolean}) allowDeselect = false;\n\n private handleItemSelected(event: CustomEvent) {\n event.stopPropagation();\n const {value} = event.detail;\n\n if (this.allowDeselect && this.selectedValue === value) {\n this.selectedValue = '';\n } else {\n this.selectedValue = value;\n }\n\n this.updateChildItems();\n\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: {selectedValue: this.selectedValue},\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private updateChildItems() {\n const items = this.querySelectorAll(\n 'obc-pivot-item'\n ) as NodeListOf<ObcPivotItem>;\n\n items.forEach((item) => {\n item.selected = item.value === this.selectedValue;\n item.direction = this.direction;\n });\n }\n\n override firstUpdated() {\n this.updateComplete.then(() => {\n this.updateChildItems();\n });\n }\n\n override updated(changedProperties: PropertyValues) {\n if (\n changedProperties.has('selectedValue') ||\n changedProperties.has('direction')\n ) {\n this.updateChildItems();\n }\n }\n\n override render() {\n const classes = classMap({\n 'pivot-group': true,\n 'direction-horizontal':\n this.direction === ObcPivotItemDirection.horizontal,\n 'direction-vertical': this.direction === ObcPivotItemDirection.vertical,\n });\n\n return html`\n <div class=${classes} @selected=${this.handleItemSelected}>\n <slot></slot>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-pivot-item-group': ObcPivotItemGroup;\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"pivot-item-group.js","sources":["../../../src/components/pivot-item-group/pivot-item-group.ts"],"sourcesContent":["import {LitElement, PropertyValues, html, unsafeCSS} from 'lit';\nimport {customElement} from '../../decorator.js';\nimport componentStyle from './pivot-item-group.css?inline';\nimport {property} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\nimport {ObcPivotItemDirection, ObcPivotItem} from '../pivot-item/pivot-item.js';\n\n/**\n * `<obc-pivot-item-group>` – A container for managing a set of pivot items (tab-like or segmented controls) with single selection.\n *\n * Groups multiple `<obc-pivot-item>` components and handles their selection logic, ensuring only one item is selected at a time (or none, if allowed). Provides a way to visually and functionally organize navigation or filtering controls in either a horizontal or vertical layout.\n *\n * ### Features\n * - **Single selection management:** Only one pivot item can be selected at a time. Optionally allows deselecting all.\n * - **Layout direction:** Supports `horizontal` (default, items in a row) or `vertical` (items stacked in a column) via the `direction` property.\n * - **Deselect option:** When `allowDeselect` is true, clicking the selected item will deselect it (no item selected).\n * - **Syncs child state:** Automatically updates all child `<obc-pivot-item>` components to reflect the current selection and layout direction.\n * - **Change event:** Emits a `change` event whenever the selection changes, with the new value.\n *\n * ### Usage Guidelines\n * Use `obc-pivot-item-group` to present a set of mutually exclusive options, such as navigation tabs, segmented controls, or filter toggles. Ideal for scenarios where users need to switch between views, categories, or modes.\n * For independent toggles, use a different component (e.g., checkboxes or chips).\n *\n * - Set the `direction` property to `vertical` for sidebar or stacked layouts, or leave as `horizontal` for toolbar/tab layouts.\n * - Use `allowDeselect` if you want to permit users to clear their selection entirely.\n * - Place one or more `<obc-pivot-item>` elements as children; each should have a unique `value` property.\n *\n * **TODO(designer):** Clarify if there are recommended use cases or visual constraints for horizontal vs. vertical layouts, and if there are accessibility guidelines for keyboard navigation.\n *\n * ### Slots and Content Structure\n * | Slot Name | Renders When... | Purpose |\n * |-----------|-----------------|---------|\n * | (default) | Always | Place one or more `<obc-pivot-item>` elements here. Each represents a selectable option. |\n *\n * ### Events\n * - `change` – Fired whenever the selected value changes. Event detail: `{selectedValue: string}`.\n *\n * ### Best Practices and Constraints\n * - Ensure each child `<obc-pivot-item>` has a unique `value` property.\n * - Only use `allowDeselect` if having no selection is a valid state for your use case.\n * - For accessibility, ensure the group and items are labeled appropriately (see `<obc-pivot-item>` docs for ARIA guidance).\n *\n * **Example:**\n * ```html\n * <obc-pivot-item-group direction=\"horizontal\" selectedValue=\"a\">\n * <obc-pivot-item value=\"a\">Option A</obc-pivot-item>\n * <obc-pivot-item value=\"b\">Option B</obc-pivot-item>\n * <obc-pivot-item value=\"c\">Option C</obc-pivot-item>\n * </obc-pivot-item-group>\n * ```\n *\n * @slot - Default slot for one or more `<obc-pivot-item>` elements.\n * @fires {CustomEvent<{selectedValue: string}>} change - Fired when the selected value changes.\n * @stable\n */\n@customElement('obc-pivot-item-group')\nexport class ObcPivotItemGroup extends LitElement {\n /**\n * Layout direction for the group: `'horizontal'` (default, items in a row) or `'vertical'` (items stacked).\n * Changing this updates the direction of all child pivot items.\n *\n * @default ObcPivotItemDirection.horizontal\n */\n @property({type: String}) direction: ObcPivotItemDirection =\n ObcPivotItemDirection.horizontal;\n\n /**\n * The value of the currently selected pivot item.\n * Set this to control selection programmatically, or listen for the `change` event to react to user selection.\n * Defaults to empty string (no selection).\n */\n @property({type: String}) selectedValue = '';\n\n /**\n * If true, allows the user to deselect the currently selected item by clicking it again (resulting in no selection).\n * If false (default), one item is always selected unless `selectedValue` is empty.\n *\n * @availableWhen selectedValue!=''\n */\n @property({type: Boolean}) allowDeselect = false;\n\n private handleItemSelected(event: CustomEvent) {\n event.stopPropagation();\n const {value} = event.detail;\n\n if (this.allowDeselect && this.selectedValue === value) {\n this.selectedValue = '';\n } else {\n this.selectedValue = value;\n }\n\n this.updateChildItems();\n\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: {selectedValue: this.selectedValue},\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private updateChildItems() {\n const items = this.querySelectorAll(\n 'obc-pivot-item'\n ) as NodeListOf<ObcPivotItem>;\n\n items.forEach((item) => {\n item.selected = item.value === this.selectedValue;\n item.direction = this.direction;\n });\n }\n\n override firstUpdated() {\n this.updateComplete.then(() => {\n this.updateChildItems();\n });\n }\n\n override updated(changedProperties: PropertyValues) {\n if (\n changedProperties.has('selectedValue') ||\n changedProperties.has('direction')\n ) {\n this.updateChildItems();\n }\n }\n\n override render() {\n const classes = classMap({\n 'pivot-group': true,\n 'direction-horizontal':\n this.direction === ObcPivotItemDirection.horizontal,\n 'direction-vertical': this.direction === ObcPivotItemDirection.vertical,\n });\n\n return html`\n <div class=${classes} @selected=${this.handleItemSelected}>\n <slot></slot>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-pivot-item-group': ObcPivotItemGroup;\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAwDO,IAAM,oBAAN,cAAgC,WAAW;AAAA,EAA3C,cAAA;AAAA,UAAA,GAAA,SAAA;AAOqB,SAAA,YACxB,sBAAsB;AAOE,SAAA,gBAAgB;AAQf,SAAA,gBAAgB;AAAA,EAAA;AAAA,EAEnC,mBAAmB,OAAoB;AAC7C,UAAM,gBAAA;AACN,UAAM,EAAC,UAAS,MAAM;AAEtB,QAAI,KAAK,iBAAiB,KAAK,kBAAkB,OAAO;AACtD,WAAK,gBAAgB;AAAA,IACvB,OAAO;AACL,WAAK,gBAAgB;AAAA,IACvB;AAEA,SAAK,iBAAA;AAEL,SAAK;AAAA,MACH,IAAI,YAAY,UAAU;AAAA,QACxB,QAAQ,EAAC,eAAe,KAAK,cAAA;AAAA,QAC7B,SAAS;AAAA,QACT,UAAU;AAAA,MAAA,CACX;AAAA,IAAA;AAAA,EAEL;AAAA,EAEQ,mBAAmB;AACzB,UAAM,QAAQ,KAAK;AAAA,MACjB;AAAA,IAAA;AAGF,UAAM,QAAQ,CAAC,SAAS;AACtB,WAAK,WAAW,KAAK,UAAU,KAAK;AACpC,WAAK,YAAY,KAAK;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAES,eAAe;AACtB,SAAK,eAAe,KAAK,MAAM;AAC7B,WAAK,iBAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAES,QAAQ,mBAAmC;AAClD,QACE,kBAAkB,IAAI,eAAe,KACrC,kBAAkB,IAAI,WAAW,GACjC;AACA,WAAK,iBAAA;AAAA,IACP;AAAA,EACF;AAAA,EAES,SAAS;AAChB,UAAM,UAAU,SAAS;AAAA,MACvB,eAAe;AAAA,MACf,wBACE,KAAK,cAAc,sBAAsB;AAAA,MAC3C,sBAAsB,KAAK,cAAc,sBAAsB;AAAA,IAAA,CAChE;AAED,WAAO;AAAA,mBACQ,OAAO,cAAc,KAAK,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAI7D;AAGF;AAxFa,kBAuFK,SAAS,UAAU,cAAc;AAhFvB,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAPb,kBAOe,WAAA,aAAA,CAAA;AAQA,gBAAA;AAAA,EAAzB,SAAS,EAAC,MAAM,OAAA,CAAO;AAAA,GAfb,kBAee,WAAA,iBAAA,CAAA;AAQC,gBAAA;AAAA,EAA1B,SAAS,EAAC,MAAM,QAAA,CAAQ;AAAA,GAvBd,kBAuBgB,WAAA,iBAAA,CAAA;AAvBhB,oBAAN,gBAAA;AAAA,EADN,cAAc,sBAAsB;AAAA,GACxB,iBAAA;"}
|
package/eslint.config.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
|
2
2
|
import globals from 'globals';
|
|
3
3
|
import tsParser from '@typescript-eslint/parser';
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import {fileURLToPath} from 'node:url';
|
|
6
7
|
import js from '@eslint/js';
|
|
@@ -159,6 +160,174 @@ function toTitleCase(segment) {
|
|
|
159
160
|
});
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
// --- Component lifecycle tags (AGENTS.md § 3) -------------------------------
|
|
164
|
+
// The class-level JSDoc tag on an `@customElement` class is the single source
|
|
165
|
+
// of truth for a component's lifecycle state; a story's `meta.tags` mirrors it.
|
|
166
|
+
|
|
167
|
+
const LIFECYCLE_JSDOC_TAGS = ['stable', 'beta', 'experimental', 'deprecated'];
|
|
168
|
+
|
|
169
|
+
// `@stable` deliberately maps to no story tag, so a sidebar badge always means
|
|
170
|
+
// "there is a caveat here" rather than decorating every entry.
|
|
171
|
+
const LIFECYCLE_STORY_TAG = {
|
|
172
|
+
beta: 'beta',
|
|
173
|
+
experimental: 'experimental',
|
|
174
|
+
deprecated: 'deprecated',
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Story tags this rule owns and will rewrite. `wip` and `alpha` are the retired
|
|
178
|
+
// names, listed so the autofix removes them.
|
|
179
|
+
const OWNED_STORY_TAGS = new Set([
|
|
180
|
+
'beta',
|
|
181
|
+
'experimental',
|
|
182
|
+
'deprecated',
|
|
183
|
+
'wip',
|
|
184
|
+
'alpha',
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const SRC_DIR = path.join(__dirname, 'src');
|
|
188
|
+
|
|
189
|
+
// A JSDoc block (`/** … */` — never a plain `/* … */`) immediately preceding a
|
|
190
|
+
// `@customElement('…')` decorator. The inner group forbids `*/` so an earlier,
|
|
191
|
+
// unrelated JSDoc block cannot be stretched across intervening code to reach
|
|
192
|
+
// the decorator.
|
|
193
|
+
const CUSTOM_ELEMENT_WITH_DOC =
|
|
194
|
+
/\/\*\*((?:(?!\*\/)[\s\S])*)\*\/\s*@customElement\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
195
|
+
// Anchored to the start of a line so a `@customElement(…)` written inside a
|
|
196
|
+
// JSDoc code fence (where the line starts with ` * `) is not mistaken for a
|
|
197
|
+
// real decorator.
|
|
198
|
+
const CUSTOM_ELEMENT_ANY =
|
|
199
|
+
/(?:^|\r?\n)[ \t]*@customElement\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
200
|
+
|
|
201
|
+
const lifecycleTagPatterns = new Map(
|
|
202
|
+
LIFECYCLE_JSDOC_TAGS.map((tag) => [
|
|
203
|
+
tag,
|
|
204
|
+
new RegExp(`^[ \\t]*\\*?[ \\t]*@${tag}\\b`, 'm'),
|
|
205
|
+
])
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Every `@customElement` in a source text, with the lifecycle tags declared in
|
|
209
|
+
// its class JSDoc. Shared by both rules so they can never disagree.
|
|
210
|
+
function extractComponents(source) {
|
|
211
|
+
const components = [];
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
let match;
|
|
214
|
+
|
|
215
|
+
CUSTOM_ELEMENT_WITH_DOC.lastIndex = 0;
|
|
216
|
+
while ((match = CUSTOM_ELEMENT_WITH_DOC.exec(source)) !== null) {
|
|
217
|
+
const doc = match[1];
|
|
218
|
+
const decoratorOffset = match[0].lastIndexOf('@customElement');
|
|
219
|
+
components.push({
|
|
220
|
+
tag: match[2],
|
|
221
|
+
tags: LIFECYCLE_JSDOC_TAGS.filter((tag) =>
|
|
222
|
+
lifecycleTagPatterns.get(tag).test(doc)
|
|
223
|
+
),
|
|
224
|
+
index: match.index + decoratorOffset,
|
|
225
|
+
length: match[0].length - decoratorOffset,
|
|
226
|
+
});
|
|
227
|
+
seen.add(match[2]);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
CUSTOM_ELEMENT_ANY.lastIndex = 0;
|
|
231
|
+
while ((match = CUSTOM_ELEMENT_ANY.exec(source)) !== null) {
|
|
232
|
+
if (seen.has(match[1])) continue;
|
|
233
|
+
seen.add(match[1]);
|
|
234
|
+
const decoratorOffset = match[0].indexOf('@customElement');
|
|
235
|
+
components.push({
|
|
236
|
+
tag: match[1],
|
|
237
|
+
tags: [],
|
|
238
|
+
index: match.index + decoratorOffset,
|
|
239
|
+
length: match[0].length - decoratorOffset,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return components;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function collectComponentSources(dir, out) {
|
|
247
|
+
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
|
|
248
|
+
const full = path.join(dir, entry.name);
|
|
249
|
+
if (entry.isDirectory()) {
|
|
250
|
+
// Icons are generated and carry no lifecycle tags by design.
|
|
251
|
+
if (entry.name === 'icons' && dir === SRC_DIR) continue;
|
|
252
|
+
collectComponentSources(full, out);
|
|
253
|
+
} else if (
|
|
254
|
+
entry.name.endsWith('.ts') &&
|
|
255
|
+
!entry.name.endsWith('.stories.ts')
|
|
256
|
+
) {
|
|
257
|
+
out.push(full);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ESLint has no cross-file API, so the tag → lifecycle map is built by reading
|
|
264
|
+
// the sources directly. The result is cached: rebuilt at most once every two
|
|
265
|
+
// seconds so a long-lived editor process still picks up JSDoc edits, while a
|
|
266
|
+
// single CLI run over hundreds of story files pays for it only a few times.
|
|
267
|
+
const COMPONENT_INDEX_TTL_MS = 2000;
|
|
268
|
+
let componentIndex = null;
|
|
269
|
+
let componentIndexBuiltAt = 0;
|
|
270
|
+
let componentIndexPinned = false;
|
|
271
|
+
|
|
272
|
+
function getComponentIndex() {
|
|
273
|
+
if (componentIndexPinned) return componentIndex;
|
|
274
|
+
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
if (componentIndex && now - componentIndexBuiltAt < COMPONENT_INDEX_TTL_MS) {
|
|
277
|
+
return componentIndex;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const index = new Map();
|
|
281
|
+
let files;
|
|
282
|
+
try {
|
|
283
|
+
files = collectComponentSources(SRC_DIR, []);
|
|
284
|
+
} catch {
|
|
285
|
+
files = [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
let source;
|
|
290
|
+
try {
|
|
291
|
+
source = fs.readFileSync(file, 'utf8');
|
|
292
|
+
} catch {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (!source.includes('@customElement')) continue;
|
|
296
|
+
for (const component of extractComponents(source)) {
|
|
297
|
+
index.set(component.tag, {file, tags: component.tags});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
componentIndex = index;
|
|
302
|
+
componentIndexBuiltAt = now;
|
|
303
|
+
return index;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function unwrapTypeExpression(node) {
|
|
307
|
+
let current = node;
|
|
308
|
+
while (
|
|
309
|
+
current &&
|
|
310
|
+
(current.type === 'TSSatisfiesExpression' || current.type === 'TSAsExpression')
|
|
311
|
+
) {
|
|
312
|
+
current = current.expression;
|
|
313
|
+
}
|
|
314
|
+
return current;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function findObjectProperty(objectExpression, name) {
|
|
318
|
+
return objectExpression.properties.find(
|
|
319
|
+
(property) =>
|
|
320
|
+
property.type === 'Property' &&
|
|
321
|
+
!property.computed &&
|
|
322
|
+
property.key.type === 'Identifier' &&
|
|
323
|
+
property.key.name === name
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function quoteList(names) {
|
|
328
|
+
return names.map((name) => `'${name}'`).join(', ');
|
|
329
|
+
}
|
|
330
|
+
|
|
162
331
|
// Custom plugin for local OpenBridge lint rules
|
|
163
332
|
const openbridgePlugin = {
|
|
164
333
|
rules: {
|
|
@@ -268,6 +437,166 @@ const openbridgePlugin = {
|
|
|
268
437
|
};
|
|
269
438
|
},
|
|
270
439
|
},
|
|
440
|
+
'component-lifecycle-tag': {
|
|
441
|
+
meta: {
|
|
442
|
+
type: 'suggestion',
|
|
443
|
+
docs: {
|
|
444
|
+
description:
|
|
445
|
+
'Require exactly one lifecycle tag (@stable, @beta, @experimental, @deprecated) in the class JSDoc of every @customElement',
|
|
446
|
+
},
|
|
447
|
+
schema: [],
|
|
448
|
+
},
|
|
449
|
+
create(context) {
|
|
450
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
'Program:exit'() {
|
|
454
|
+
const text = sourceCode.getText();
|
|
455
|
+
if (!text.includes('@customElement')) return;
|
|
456
|
+
|
|
457
|
+
for (const component of extractComponents(text)) {
|
|
458
|
+
if (component.tags.length === 1) continue;
|
|
459
|
+
|
|
460
|
+
context.report({
|
|
461
|
+
loc: {
|
|
462
|
+
start: sourceCode.getLocFromIndex(component.index),
|
|
463
|
+
end: sourceCode.getLocFromIndex(
|
|
464
|
+
component.index + component.length
|
|
465
|
+
),
|
|
466
|
+
},
|
|
467
|
+
message:
|
|
468
|
+
component.tags.length === 0
|
|
469
|
+
? `<${component.tag}> has no lifecycle tag in its class JSDoc. Add exactly one of @stable, @beta, @experimental or @deprecated (see AGENTS.md § 3).`
|
|
470
|
+
: `<${component.tag}> declares more than one lifecycle tag (${component.tags
|
|
471
|
+
.map((tag) => `@${tag}`)
|
|
472
|
+
.join(', ')}). Exactly one is allowed.`,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
'story-lifecycle-tags': {
|
|
480
|
+
meta: {
|
|
481
|
+
type: 'problem',
|
|
482
|
+
docs: {
|
|
483
|
+
description:
|
|
484
|
+
"Keep a story's meta.tags lifecycle entry in sync with the class JSDoc lifecycle tag of its meta.component",
|
|
485
|
+
},
|
|
486
|
+
fixable: 'code',
|
|
487
|
+
schema: [],
|
|
488
|
+
},
|
|
489
|
+
create(context) {
|
|
490
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
VariableDeclarator(node) {
|
|
494
|
+
if (node.id.type !== 'Identifier' || node.id.name !== 'meta') return;
|
|
495
|
+
|
|
496
|
+
const meta = unwrapTypeExpression(node.init);
|
|
497
|
+
if (!meta || meta.type !== 'ObjectExpression') return;
|
|
498
|
+
|
|
499
|
+
const componentProperty = findObjectProperty(meta, 'component');
|
|
500
|
+
if (
|
|
501
|
+
!componentProperty ||
|
|
502
|
+
componentProperty.value.type !== 'Literal' ||
|
|
503
|
+
typeof componentProperty.value.value !== 'string'
|
|
504
|
+
)
|
|
505
|
+
return;
|
|
506
|
+
|
|
507
|
+
const componentTag = componentProperty.value.value;
|
|
508
|
+
const component = getComponentIndex().get(componentTag);
|
|
509
|
+
|
|
510
|
+
// A component with no tag, or more than one, is reported against
|
|
511
|
+
// its own source file by `component-lifecycle-tag`; there is no
|
|
512
|
+
// single correct story tag to fix towards here.
|
|
513
|
+
if (!component || component.tags.length !== 1) return;
|
|
514
|
+
|
|
515
|
+
const lifecycle = component.tags[0];
|
|
516
|
+
const expected = LIFECYCLE_STORY_TAG[lifecycle];
|
|
517
|
+
const desired = expected ? [expected] : [];
|
|
518
|
+
const tagsProperty = findObjectProperty(meta, 'tags');
|
|
519
|
+
|
|
520
|
+
if (!tagsProperty) {
|
|
521
|
+
if (!expected) return;
|
|
522
|
+
|
|
523
|
+
context.report({
|
|
524
|
+
node: componentProperty.value,
|
|
525
|
+
message: `<${componentTag}> is @${lifecycle}, so meta.tags must contain '${expected}', but meta has no tags.`,
|
|
526
|
+
fix(fixer) {
|
|
527
|
+
const titleProperty = findObjectProperty(meta, 'title');
|
|
528
|
+
if (titleProperty) {
|
|
529
|
+
const next = sourceCode.getTokenAfter(titleProperty);
|
|
530
|
+
return next && next.value === ','
|
|
531
|
+
? fixer.insertTextAfter(next, `\n tags: ['${expected}'],`)
|
|
532
|
+
: fixer.insertTextAfter(
|
|
533
|
+
titleProperty,
|
|
534
|
+
`,\n tags: ['${expected}']`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
const first = meta.properties[0];
|
|
538
|
+
return first
|
|
539
|
+
? fixer.insertTextBefore(
|
|
540
|
+
first,
|
|
541
|
+
`tags: ['${expected}'],\n `
|
|
542
|
+
)
|
|
543
|
+
: null;
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (tagsProperty.value.type !== 'ArrayExpression') return;
|
|
550
|
+
|
|
551
|
+
const elements = tagsProperty.value.elements.filter(Boolean);
|
|
552
|
+
const owned = elements.filter(
|
|
553
|
+
(element) =>
|
|
554
|
+
element.type === 'Literal' &&
|
|
555
|
+
typeof element.value === 'string' &&
|
|
556
|
+
OWNED_STORY_TAGS.has(element.value)
|
|
557
|
+
);
|
|
558
|
+
const found = owned.map((element) => element.value);
|
|
559
|
+
|
|
560
|
+
if (
|
|
561
|
+
found.length === desired.length &&
|
|
562
|
+
found.every((name, i) => name === desired[i])
|
|
563
|
+
)
|
|
564
|
+
return;
|
|
565
|
+
|
|
566
|
+
const kept = elements.filter((element) => !owned.includes(element));
|
|
567
|
+
const nextElements = [
|
|
568
|
+
...kept.map((element) => sourceCode.getText(element)),
|
|
569
|
+
...desired.map((name) => `'${name}'`),
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
context.report({
|
|
573
|
+
node: tagsProperty.value,
|
|
574
|
+
message: expected
|
|
575
|
+
? `<${componentTag}> is @${lifecycle}, so meta.tags must carry ${quoteList(desired)}, but it carries ${found.length ? quoteList(found) : 'no lifecycle tag'}.`
|
|
576
|
+
: `<${componentTag}> is @${lifecycle}, so meta.tags must carry no lifecycle tag, but it carries ${quoteList(found)}.`,
|
|
577
|
+
fix(fixer) {
|
|
578
|
+
if (nextElements.length > 0) {
|
|
579
|
+
return fixer.replaceText(
|
|
580
|
+
tagsProperty.value,
|
|
581
|
+
`[${nextElements.join(', ')}]`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
const previous = sourceCode.getTokenBefore(tagsProperty);
|
|
585
|
+
const next = sourceCode.getTokenAfter(tagsProperty);
|
|
586
|
+
const start = previous
|
|
587
|
+
? previous.range[1]
|
|
588
|
+
: tagsProperty.range[0];
|
|
589
|
+
const end =
|
|
590
|
+
next && next.value === ','
|
|
591
|
+
? next.range[1]
|
|
592
|
+
: tagsProperty.range[1];
|
|
593
|
+
return fixer.removeRange([start, end]);
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
},
|
|
599
|
+
},
|
|
271
600
|
'prefer-boolean-property-default-false': {
|
|
272
601
|
meta: {
|
|
273
602
|
type: 'problem',
|
|
@@ -539,7 +868,9 @@ export default [
|
|
|
539
868
|
'openbridge/prefer-enum-over-string-literal-union': 'error',
|
|
540
869
|
'openbridge/prefer-boolean-property-default-false': 'error',
|
|
541
870
|
'openbridge/prefer-array-property-type-and-item-interface': 'error',
|
|
871
|
+
'openbridge/component-lifecycle-tag': 'warn',
|
|
542
872
|
'openbridge/storybook-title-case': 'off',
|
|
873
|
+
'openbridge/story-lifecycle-tags': 'off',
|
|
543
874
|
// Disabled because eslint-plugin-file-extension-in-import-ts is not yet
|
|
544
875
|
// compatible with ESLint v10 (it still uses deprecated context methods).
|
|
545
876
|
'file-extension-in-import-ts/file-extension-in-import-ts': 'off',
|
|
@@ -574,11 +905,35 @@ export default [
|
|
|
574
905
|
'openbridge/prefer-enum-over-string-literal-union': 'off',
|
|
575
906
|
},
|
|
576
907
|
},
|
|
908
|
+
{
|
|
909
|
+
// Icon components carry no lifecycle tags by design.
|
|
910
|
+
files: ['src/icons/**/*.ts', 'src/manual-icon/**/*.ts'],
|
|
911
|
+
|
|
912
|
+
rules: {
|
|
913
|
+
'openbridge/component-lifecycle-tag': 'off',
|
|
914
|
+
},
|
|
915
|
+
},
|
|
577
916
|
{
|
|
578
917
|
files: ['**/*.stories.ts'],
|
|
579
918
|
|
|
580
919
|
rules: {
|
|
581
920
|
'openbridge/storybook-title-case': 'error',
|
|
921
|
+
'openbridge/story-lifecycle-tags': 'error',
|
|
922
|
+
'openbridge/component-lifecycle-tag': 'off',
|
|
582
923
|
},
|
|
583
924
|
},
|
|
584
925
|
];
|
|
926
|
+
|
|
927
|
+
// ESLint reads only the default export. These named exports exist so the
|
|
928
|
+
// lifecycle-tag rules can be unit-tested (script/eslint-rules.test.ts) without
|
|
929
|
+
// depending on the real state of src/.
|
|
930
|
+
export const __testables = {
|
|
931
|
+
openbridgePlugin,
|
|
932
|
+
extractComponents,
|
|
933
|
+
pinComponentIndex(entries) {
|
|
934
|
+
componentIndex = new Map(
|
|
935
|
+
Object.entries(entries).map(([tag, tags]) => [tag, {file: tag, tags}])
|
|
936
|
+
);
|
|
937
|
+
componentIndexPinned = true;
|
|
938
|
+
},
|
|
939
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oicl/openbridge-webcomponents-full-bundle",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.119",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -147,6 +147,8 @@
|
|
|
147
147
|
"lint:icons": "tsx ./script/check-icon-hex-leaks.ts",
|
|
148
148
|
"lint:eslint": "eslint 'src/**/*.ts'",
|
|
149
149
|
"lint:lit-analyzer": "lit-analyzer --strict --maxWarnings 0",
|
|
150
|
+
"lint:fix:stories": "eslint 'src/**/*.stories.ts' --fix",
|
|
151
|
+
"test:rules": "vitest run --project rules",
|
|
150
152
|
"fix-imports": "node fix-imports.mjs",
|
|
151
153
|
"fix-imports:check": "node fix-imports.mjs --dry-run",
|
|
152
154
|
"test:browser": "vitest --config=vitest.browser.config.ts",
|