@kubex/zinc 1.1.178 → 1.1.179

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.
@@ -187,6 +187,19 @@ Use the `xaxis` attribute to specify the x-axis type. Common values include `dat
187
187
  When using `xaxis="datetime"`, provide data in the format `[{x: timestamp, y: value}]` where `x` is a Unix timestamp in milliseconds. The chart will automatically format the dates on the x-axis.
188
188
  :::
189
189
 
190
+ ### Expected Range Bands
191
+
192
+ Each series accepts optional `stack`, `areaStyle`, `lineStyle`, `symbol`, `silent` and `z` settings, which apply to that series only. Draw a shaded band with two series sharing a `stack`: an invisible base holding the lower bound, then a filled series holding the band's height. Giving both the same `name` lets the legend toggle the band as one. `silent` series ignore the mouse and stay out of tooltips.
193
+
194
+ ```html:preview
195
+ <zn-chart
196
+ type="line"
197
+ xaxis="datetime"
198
+ data="[{&quot;name&quot;:&quot;Expected range&quot;,&quot;data&quot;:[[1609459200000,20],[1609462800000,25],[1609466400000,28],[1609470000000,30],[1609473600000,26],[1609477200000,22]],&quot;stack&quot;:&quot;band&quot;,&quot;areaStyle&quot;:{&quot;opacity&quot;:0},&quot;lineStyle&quot;:{&quot;opacity&quot;:0},&quot;symbol&quot;:&quot;none&quot;,&quot;silent&quot;:true,&quot;color&quot;:&quot;#93c5fd&quot;},{&quot;name&quot;:&quot;Expected range&quot;,&quot;data&quot;:[[1609459200000,20],[1609462800000,20],[1609466400000,22],[1609470000000,24],[1609473600000,22],[1609477200000,20]],&quot;stack&quot;:&quot;band&quot;,&quot;areaStyle&quot;:{&quot;opacity&quot;:0.25},&quot;lineStyle&quot;:{&quot;opacity&quot;:0},&quot;symbol&quot;:&quot;none&quot;,&quot;silent&quot;:true,&quot;color&quot;:&quot;#93c5fd&quot;},{&quot;name&quot;:&quot;Traffic&quot;,&quot;data&quot;:[[1609459200000,30],[1609462800000,38],[1609466400000,70],[1609470000000,44],[1609473600000,40],[1609477200000,33]],&quot;color&quot;:&quot;#2563eb&quot;}]"
199
+ height="300">
200
+ </zn-chart>
201
+ ```
202
+
190
203
  ### Enable Animations
191
204
 
192
205
  By default, animations are disabled for better performance. Add the `enable-animations` attribute to animate on first render — bars grow from the baseline and lines draw in. Pass a number (milliseconds) to control the duration; bare `enable-animations` defaults to `1500`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.1.178",
3
+ "version": "1.1.179",
4
4
  "description": "A collection of web components for building web applications based off of @shoelace-style/Shoelace",
5
5
  "keywords": [
6
6
  "web components",
@@ -123,6 +123,38 @@ describe('buildLineOption', () => {
123
123
  });
124
124
  });
125
125
 
126
+ describe('per-series options', () => {
127
+ const band = (overrides: Partial<BuilderProps> = {}) => buildLineOption({
128
+ ...baseProps,
129
+ xAxisType: 'datetime',
130
+ data: [
131
+ { name: 'Expected range', data: [[1, 10], [2, 12]], stack: 'band', areaStyle: { opacity: 0 }, lineStyle: { opacity: 0 }, symbol: 'none', silent: true },
132
+ { name: 'Expected range', data: [[1, 5], [2, 6]], stack: 'band', areaStyle: { opacity: 0.15 }, lineStyle: { opacity: 0 }, symbol: 'none', silent: true },
133
+ { name: 'Events', data: [[1, 12], [2, 30]] },
134
+ ],
135
+ ...overrides,
136
+ });
137
+
138
+ it('draws a band from stacked series without styling other series', () => {
139
+ const series = band().series as any[];
140
+ expect(series[0].stack).to.equal('band');
141
+ expect(series[1].stack).to.equal('band');
142
+ expect(series[1].areaStyle).to.deep.equal({ opacity: 0.15 });
143
+ expect(series[0].symbol).to.equal('none');
144
+ expect(series[0].silent).to.equal(true);
145
+ expect(series[0].tooltip).to.deep.equal({ show: false });
146
+ expect(series[2].stack).to.be.undefined;
147
+ expect(series[2].areaStyle).to.be.undefined;
148
+ expect(series[2].silent).to.be.undefined;
149
+ });
150
+
151
+ it('keeps a per-series stack when the chart is stacked', () => {
152
+ const series = band({ stacked: true }).series as any[];
153
+ expect(series[0].stack).to.equal('band');
154
+ expect(series[2].stack).to.equal('total');
155
+ });
156
+ });
157
+
126
158
  describe('buildAreaOption', () => {
127
159
  const baseAreaProps: BuilderProps = {
128
160
  type: 'area',
@@ -20,6 +20,15 @@ export interface SeriesItem {
20
20
  name: string;
21
21
  data: any[];
22
22
  color?: string;
23
+ /** Series sharing a stack ID are stacked, overriding the chart-wide `stacked` setting. */
24
+ stack?: string;
25
+ /** Fill below a line, for example `{ opacity: 0.15 }`; `{ opacity: 0 }` keeps a stacked base invisible. */
26
+ areaStyle?: Record<string, unknown>;
27
+ lineStyle?: Record<string, unknown>;
28
+ symbol?: string;
29
+ /** Excludes the series from mouse interaction and tooltips. */
30
+ silent?: boolean;
31
+ z?: number;
23
32
  }
24
33
 
25
34
  export interface SankeyEdge {
@@ -168,14 +177,28 @@ function seriesFromProps(
168
177
  name: s.name,
169
178
  data: normalizeData(s.data),
170
179
  ...(s.color ? { itemStyle: { color: s.color } } : {}),
171
- ...(props.stacked ? { stack: 'total' } : {}),
180
+ ...(s.stack ? { stack: s.stack } : props.stacked ? { stack: 'total' } : {}),
172
181
  ...(props.enableAnimations !== false && props.enableAnimations !== 0 && seriesType === 'bar'
173
182
  ? { animationDelay: (idx: number) => idx * 50 }
174
183
  : {}),
175
184
  ...extra(s),
185
+ ...seriesStyle(s),
176
186
  }));
177
187
  }
178
188
 
189
+ // seriesStyle applies a series' own display options, which take precedence
190
+ // over chart-wide defaults. Bands are drawn as an invisible stacked base
191
+ // followed by a filled series holding the band's height.
192
+ function seriesStyle(s: SeriesItem): Record<string, unknown> {
193
+ return {
194
+ ...(s.areaStyle ? { areaStyle: s.areaStyle } : {}),
195
+ ...(s.lineStyle ? { lineStyle: s.lineStyle } : {}),
196
+ ...(s.symbol ? { symbol: s.symbol } : {}),
197
+ ...(s.silent ? { silent: true, tooltip: { show: false } } : {}),
198
+ ...(s.z !== undefined ? { z: s.z } : {}),
199
+ };
200
+ }
201
+
179
202
  export function buildBarOption(props: BuilderProps): EChartsOption {
180
203
  return {
181
204
  ...commonOption(props),
@@ -11,6 +11,28 @@ describe('<zn-input>', () => {
11
11
  expect(el).to.exist;
12
12
  });
13
13
 
14
+ describe('form data', () => {
15
+ it('trims surrounding whitespace, except for passwords', async () => {
16
+ const form = await fixture<HTMLFormElement>(html`
17
+ <form>
18
+ <zn-input name="search" value=" foo bar "></zn-input>
19
+ <zn-input name="pass" type="password" value=" secret "></zn-input>
20
+ </form>
21
+ `);
22
+ const [search, pass] = form.querySelectorAll<ZnInput>('zn-input');
23
+ await search.updateComplete;
24
+
25
+ const data = new FormData(form);
26
+ expect(data.get('search')).to.equal('foo bar');
27
+ expect(data.get('pass')).to.equal(' secret ');
28
+
29
+ await search.updateComplete;
30
+ expect(search.value).to.equal('foo bar');
31
+ expect(search.input.value).to.equal('foo bar');
32
+ expect(pass.value).to.equal(' secret ');
33
+ });
34
+ });
35
+
14
36
  describe('Escape key', () => {
15
37
  it('blurs the inner input and stops propagation when focused', async () => {
16
38
  const wrapper = await fixture<HTMLDivElement>(html`
@@ -429,8 +429,10 @@ export class FormControlController implements ReactiveController {
429
429
  name.length > 0 &&
430
430
  typeof value !== 'undefined'
431
431
  ) {
432
+ const isPassword = (this.host as HTMLElement & { type?: string }).type === 'password';
432
433
  const appendPrimitive = (val: string | number | boolean) => {
433
- event.formData.append(name, val.toString());
434
+ const str = val.toString();
435
+ event.formData.append(name, isPassword ? str : str.trim());
434
436
  };
435
437
  const appendFile = (file: Blob, filename?: string) => {
436
438
  // If a filename is provided or it's a File (has name), FormData will use it properly
@@ -473,6 +475,11 @@ export class FormControlController implements ReactiveController {
473
475
  });
474
476
  } else {
475
477
  appendPrimitive(value as string | number | boolean);
478
+
479
+ const host = this.host as HTMLElement & { value?: unknown };
480
+ if (!isPassword && typeof value === 'string' && host.value === value && value.trim() !== value) {
481
+ host.value = value.trim();
482
+ }
476
483
  }
477
484
 
478
485
  this.host.dispatchEvent(new CustomEvent('zn-formdata', {bubbles: true, composed: true}));