@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.117 → 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 +37 -33
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +30 -24
- package/dist/ar/poi/poi-base.d.ts +6 -0
- package/dist/ar/poi/poi-base.d.ts.map +1 -1
- package/dist/ar/poi/poi-base.js.map +1 -1
- package/dist/ar/poi-button/poi-button.d.ts +2 -2
- package/dist/ar/poi-button/poi-button.js.map +1 -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/accordion-card/accordion-card.d.ts.map +1 -1
- package/dist/components/accordion-card/accordion-card.js.map +1 -1
- package/dist/components/advice-message-item/advice-message-item.d.ts +4 -2
- package/dist/components/advice-message-item/advice-message-item.d.ts.map +1 -1
- package/dist/components/advice-message-item/advice-message-item.js +4 -2
- package/dist/components/advice-message-item/advice-message-item.js.map +1 -1
- package/dist/components/app-menu/app-menu.d.ts +1 -1
- package/dist/components/app-menu/app-menu.js +1 -1
- package/dist/components/app-menu/app-menu.js.map +1 -1
- package/dist/components/brilliance-menu/brilliance-menu.d.ts +8 -4
- package/dist/components/brilliance-menu/brilliance-menu.d.ts.map +1 -1
- package/dist/components/brilliance-menu/brilliance-menu.js +8 -4
- package/dist/components/brilliance-menu/brilliance-menu.js.map +1 -1
- package/dist/components/check-button/check-button.js.map +1 -1
- package/dist/components/checkbox/checkbox.d.ts +2 -2
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/command-menu/command-menu.d.ts +1 -1
- package/dist/components/command-menu/command-menu.js +1 -1
- package/dist/components/command-menu/command-menu.js.map +1 -1
- package/dist/components/dropdown-button/dropdown-button.d.ts +2 -2
- package/dist/components/dropdown-button/dropdown-button.js +2 -2
- package/dist/components/dropdown-button/dropdown-button.js.map +1 -1
- package/dist/components/menu-button/menu-button.d.ts.map +1 -1
- package/dist/components/menu-button/menu-button.js.map +1 -1
- package/dist/components/navigation-item/navigation-item.d.ts +1 -5
- package/dist/components/navigation-item/navigation-item.d.ts.map +1 -1
- package/dist/components/navigation-item/navigation-item.js +0 -8
- package/dist/components/navigation-item/navigation-item.js.map +1 -1
- package/dist/components/navigation-item-group/navigation-item-group.d.ts +2 -1
- package/dist/components/navigation-item-group/navigation-item-group.d.ts.map +1 -1
- package/dist/components/navigation-item-group/navigation-item-group.js +2 -1
- package/dist/components/navigation-item-group/navigation-item-group.js.map +1 -1
- package/dist/components/notification-button/notification-button.d.ts +1 -1
- package/dist/components/notification-button/notification-button.js +1 -1
- package/dist/components/notification-button/notification-button.js.map +1 -1
- package/dist/components/notification-message-item/notification-message-item.d.ts +4 -2
- package/dist/components/notification-message-item/notification-message-item.d.ts.map +1 -1
- package/dist/components/notification-message-item/notification-message-item.js +4 -2
- package/dist/components/notification-message-item/notification-message-item.js.map +1 -1
- package/dist/components/pivot-item/pivot-item.d.ts +1 -1
- package/dist/components/pivot-item/pivot-item.js +1 -1
- package/dist/components/pivot-item/pivot-item.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/dist/components/slide-button/slide-button.d.ts.map +1 -1
- package/dist/components/slide-button/slide-button.js.map +1 -1
- package/dist/components/slider/slider.d.ts +2 -2
- package/dist/components/slider/slider.js +2 -2
- package/dist/components/slider/slider.js.map +1 -1
- package/dist/components/slider-double/slider-double.d.ts +2 -2
- package/dist/components/slider-double/slider-double.js +2 -2
- package/dist/components/slider-double/slider-double.js.map +1 -1
- package/dist/components/stepper-box/stepper-box.d.ts +4 -2
- package/dist/components/stepper-box/stepper-box.d.ts.map +1 -1
- package/dist/components/stepper-box/stepper-box.js +4 -2
- package/dist/components/stepper-box/stepper-box.js.map +1 -1
- package/dist/components/system-button/system-button.d.ts +1 -1
- package/dist/components/system-button/system-button.js +1 -1
- package/dist/components/system-button/system-button.js.map +1 -1
- package/dist/components/tabbed-card/tabbed-card.d.ts.map +1 -1
- package/dist/components/tabbed-card/tabbed-card.js.map +1 -1
- package/dist/components/toggle-button-option/toggle-button-option.d.ts +2 -1
- package/dist/components/toggle-button-option/toggle-button-option.d.ts.map +1 -1
- package/dist/components/toggle-button-option/toggle-button-option.js +2 -1
- package/dist/components/toggle-button-option/toggle-button-option.js.map +1 -1
- package/dist/components/toggle-button-vertical-group/toggle-button-vertical-group.js.map +1 -1
- package/dist/components/toggle-switch/toggle-switch.d.ts +1 -1
- package/dist/components/toggle-switch/toggle-switch.js +1 -1
- package/dist/components/toggle-switch/toggle-switch.js.map +1 -1
- package/dist/components/top-bar/top-bar.d.ts.map +1 -1
- package/dist/components/top-bar/top-bar.js.map +1 -1
- package/dist/components/topbar-message-item/topbar-message-item.js.map +1 -1
- package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.d.ts +1 -1
- package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.js +1 -1
- package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.js.map +1 -1
- package/eslint.config.mjs +355 -0
- package/package.json +3 -1
- package/script/check-slot-event-docs.ts +36 -3
- 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-base.ts +6 -0
- 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-button/poi-button.ts +2 -2
- 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/accordion-card/accordion-card.ts +2 -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/advice-message-item/advice-message-item.ts +4 -2
- package/src/components/alert-frame/alert-frame.stories.ts +1 -1
- package/src/components/app-menu/app-menu.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/brilliance-menu/brilliance-menu.ts +8 -4
- package/src/components/calendar/calendar.stories.ts +1 -1
- package/src/components/check-button/check-button.ts +1 -1
- package/src/components/checkbox/checkbox.ts +2 -2
- 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/command-menu/command-menu.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/dropdown-button/dropdown-button.ts +2 -2
- 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/menu-button/menu-button.ts +8 -4
- package/src/components/message-menu-item/message-menu-item.stories.ts +1 -1
- package/src/components/navigation-item/navigation-item.ts +1 -10
- package/src/components/navigation-item-group/navigation-item-group.ts +2 -1
- package/src/components/notification-badge-button/notification-badge-button.stories.ts +1 -1
- package/src/components/notification-button/notification-button.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/notification-message-item/notification-message-item.ts +4 -2
- package/src/components/pivot-item/pivot-item.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/slide-button/slide-button.ts +2 -1
- package/src/components/slider/slider.ts +2 -2
- package/src/components/slider-double/slider-double.ts +2 -2
- 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/stepper-box/stepper-box.ts +4 -2
- package/src/components/system-button/system-button.ts +5 -5
- package/src/components/tabbed-card/tabbed-card.ts +2 -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-option/toggle-button-option.ts +2 -1
- package/src/components/toggle-button-three-state/toggle-button-three-state.stories.ts +1 -1
- package/src/components/toggle-button-vertical-group/toggle-button-vertical-group.ts +2 -2
- package/src/components/toggle-switch/toggle-switch.ts +1 -1
- package/src/components/toggletip/toggletip.stories.ts +1 -1
- package/src/components/top-bar/top-bar.ts +8 -4
- package/src/components/topbar-message-item/topbar-message-item.ts +2 -2
- 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-dropdown-button/integration-dropdown-button.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/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",
|
|
@@ -142,6 +142,20 @@ function findDispatchedEvents(code: string): Set<string> {
|
|
|
142
142
|
return events;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Unqualified `dispatchEvent(...)` calls. Inside a class method these resolve to
|
|
147
|
+
* `globalThis.dispatchEvent`, so the event lands on `window` and no consumer of
|
|
148
|
+
* the element ever receives it — almost always a missing `this.`.
|
|
149
|
+
*/
|
|
150
|
+
function findBareDispatchCalls(code: string): Set<string> {
|
|
151
|
+
const events = new Set<string>();
|
|
152
|
+
const re =
|
|
153
|
+
/(^|[^.\w$])dispatchEvent\(\s*new\s+(?:Custom)?Event\s*(?:<(?:[^<>]|<[^<>]*>)*>)?\s*\(\s*['"]([^'"]+)['"]/g;
|
|
154
|
+
let m: RegExpExecArray | null;
|
|
155
|
+
while ((m = re.exec(code)) !== null) events.add(m[2]);
|
|
156
|
+
return events;
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
/** Documented slot names from `@slot name` tags (`-` denotes the default slot). */
|
|
146
160
|
function findDocumentedSlots(source: string): {
|
|
147
161
|
names: Set<string>;
|
|
@@ -175,6 +189,16 @@ interface TagDoc {
|
|
|
175
189
|
hasDescription: boolean;
|
|
176
190
|
}
|
|
177
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Only the unindented docblocks — the class-level ones whose tags `cem analyze`
|
|
194
|
+
* and `lit labs gen` actually read. Tags in indented (method-level) blocks reach
|
|
195
|
+
* neither, so demanding a description there is noise: the description belongs on
|
|
196
|
+
* the class tag, and the method block's own summary line describes the method.
|
|
197
|
+
*/
|
|
198
|
+
function topLevelDocblocks(source: string): string {
|
|
199
|
+
return (source.match(/^\/\*\*[\s\S]*?\*\//gm) ?? []).join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
178
202
|
/** A plausible slot/event name (identifier or kebab-case), or `-` for default. */
|
|
179
203
|
function isValidTagName(name: string): boolean {
|
|
180
204
|
return name === '-' || /^[A-Za-z][\w-]*$/.test(name);
|
|
@@ -280,6 +304,14 @@ async function run(): Promise<void> {
|
|
|
280
304
|
}
|
|
281
305
|
}
|
|
282
306
|
|
|
307
|
+
// Bare `dispatchEvent(...)` — dispatches on `window`, not the element.
|
|
308
|
+
for (const name of findBareDispatchCalls(code)) {
|
|
309
|
+
errors.push({
|
|
310
|
+
file: rel,
|
|
311
|
+
message: `calls dispatchEvent(new …Event('${name}')) without \`this.\` — this dispatches on window, so the event never reaches consumers of the element`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
283
315
|
// Phantom @slot — only for direct LitElement subclasses without dynamic
|
|
284
316
|
// slot names, so inherited/dynamic slots never produce false positives.
|
|
285
317
|
if (extendsLitElementDirectly(source) && !dynamicSlot) {
|
|
@@ -304,14 +336,15 @@ async function run(): Promise<void> {
|
|
|
304
336
|
}
|
|
305
337
|
|
|
306
338
|
// Empty description (warning) — a @slot/@fires tag with only a name produces
|
|
307
|
-
// a blank cell in the manifest / Storybook controls.
|
|
308
|
-
|
|
339
|
+
// a blank cell in the manifest / Storybook controls. Class-level tags only.
|
|
340
|
+
const classDocs = topLevelDocblocks(source);
|
|
341
|
+
for (const tag of parseSlotTags(classDocs)) {
|
|
309
342
|
if (!tag.hasDescription) {
|
|
310
343
|
const label = tag.name === '-' ? 'default slot' : `@slot ${tag.name}`;
|
|
311
344
|
warnings.push({file: rel, message: `${label} has no description`});
|
|
312
345
|
}
|
|
313
346
|
}
|
|
314
|
-
for (const tag of parseEventTags(
|
|
347
|
+
for (const tag of parseEventTags(classDocs)) {
|
|
315
348
|
if (!tag.hasDescription) {
|
|
316
349
|
warnings.push({
|
|
317
350
|
file: rel,
|