@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.66 → 2.0.0-next.68
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/bundle/openbridge-webcomponents.bundle.js +2738 -2408
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +347 -30
- package/dist/automation/automation-button/automation-button.css.js +9 -9
- package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.d.ts +1 -0
- package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.d.ts.map +1 -1
- package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.js +9 -28
- package/dist/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.js.map +1 -1
- package/dist/components/accordion-card/accordion-card.css.js +1 -1
- package/dist/components/dropdown-button/dropdown-button.d.ts +11 -0
- package/dist/components/dropdown-button/dropdown-button.d.ts.map +1 -1
- package/dist/components/dropdown-button/dropdown-button.js +24 -2
- package/dist/components/dropdown-button/dropdown-button.js.map +1 -1
- package/dist/components/keyboard-numeric/keyboard-numeric.d.ts +1 -2
- package/dist/components/keyboard-numeric/keyboard-numeric.d.ts.map +1 -1
- package/dist/components/keyboard-numeric/keyboard-numeric.js +10 -15
- package/dist/components/keyboard-numeric/keyboard-numeric.js.map +1 -1
- package/dist/components/number-input-field/number-input-field.d.ts +9 -0
- package/dist/components/number-input-field/number-input-field.d.ts.map +1 -1
- package/dist/components/number-input-field/number-input-field.js +30 -1
- package/dist/components/number-input-field/number-input-field.js.map +1 -1
- package/dist/components/number-input-field/number-input-format.d.ts +9 -0
- package/dist/components/number-input-field/number-input-format.d.ts.map +1 -1
- package/dist/components/number-input-field/number-input-format.js +33 -2
- package/dist/components/number-input-field/number-input-format.js.map +1 -1
- package/dist/components/stepper-box/stepper-box.css.js +0 -6
- package/dist/components/stepper-box/stepper-box.css.js.map +1 -1
- package/dist/components/stepper-box/stepper-box.d.ts +3 -13
- package/dist/components/stepper-box/stepper-box.d.ts.map +1 -1
- package/dist/components/stepper-box/stepper-box.js.map +1 -1
- package/dist/components/toggle-button-group/toggle-button-group.d.ts +14 -0
- package/dist/components/toggle-button-group/toggle-button-group.d.ts.map +1 -1
- package/dist/components/toggle-button-group/toggle-button-group.js +25 -5
- package/dist/components/toggle-button-group/toggle-button-group.js.map +1 -1
- package/dist/integration-systems/integration-fleet-button/integration-fleet-button.css.js +4 -0
- package/dist/integration-systems/integration-fleet-button/integration-fleet-button.css.js.map +1 -1
- package/dist/integration-systems/integration-fleet-button/integration-fleet-button.d.ts +1 -0
- package/dist/integration-systems/integration-fleet-button/integration-fleet-button.d.ts.map +1 -1
- package/dist/integration-systems/integration-fleet-button/integration-fleet-button.js +13 -1
- package/dist/integration-systems/integration-fleet-button/integration-fleet-button.js.map +1 -1
- package/dist/navigation-instruments/graph-mini/graph-mini.css.js +2 -1
- package/dist/navigation-instruments/graph-mini/graph-mini.css.js.map +1 -1
- package/dist/navigation-instruments/graph-mini/graph-mini.d.ts.map +1 -1
- package/dist/navigation-instruments/graph-mini/graph-mini.js +7 -5
- package/dist/navigation-instruments/graph-mini/graph-mini.js.map +1 -1
- package/dist/navigation-instruments/indicator-graph/indicator-graph.css.js +66 -0
- package/dist/navigation-instruments/indicator-graph/indicator-graph.css.js.map +1 -0
- package/dist/navigation-instruments/indicator-graph/indicator-graph.d.ts +54 -0
- package/dist/navigation-instruments/indicator-graph/indicator-graph.d.ts.map +1 -0
- package/dist/navigation-instruments/indicator-graph/indicator-graph.js +203 -0
- package/dist/navigation-instruments/indicator-graph/indicator-graph.js.map +1 -0
- package/docs/GettingStarted.mdx +28 -12
- package/package.json +1 -1
- package/src/automation/automation-button/automation-button.css +9 -9
- package/src/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.ts +9 -28
- package/src/components/accordion-card/accordion-card.css +1 -1
- package/src/components/dropdown-button/dropdown-button.stories.ts +22 -0
- package/src/components/dropdown-button/dropdown-button.ts +33 -4
- package/src/components/keyboard-numeric/keyboard-numeric.ts +12 -18
- package/src/components/number-input-field/number-input-field.spec.ts +134 -0
- package/src/components/number-input-field/number-input-field.ts +47 -0
- package/src/components/number-input-field/number-input-format.spec.ts +62 -0
- package/src/components/number-input-field/number-input-format.ts +59 -2
- package/src/components/stepper-box/stepper-box.css +0 -6
- package/src/components/stepper-box/stepper-box.ts +3 -13
- package/src/components/toggle-button-group/toggle-button-group.ts +36 -5
- package/src/components/toggle-button-group/toggle-button.stories.ts +31 -0
- package/src/integration-systems/integration-fleet-button/integration-fleet-button.css +4 -0
- package/src/integration-systems/integration-fleet-button/integration-fleet-button.stories.ts +9 -0
- package/src/integration-systems/integration-fleet-button/integration-fleet-button.ts +10 -1
- package/src/navigation-instruments/graph-mini/graph-mini.css +2 -1
- package/src/navigation-instruments/graph-mini/graph-mini.stories.ts +11 -3
- package/src/navigation-instruments/graph-mini/graph-mini.ts +7 -5
- package/src/navigation-instruments/indicator-graph/indicator-graph.css +55 -0
- package/src/navigation-instruments/indicator-graph/indicator-graph.stories.ts +116 -0
- package/src/navigation-instruments/indicator-graph/indicator-graph.ts +222 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indicator-graph.js","sources":["../../../src/navigation-instruments/indicator-graph/indicator-graph.ts"],"sourcesContent":["import {LitElement, PropertyValues, html, unsafeCSS} from 'lit';\nimport {property, query, state} from 'lit/decorators.js';\nimport compentStyle from './indicator-graph.css?inline';\nimport uPlot from 'uplot';\nimport {customElement} from '../../decorator.js';\n\nexport enum ObcIndicatorGraphSize {\n small = 'small',\n medium = 'medium',\n large = 'large',\n}\n\nexport enum ObcIndicatorGraphPriority {\n regular = 'regular',\n enhanced = 'enhanced',\n}\n\nexport interface ObcIndicatorGraphLayout {\n size?: ObcIndicatorGraphSize;\n priority?: ObcIndicatorGraphPriority;\n y?: {min?: number; max?: number};\n}\n\n/**\n * @element obc-indicator-graph\n * @description A mini graph component\n *\n * @property {Array} data - The data to display in the graph, first array is the x values, second array is the y values\n */\n@customElement('obc-indicator-graph')\nexport class ObcIndicatorGraph extends LitElement {\n @property({type: Array})\n data: [number[], number[]] = [[], []];\n\n @property({type: Object})\n layout: ObcIndicatorGraphLayout = {};\n\n @query('.chart-container')\n private chart!: HTMLDivElement;\n\n @state()\n private y: number = 24;\n\n @state()\n private zeroLineY: number = 0;\n\n private uplot: uPlot | null = null;\n private resizeObserver: ResizeObserver | null = null;\n\n private getCssColor(name: string) {\n const color = getComputedStyle(this).getPropertyValue(name).trim();\n return color;\n }\n\n override firstUpdated() {\n const opts = {\n width: this.chart.clientWidth,\n height: this.chart.clientHeight,\n scales: {\n x: {time: false, show: false},\n y: {\n auto: true,\n show: false,\n range: this._range.bind(this),\n },\n },\n series: [\n {},\n {\n stroke: this._getStrokeColor(),\n width: this._getStrokeWidth(),\n points: {show: false},\n },\n ],\n axes: [\n {show: false},\n {ticks: {show: false}, show: false, grid: {show: false}},\n ],\n legend: {show: false},\n cursor: {show: false},\n };\n\n this.uplot = new uPlot(opts, this.data, this.chart);\n requestAnimationFrame(() => this.updateY());\n\n this.resizeObserver = new ResizeObserver(() => this.updateSize());\n this.resizeObserver.observe(this.chart);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.uplot?.destroy();\n this.uplot = null;\n }\n\n private updateSize() {\n if (!this.uplot) {\n return;\n }\n this.uplot.setSize({\n width: this.chart.clientWidth,\n height: this.chart.clientHeight,\n });\n this.updateY();\n }\n\n private _range(\n _self: uPlot,\n initMin: number,\n initMax: number,\n _scaleKey: string\n ): [number, number] {\n const {min: minY, max: maxY} = this.layout.y ?? {};\n const range = maxY ?? initMax - (minY ?? initMin);\n let min = minY ?? initMin - range * 0.1;\n if (minY === undefined) {\n min = Math.min(0, min);\n }\n return [min, maxY ?? initMax + range * 0.1] as [number, number];\n }\n\n private get _effectiveSize() {\n return this.layout.size ?? ObcIndicatorGraphSize.medium;\n }\n\n private get _effectivePriority() {\n return this.layout.priority ?? ObcIndicatorGraphPriority.regular;\n }\n\n private _getStrokeWidth() {\n switch (this._effectiveSize) {\n case ObcIndicatorGraphSize.small:\n return 1;\n case ObcIndicatorGraphSize.medium:\n return 1.5;\n case ObcIndicatorGraphSize.large:\n return 2;\n }\n }\n\n private _getStrokeColor() {\n switch (this._effectivePriority) {\n case ObcIndicatorGraphPriority.regular:\n return this.getCssColor('--element-neutral-color');\n case ObcIndicatorGraphPriority.enhanced:\n return this.getCssColor('--element-neutral-enhanced-color');\n }\n }\n\n updatePalette() {\n if (!this.uplot) {\n return;\n }\n const stroke = this._getStrokeColor();\n this.uplot.setSeries(1, {\n // @ts-expect-error - stroke is not a property of the Series interface\n stroke: stroke,\n width: this._getStrokeWidth(),\n points: {show: false},\n });\n }\n\n private updateY() {\n if (!this.uplot) {\n return;\n }\n const lastY = this.data[1][this.data[1].length - 1];\n // @ts-expect-error - valToPct is not a property of the Scale interface\n const yRatio = this.uplot.scales.y.valToPct(lastY);\n this.y = yRatio * this.chart.clientHeight;\n this.updateZeroLine();\n }\n\n private updateZeroLine() {\n if (!this.uplot) {\n return;\n }\n // @ts-expect-error - valToPct is not a property of the Scale interface\n const yRatio = this.uplot.scales.y.valToPct(0);\n this.zeroLineY = yRatio * this.chart.clientHeight;\n }\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('data') || changedProperties.has('layout')) {\n this.updatePalette();\n this.uplot?.setData(this.data);\n requestAnimationFrame(() => this.updateY());\n }\n }\n\n override render() {\n return html`\n <div\n class=\"chart-container ${this._effectivePriority} ${this\n ._effectiveSize}\"\n >\n <div\n id=\"zero-line\"\n style=\"transform: translateY(${-this.zeroLineY}px);\n display: ${this.zeroLineY > -0.5 ? 'block' : 'none'};\n \"\n ></div>\n <div\n id=\"dot\"\n style=\"transform: translateY(${-this.y}px);\n display: ${this.y > -0.5 ? 'block' : 'none'};\n \"\n ></div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-indicator-graph': ObcIndicatorGraph;\n }\n}\n"],"names":["ObcIndicatorGraphSize","ObcIndicatorGraphPriority"],"mappings":"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"}
|
package/docs/GettingStarted.mdx
CHANGED
|
@@ -2,23 +2,19 @@ import {Meta} from '@storybook/addon-docs/blocks';
|
|
|
2
2
|
|
|
3
3
|
<Meta title="Introduction/Getting Started" />
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# 🚀 Project Status
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**v1.0.0 is now released!** It has been an incredibly productive first year, with more than 200 components already
|
|
8
|
+
implemented. The code was officially opened by **Prince Sverre Magnus of Norway** and is now publicly available,
|
|
9
|
+
marking our first stable milestone.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
# Getting Started
|
|
10
12
|
|
|
11
13
|
## Installation
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
The package is publicly available on [npm](https://www.npmjs.com/package/@oicl/openbridge-webcomponents) — no authentication required.
|
|
14
16
|
|
|
15
|
-
1. **
|
|
16
|
-
2. **Login to the GitHub package registry:**
|
|
17
|
-
```bash
|
|
18
|
-
npm login --registry https://npm.pkg.github.com/ --scope=@oicl
|
|
19
|
-
```
|
|
20
|
-
Use your GitHub username and the generated token as password.
|
|
21
|
-
3. **Install the package:**
|
|
17
|
+
1. **Install the package:**
|
|
22
18
|
```bash
|
|
23
19
|
npm install @oicl/openbridge-webcomponents
|
|
24
20
|
```
|
|
@@ -26,6 +22,11 @@ To use the components in your project, you need access to the private package re
|
|
|
26
22
|
```bash
|
|
27
23
|
npm install @oicl/openbridge-webcomponents-vue
|
|
28
24
|
```
|
|
25
|
+
React, Angular, and Svelte wrappers are published as `@oicl/openbridge-webcomponents-react`, `-ng`, and `-svelte`.
|
|
26
|
+
|
|
27
|
+
2. **Choose a release channel:**
|
|
28
|
+
- The `latest` tag tracks the stable, production-ready releases (updated from the `stable` branch).
|
|
29
|
+
- The `next` tag tracks the active development releases (from the `develop` branch) and may include breaking changes. Install it with `npm install @oicl/openbridge-webcomponents@next`.
|
|
29
30
|
|
|
30
31
|
## Usage
|
|
31
32
|
|
|
@@ -71,4 +72,19 @@ To use the components in your project, you need access to the private package re
|
|
|
71
72
|
<ObcTopBar></ObcTopBar>
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
## Licensing Model
|
|
76
|
+
|
|
77
|
+
The project will **always remain open source**, but with the 1.0.0 release it has transitioned to a new licensing system. Each release — based on both the `develop` and `stable` branches — follows a "delayed" permissive license cycle:
|
|
78
|
+
|
|
79
|
+
1. **Initial Release:** Each new version is licensed under **AGPL** for the first 6 months.
|
|
80
|
+
2. **Transition:** After 6 months, the license for that specific version automatically switches to **Apache 2.0**.
|
|
81
|
+
3. **Supporter Access:** Project donors and sponsors receive **immediate access** to the code under the **Apache 2.0** license.
|
|
82
|
+
|
|
83
|
+
> **Example:**
|
|
84
|
+
> If version 1.2.2 is released on **August 14, 2026**:
|
|
85
|
+
>
|
|
86
|
+
> - **open-source projects and internal users** can use it immediately under AGPL.
|
|
87
|
+
> - **donors** can use it immediately under **Apache 2.0**.
|
|
88
|
+
> - From **February 14, 2027**, version 1.2.2 becomes available to everyone under the **Apache 2.0** license (e.g., for commercial use without AGPL restrictions).
|
|
89
|
+
|
|
90
|
+
For more details visit the [OpenBridge Design System website](https://www.openbridge.no/).
|
package/package.json
CHANGED
|
@@ -219,18 +219,18 @@
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
.state-closed .icon-holder {
|
|
222
|
-
border-color: var(--automation-
|
|
223
|
-
background: var(--automation-
|
|
224
|
-
|
|
225
|
-
.static & {
|
|
226
|
-
border-color: var(--automation-pipe-secondary-color);
|
|
227
|
-
background: var(--automation-pipe-primary-inverted-color);
|
|
228
|
-
}
|
|
222
|
+
border-color: var(--automation-button-off-border-color);
|
|
223
|
+
background: var(--automation-button-off-background-color);
|
|
229
224
|
}
|
|
230
225
|
|
|
231
226
|
.state-open .icon-holder {
|
|
232
|
-
border-color: var(--
|
|
233
|
-
background: var(--
|
|
227
|
+
border-color: var(--on-border-color);
|
|
228
|
+
background: var(--on-background-color);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.static .icon-holder {
|
|
232
|
+
border-color: var(--automation-button-static-broder-color);
|
|
233
|
+
background: var(--automation-button-static-background-color);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
.alert-frame {
|
|
@@ -2,6 +2,7 @@ import {LitElement, html, svg, unsafeCSS} from 'lit';
|
|
|
2
2
|
import {property} from 'lit/decorators.js';
|
|
3
3
|
import compentStyle from './valve-analog-two-way-icon.css?inline';
|
|
4
4
|
import {customElement} from '../../decorator.js';
|
|
5
|
+
import '../../icons/icon-twoway-analog-closed.js';
|
|
5
6
|
|
|
6
7
|
@customElement('obc-valve-analog-two-way-icon')
|
|
7
8
|
export class ObcValveAnalogTwoWayIcon extends LitElement {
|
|
@@ -13,36 +14,16 @@ export class ObcValveAnalogTwoWayIcon extends LitElement {
|
|
|
13
14
|
const transform = this.vertical ? 'transform: rotate(90deg);' : '';
|
|
14
15
|
if (this.closed) {
|
|
15
16
|
return html` <div class="wrapper" style="${transform}">
|
|
16
|
-
<
|
|
17
|
-
width="24"
|
|
18
|
-
height="24"
|
|
19
|
-
viewBox="0 0 24 24"
|
|
20
|
-
fill="none"
|
|
21
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
-
>
|
|
23
|
-
<path
|
|
24
|
-
fill-rule="evenodd"
|
|
25
|
-
clip-rule="evenodd"
|
|
26
|
-
d="M3 16.8892L10 12.8059V10.4546L3 6.37127V16.8892ZM11 9.88024V13.3802L2.75194 18.1916C2.41861 18.386 2 18.1456 2 17.7597V5.50075C2 5.11486 2.41861 4.87442 2.75193 5.06886L11 9.88024ZM21 16.8892V6.37127L14 10.4546V12.8059L21 16.8892ZM13 13.3802L21.2481 18.1916C21.5814 18.386 22 18.1456 22 17.7597V5.50075C22 5.11486 21.5814 4.87442 21.2481 5.06886L13 9.88024V13.3802ZM12 22.6301C11.4477 22.6301 11 22.1824 11 21.6301V15.6301C11 15.0778 11.4477 14.6301 12 14.6301C12.5523 14.6301 13 15.0778 13 15.6301V21.6301C13 22.1824 12.5523 22.6301 12 22.6301Z"
|
|
27
|
-
fill="var(--automation-device-secondary-color)"
|
|
28
|
-
/>
|
|
29
|
-
<path
|
|
30
|
-
d="M10 12.8059L3 16.8892V6.37127L10 10.4546V12.8059Z"
|
|
31
|
-
fill="var(--automation-device-tertiary-color)"
|
|
32
|
-
/>
|
|
33
|
-
<path
|
|
34
|
-
d="M21 6.37127V16.8892L14 12.8059V10.4546L21 6.37127Z"
|
|
35
|
-
fill="var(--automation-device-tertiary-color)"
|
|
36
|
-
/>
|
|
37
|
-
</svg>
|
|
17
|
+
<obi-twoway-analog-closed useCssColor> </obi-twoway-analog-closed>
|
|
38
18
|
</div>`;
|
|
39
19
|
}
|
|
40
20
|
|
|
41
21
|
const handleRotation = -(1 - this.value / 100) * 90;
|
|
22
|
+
const handleTranslation = (1 - this.value / 100) * 2;
|
|
42
23
|
const handle = svg`
|
|
43
|
-
<g transform="rotate(${handleRotation} 12
|
|
44
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.
|
|
45
|
-
<path d="
|
|
24
|
+
<g transform="translate(0, ${handleTranslation}) rotate(${handleRotation} 12 3.5) ">
|
|
25
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 5H14.5C15.3284 5 16 4.32843 16 3.5C16 2.67157 15.3284 2 14.5 2H9.5C8.67157 2 8 2.67157 8 3.5C8 4.32843 8.67157 5 9.5 5ZM9.5 4L14.5 4C14.7761 4 15 3.77614 15 3.5C15 3.22386 14.7761 3 14.5 3L9.5 3C9.22386 3 9 3.22386 9 3.5C9 3.77614 9.22386 4 9.5 4Z" fill="var(--automation-device-tertiary-color)"/>
|
|
26
|
+
<path d="M9.5 4L14.5 4C14.7761 4 15 3.77614 15 3.5C15 3.22386 14.7761 3 14.5 3L9.5 3C9.22386 3 9 3.22386 9 3.5C9 3.77614 9.22386 4 9.5 4Z" fill="var(--automation-device-primary-color)"/>
|
|
46
27
|
</g>
|
|
47
28
|
`;
|
|
48
29
|
|
|
@@ -60,11 +41,11 @@ export class ObcValveAnalogTwoWayIcon extends LitElement {
|
|
|
60
41
|
xmlns="http://www.w3.org/2000/svg"
|
|
61
42
|
>
|
|
62
43
|
<path
|
|
63
|
-
d="
|
|
44
|
+
d="M11 11L3.5547 6.03645C2.89015 5.59342 2 6.06981 2 6.8685V19.1315C2 19.9302 2.89015 20.4066 3.5547 19.9635L11 15H13L20.4453 19.9635C21.1099 20.4066 22 19.9302 22 19.1315V6.8685C22 6.06981 21.1099 5.59342 20.4453 6.03645L13 11H11ZM3 6.8685L3 19.1315L10.6972 14H13.3028L21 19.1315V6.8685L13.3028 12H10.6972L3 6.8685Z"
|
|
64
45
|
fill="var(--automation-device-tertiary-color)"
|
|
65
46
|
/>
|
|
66
47
|
<path
|
|
67
|
-
d="
|
|
48
|
+
d="M10.6972 12H13.3028L21 6.8685V19.1315L13.3028 14H10.6972L3 19.1315V6.8685L10.6972 12Z"
|
|
68
49
|
fill="var(--automation-device-primary-color)"
|
|
69
50
|
/>
|
|
70
51
|
${handle}
|
|
@@ -104,7 +85,7 @@ export class ObcValveAnalogTwoWayIcon extends LitElement {
|
|
|
104
85
|
<defs>
|
|
105
86
|
<clipPath id="clip0">
|
|
106
87
|
<path
|
|
107
|
-
d="
|
|
88
|
+
d="M10.6972 12H13.3028L21 6.8685V19.1315L13.3028 14H10.6972L3 19.1315V6.8685L10.6972 12Z"
|
|
108
89
|
/>
|
|
109
90
|
</clipPath>
|
|
110
91
|
</defs>
|
|
@@ -25,6 +25,12 @@ const meta: Meta<ObcDropdownButton> = {
|
|
|
25
25
|
openTop: {
|
|
26
26
|
control: 'boolean',
|
|
27
27
|
},
|
|
28
|
+
allowEmptySelection: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
},
|
|
31
|
+
placeholder: {
|
|
32
|
+
control: 'text',
|
|
33
|
+
},
|
|
28
34
|
},
|
|
29
35
|
args: {
|
|
30
36
|
options: [
|
|
@@ -171,3 +177,19 @@ export const DisabledWithIcon: Story = {
|
|
|
171
177
|
</obc-dropdown-button>
|
|
172
178
|
`,
|
|
173
179
|
};
|
|
180
|
+
|
|
181
|
+
export const EmptySelection: Story = {
|
|
182
|
+
args: {
|
|
183
|
+
value: 'none',
|
|
184
|
+
allowEmptySelection: true,
|
|
185
|
+
placeholder: 'Select a brand',
|
|
186
|
+
},
|
|
187
|
+
render: (args) => html`
|
|
188
|
+
<obc-dropdown-button
|
|
189
|
+
.options=${args.options}
|
|
190
|
+
.value=${args.value}
|
|
191
|
+
.allowEmptySelection=${args.allowEmptySelection}
|
|
192
|
+
.placeholder=${args.placeholder}
|
|
193
|
+
></obc-dropdown-button>
|
|
194
|
+
`,
|
|
195
|
+
};
|
|
@@ -49,6 +49,8 @@ export type DropdownButtonOption = {
|
|
|
49
49
|
* - `options` (Array): List of selectable options, each with a `value` (string), `label` (string), and optional `level` (number) for indentation.
|
|
50
50
|
* - `value` (string): The currently selected option's value. If not set, defaults to the first option.
|
|
51
51
|
* - `fullWidth` (boolean): Expands the component to fill its container when true. Default is false.
|
|
52
|
+
* - `allowEmptySelection` (boolean): When true, a `value` that does not match any option leaves the button with no option selected, showing `placeholder` instead of defaulting to the first option. Default is false.
|
|
53
|
+
* - `placeholder` (string): Text shown when nothing is selected and `allowEmptySelection` is true. Default is an empty string.
|
|
52
54
|
*
|
|
53
55
|
* ### Events
|
|
54
56
|
* - `dropdown-change` – Fired when the user selects a different option. The event detail includes `{ value, label }` of the selected option.
|
|
@@ -102,6 +104,17 @@ export class ObcDropdownButton extends LitElement {
|
|
|
102
104
|
*/
|
|
103
105
|
@property({type: Boolean}) fullWidth = false;
|
|
104
106
|
|
|
107
|
+
/**
|
|
108
|
+
* If true, a `value` that does not match any option leaves the button with no option selected,
|
|
109
|
+
* showing `placeholder` instead of defaulting to the first option. Default is false.
|
|
110
|
+
*/
|
|
111
|
+
@property({type: Boolean}) allowEmptySelection = false;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Text shown when nothing is selected and `allowEmptySelection` is true. Default is an empty string.
|
|
115
|
+
*/
|
|
116
|
+
@property({type: String}) placeholder = '';
|
|
117
|
+
|
|
105
118
|
/**
|
|
106
119
|
* Controls the button's display type.
|
|
107
120
|
* - `label`: Text label only (default)
|
|
@@ -142,10 +155,21 @@ export class ObcDropdownButton extends LitElement {
|
|
|
142
155
|
this.selectedLabel = '';
|
|
143
156
|
return;
|
|
144
157
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
const match = this.value
|
|
159
|
+
? this.options.find((item) => item.value === this.value)
|
|
160
|
+
: undefined;
|
|
161
|
+
if (match) {
|
|
162
|
+
this.selectedValue = match.value;
|
|
163
|
+
this.selectedLabel = match.label;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (this.allowEmptySelection) {
|
|
167
|
+
this.selectedValue = '';
|
|
168
|
+
this.selectedLabel = this.placeholder;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.selectedValue = this.options[0].value;
|
|
172
|
+
this.selectedLabel = this.options[0].label;
|
|
149
173
|
}
|
|
150
174
|
|
|
151
175
|
override render() {
|
|
@@ -172,6 +196,11 @@ export class ObcDropdownButton extends LitElement {
|
|
|
172
196
|
</div>
|
|
173
197
|
</div>
|
|
174
198
|
<select @change=${this.changeHandler} ?disabled=${this.disabled}>
|
|
199
|
+
${this.allowEmptySelection && this.selectedValue === ''
|
|
200
|
+
? html`<option value="" disabled selected hidden>
|
|
201
|
+
${this.placeholder}
|
|
202
|
+
</option>`
|
|
203
|
+
: nothing}
|
|
175
204
|
${this.options.map((item) => {
|
|
176
205
|
const indent = item.level ? (item.level - 1) * 2 : 0;
|
|
177
206
|
const indentText = [];
|
|
@@ -79,6 +79,10 @@ const ALLOWED_CHARS = [
|
|
|
79
79
|
|
|
80
80
|
const OPERATORS = ['+', '-', '–', '×', '÷', '*', '/'];
|
|
81
81
|
|
|
82
|
+
const ALLOWED_CHARS_PATTERN = `^[${ALLOWED_CHARS.map((c) =>
|
|
83
|
+
c.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&')
|
|
84
|
+
).join('')}]*$`;
|
|
85
|
+
|
|
82
86
|
@customElement('obc-keyboard-numeric')
|
|
83
87
|
export class ObcKeyboardNumeric extends LitElement {
|
|
84
88
|
@property({type: String}) type: ObcKeyboardNumericType =
|
|
@@ -118,6 +122,13 @@ export class ObcKeyboardNumeric extends LitElement {
|
|
|
118
122
|
@state() private content: ObcKeyboardNumericContent =
|
|
119
123
|
ObcKeyboardNumericContent.Numbers;
|
|
120
124
|
|
|
125
|
+
private get effectiveValidationPattern(): string {
|
|
126
|
+
if (!this.validationPattern) {
|
|
127
|
+
return ALLOWED_CHARS_PATTERN;
|
|
128
|
+
}
|
|
129
|
+
return `^(?=${ALLOWED_CHARS_PATTERN})(?=${this.validationPattern}).*$`;
|
|
130
|
+
}
|
|
131
|
+
|
|
121
132
|
/** Validates if a character can be added to the current value */
|
|
122
133
|
private canAddCharacter(char: string): boolean {
|
|
123
134
|
const potentialValue = this.value + char;
|
|
@@ -150,11 +161,6 @@ export class ObcKeyboardNumeric extends LitElement {
|
|
|
150
161
|
return true;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
|
-
/** Validates that all characters in a value are allowed */
|
|
154
|
-
private isValidValue(value: string): boolean {
|
|
155
|
-
return [...value].every((char) => ALLOWED_CHARS.includes(char));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
164
|
private handleCloseClick() {
|
|
159
165
|
this.dispatchEvent(
|
|
160
166
|
new CustomEvent('close-click', {bubbles: true, composed: true})
|
|
@@ -254,19 +260,6 @@ export class ObcKeyboardNumeric extends LitElement {
|
|
|
254
260
|
const input = e.target as ObcNumberInputField;
|
|
255
261
|
const newValue = input.displayValue;
|
|
256
262
|
|
|
257
|
-
if (this.validationPattern && newValue) {
|
|
258
|
-
const regex = new RegExp(this.validationPattern);
|
|
259
|
-
if (!regex.test(newValue)) {
|
|
260
|
-
input.displayOverride = this.value;
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (newValue && !this.isValidValue(newValue)) {
|
|
266
|
-
input.displayOverride = this.value;
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
263
|
this.value = newValue;
|
|
271
264
|
this.dispatchValueChange();
|
|
272
265
|
}
|
|
@@ -359,6 +352,7 @@ export class ObcKeyboardNumeric extends LitElement {
|
|
|
359
352
|
.textAlign=${this.inputFieldTextAlign}
|
|
360
353
|
.size=${ObcNumberInputFieldSize.Large}
|
|
361
354
|
.helperText=${this.helperText}
|
|
355
|
+
.validationPattern=${this.effectiveValidationPattern}
|
|
362
356
|
?hasLeadingIcon=${this.hasLeadingIcon}
|
|
363
357
|
placeholder="00.0"
|
|
364
358
|
@input=${this.handleInput}
|
|
@@ -218,4 +218,138 @@ describe('obc-number-input-field', () => {
|
|
|
218
218
|
expect(input.value).toBe('10.');
|
|
219
219
|
});
|
|
220
220
|
});
|
|
221
|
+
|
|
222
|
+
describe('beforeinput filtering', () => {
|
|
223
|
+
function dispatchBeforeInput(
|
|
224
|
+
target: HTMLInputElement,
|
|
225
|
+
data: string | null,
|
|
226
|
+
inputType = 'insertText'
|
|
227
|
+
): InputEvent {
|
|
228
|
+
const event = new InputEvent('beforeinput', {
|
|
229
|
+
data,
|
|
230
|
+
inputType,
|
|
231
|
+
cancelable: true,
|
|
232
|
+
bubbles: true,
|
|
233
|
+
});
|
|
234
|
+
target.dispatchEvent(event);
|
|
235
|
+
return event;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
beforeEach(async () => {
|
|
239
|
+
el.decimalSeparator = '.';
|
|
240
|
+
el.groupSeparator = ',';
|
|
241
|
+
input.focus();
|
|
242
|
+
input.value = '';
|
|
243
|
+
await el.updateComplete;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('blocks letters', () => {
|
|
247
|
+
const event = dispatchBeforeInput(input, 'a');
|
|
248
|
+
expect(event.defaultPrevented).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('blocks scientific notation', () => {
|
|
252
|
+
input.value = '1';
|
|
253
|
+
input.setSelectionRange(1, 1);
|
|
254
|
+
const event = dispatchBeforeInput(input, 'e');
|
|
255
|
+
expect(event.defaultPrevented).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('blocks symbols', () => {
|
|
259
|
+
const event = dispatchBeforeInput(input, '!');
|
|
260
|
+
expect(event.defaultPrevented).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('allows digits', () => {
|
|
264
|
+
const event = dispatchBeforeInput(input, '1');
|
|
265
|
+
expect(event.defaultPrevented).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('allows decimal separator', () => {
|
|
269
|
+
const event = dispatchBeforeInput(input, '.');
|
|
270
|
+
expect(event.defaultPrevented).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('allows comma when locale uses it', () => {
|
|
274
|
+
const event = dispatchBeforeInput(input, ',');
|
|
275
|
+
expect(event.defaultPrevented).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('allows leading sign', () => {
|
|
279
|
+
const event = dispatchBeforeInput(input, '-');
|
|
280
|
+
expect(event.defaultPrevented).toBe(false);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('allows deletions (null data)', () => {
|
|
284
|
+
input.value = '12';
|
|
285
|
+
input.setSelectionRange(2, 2);
|
|
286
|
+
const event = dispatchBeforeInput(input, null, 'deleteContentBackward');
|
|
287
|
+
expect(event.defaultPrevented).toBe(false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('blocks paste containing letters', () => {
|
|
291
|
+
const event = new InputEvent('beforeinput', {
|
|
292
|
+
data: null,
|
|
293
|
+
inputType: 'insertFromPaste',
|
|
294
|
+
cancelable: true,
|
|
295
|
+
bubbles: true,
|
|
296
|
+
});
|
|
297
|
+
const dt = new DataTransfer();
|
|
298
|
+
dt.setData('text/plain', '12abc34');
|
|
299
|
+
Object.defineProperty(event, 'dataTransfer', {value: dt});
|
|
300
|
+
input.dispatchEvent(event);
|
|
301
|
+
expect(event.defaultPrevented).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('allows paste of numeric string with separators', () => {
|
|
305
|
+
const event = new InputEvent('beforeinput', {
|
|
306
|
+
data: null,
|
|
307
|
+
inputType: 'insertFromPaste',
|
|
308
|
+
cancelable: true,
|
|
309
|
+
bubbles: true,
|
|
310
|
+
});
|
|
311
|
+
const dt = new DataTransfer();
|
|
312
|
+
dt.setData('text/plain', '1,234.5');
|
|
313
|
+
Object.defineProperty(event, 'dataTransfer', {value: dt});
|
|
314
|
+
input.dispatchEvent(event);
|
|
315
|
+
expect(event.defaultPrevented).toBe(false);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('honours custom decimalSeparator', async () => {
|
|
319
|
+
el.decimalSeparator = ',';
|
|
320
|
+
el.groupSeparator = ' ';
|
|
321
|
+
await el.updateComplete;
|
|
322
|
+
|
|
323
|
+
const dot = dispatchBeforeInput(input, '.');
|
|
324
|
+
expect(dot.defaultPrevented).toBe(true);
|
|
325
|
+
|
|
326
|
+
const comma = dispatchBeforeInput(input, ',');
|
|
327
|
+
expect(comma.defaultPrevented).toBe(false);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('honours custom validationPattern', async () => {
|
|
331
|
+
el.validationPattern = '^[0-9]*$';
|
|
332
|
+
await el.updateComplete;
|
|
333
|
+
|
|
334
|
+
const minus = dispatchBeforeInput(input, '-');
|
|
335
|
+
expect(minus.defaultPrevented).toBe(true);
|
|
336
|
+
|
|
337
|
+
const digit = dispatchBeforeInput(input, '1');
|
|
338
|
+
expect(digit.defaultPrevented).toBe(false);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('does not block when disabled', async () => {
|
|
342
|
+
el.disabled = true;
|
|
343
|
+
await el.updateComplete;
|
|
344
|
+
const event = dispatchBeforeInput(input, 'a');
|
|
345
|
+
expect(event.defaultPrevented).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('does not block when readonly', async () => {
|
|
349
|
+
el.readonly = true;
|
|
350
|
+
await el.updateComplete;
|
|
351
|
+
const event = dispatchBeforeInput(input, 'a');
|
|
352
|
+
expect(event.defaultPrevented).toBe(false);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
221
355
|
});
|
|
@@ -13,6 +13,7 @@ import {classMap} from 'lit/directives/class-map.js';
|
|
|
13
13
|
import {customElement} from '../../decorator.js';
|
|
14
14
|
import {
|
|
15
15
|
formatNumberForDisplay,
|
|
16
|
+
isAllowedIntermediateInput,
|
|
16
17
|
NumberInputFormatOptions,
|
|
17
18
|
parseNumberInput,
|
|
18
19
|
removeGroupingFromDisplay,
|
|
@@ -113,6 +114,14 @@ export class ObcNumberInputField extends LitElement {
|
|
|
113
114
|
@property({type: Number}) minFractionDigits = 0;
|
|
114
115
|
@property({type: Number}) maxFractionDigits?: number | undefined;
|
|
115
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Optional regex pattern. When set, any keystroke or paste whose resulting
|
|
119
|
+
* value does not match this pattern is blocked at the `beforeinput` stage.
|
|
120
|
+
* When unset, a permissive numeric filter (digits, sign, active separators,
|
|
121
|
+
* whitespace) is applied instead.
|
|
122
|
+
*/
|
|
123
|
+
@property({type: String}) validationPattern = '';
|
|
124
|
+
|
|
116
125
|
@state() private hasFocus = false;
|
|
117
126
|
@state() private displayText = '';
|
|
118
127
|
@state() private previousValue = NaN;
|
|
@@ -150,6 +159,43 @@ export class ObcNumberInputField extends LitElement {
|
|
|
150
159
|
this.dispatchInput();
|
|
151
160
|
}
|
|
152
161
|
|
|
162
|
+
private isProjectedValueAllowed(projected: string): boolean {
|
|
163
|
+
if (this.validationPattern) {
|
|
164
|
+
let regex: RegExp;
|
|
165
|
+
try {
|
|
166
|
+
// Anchor the consumer pattern so it must match the whole projected
|
|
167
|
+
// value rather than any substring (e.g. `[0-9]` must not pass `a1`).
|
|
168
|
+
regex = new RegExp(`^(?:${this.validationPattern})$`);
|
|
169
|
+
} catch {
|
|
170
|
+
// Invalid pattern: fall back to the numeric filter instead of
|
|
171
|
+
// allowing arbitrary input.
|
|
172
|
+
return isAllowedIntermediateInput(projected, this.getFormatOptions());
|
|
173
|
+
}
|
|
174
|
+
return regex.test(projected);
|
|
175
|
+
}
|
|
176
|
+
return isAllowedIntermediateInput(projected, this.getFormatOptions());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private onBeforeInput(e: InputEvent) {
|
|
180
|
+
if (this.disabled || this.readonly) return;
|
|
181
|
+
const input = e.target as HTMLInputElement;
|
|
182
|
+
|
|
183
|
+
const incoming = e.data ?? e.dataTransfer?.getData('text/plain') ?? null;
|
|
184
|
+
// Deletions, history, and formatting (`insertLineBreak`, `historyUndo`,
|
|
185
|
+
// `deleteContentBackward`, etc.) have `data === null` and no transferable
|
|
186
|
+
// text — always allow them.
|
|
187
|
+
if (incoming === null) return;
|
|
188
|
+
|
|
189
|
+
const start = input.selectionStart ?? input.value.length;
|
|
190
|
+
const end = input.selectionEnd ?? input.value.length;
|
|
191
|
+
const projected =
|
|
192
|
+
input.value.slice(0, start) + incoming + input.value.slice(end);
|
|
193
|
+
|
|
194
|
+
if (!this.isProjectedValueAllowed(projected)) {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
153
199
|
private onFocus() {
|
|
154
200
|
this.hasFocus = true;
|
|
155
201
|
const source = this.displayOverride || this.displayText;
|
|
@@ -444,6 +490,7 @@ export class ObcNumberInputField extends LitElement {
|
|
|
444
490
|
hasHelperOrError ? 'helper-text' : undefined
|
|
445
491
|
)}
|
|
446
492
|
autocomplete="off"
|
|
493
|
+
@beforeinput=${this.onBeforeInput}
|
|
447
494
|
@input=${this.onInput}
|
|
448
495
|
/>
|
|
449
496
|
${unitInside
|