@kubex/zinc 2.0.2 → 2.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
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",
@@ -12,8 +12,8 @@ import ZnDropdown from "../dropdown";
12
12
  import ZnIcon from "../icon";
13
13
  import ZnTooltip from "../tooltip";
14
14
  import type {IconColor, IconLibrary} from "../icon";
15
- import type ZnMenu from "../menu";
16
15
  import type {ZincFormControl} from '../../internal/zinc-element';
16
+ import type ZnMenu from "../menu";
17
17
 
18
18
  import sharedStyles from '../../../scss/shared/index.scss';
19
19
  import styles from './button.scss';
@@ -641,7 +641,7 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
641
641
  'button--icon-button-small': this.iconButton === 'small',
642
642
  'button--icon-button-round': this.iconButton === 'round',
643
643
  'button--grow': this.grow,
644
- 'button--standard': !this.outline && !this.text,
644
+ 'button--standard': !this.outline && (!this.text || this.solid),
645
645
  'button--with-icon': this.icon,
646
646
  'button--icon-left': this.iconPosition === 'left',
647
647
  'button--icon-right': this.iconPosition === 'right',
@@ -66,6 +66,13 @@ describe('<zn-button>', () => {
66
66
  expect(base.classList.contains('button--large')).to.be.true;
67
67
  });
68
68
 
69
+ it('keeps the filled style when solid overrides text', async () => {
70
+ const el = await fixture<ZnButton>(html`<zn-button text solid color="primary">Create</zn-button>`);
71
+ const base = el.shadowRoot!.querySelector('[part~="base"]')!;
72
+ expect(base.classList.contains('button--standard')).to.be.true;
73
+ expect(base.classList.contains('button--text')).to.be.false;
74
+ });
75
+
69
76
  it('applies the ai color', async () => {
70
77
  const el = await fixture<ZnButton>(html`<zn-button color="ai">Summarise</zn-button>`);
71
78
  const base = el.shadowRoot!.querySelector('[part~="base"]')!;
@@ -16,8 +16,8 @@ import {
16
16
  type RadarIndicator,
17
17
  type SeriesItem,
18
18
  } from './builders';
19
- import { type CSSResultGroup, html, type PropertyValues, unsafeCSS } from 'lit';
20
19
  import { classMap } from 'lit/directives/class-map.js';
20
+ import { type CSSResultGroup, html, type PropertyValues, unsafeCSS } from 'lit';
21
21
  import { type EChartsModule, loadECharts } from './echarts-loader';
22
22
  import { LiveTransport, LiveWindow } from './live';
23
23
  import { property, state } from 'lit/decorators.js';
@@ -1,7 +1,7 @@
1
1
  import '../../../../../dist/zn.min.js';
2
2
  import {expect, fixture, html, oneEvent} from '@open-wc/testing';
3
- import type DialogComponent from './dialog.component';
4
3
  import type {ZnEditorInsertEvent} from '../events/zn-editor-insert';
4
+ import type DialogComponent from './dialog.component';
5
5
 
6
6
  describe('<zn-editor-dialog> content inserts', () => {
7
7
  const dialog = () => fixture<DialogComponent>(html`
@@ -3,10 +3,10 @@ import {type CSSResultGroup, html, nothing, type PropertyValues, unsafeCSS} from
3
3
  import {deepQuerySelectorAll} from "../../utilities/query";
4
4
  import {md5} from "../../utilities/md5";
5
5
  import {MutationController} from '@lit-labs/observers/mutation-controller.js';
6
+ import {onHoverIntent} from '../../internal/hover-intent';
6
7
  import {property} from 'lit/decorators.js';
7
8
  import {styleMap} from "lit/directives/style-map.js";
8
9
  import ZincElement from '../../internal/zinc-element';
9
- import {onHoverIntent} from '../../internal/hover-intent';
10
10
 
11
11
  import styles from './expanding-action.scss';
12
12
 
@@ -3,8 +3,8 @@ import {property, state} from "lit/decorators.js";
3
3
  import {trimControlValue} from '../../internal/form';
4
4
  import ZincElement from '../../internal/zinc-element';
5
5
 
6
- import type ZnDataSelect from "../data-select";
7
6
  import type ZnButton from "../button";
7
+ import type ZnDataSelect from "../data-select";
8
8
  import type ZnDataTable from "../data-table";
9
9
  import type ZnInput from "../input";
10
10
  import type ZnQueryBuilder from "../query-builder";
@@ -86,7 +86,7 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
86
86
 
87
87
  @property({ type: Boolean }) inline: boolean
88
88
 
89
- @property({ type: Boolean }) padded: boolean
89
+ @property({ type: Boolean, reflect: true }) padded: boolean
90
90
 
91
91
  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
92
92
 
@@ -436,7 +436,6 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
436
436
  'ai--focused': this.hasFocus,
437
437
  'ai--disabled': this.disabled,
438
438
  'ai--inline': this.inline,
439
- 'ai--padded': this.padded,
440
439
  'ai--masked': !!this.displayValue,
441
440
  })}" dir="ltr">
442
441
 
@@ -1,30 +1,34 @@
1
1
  @use "../../wc";
2
2
 
3
3
  :host {
4
+ --ai-input-height: var(--zn-input-height-medium);
5
+
4
6
  display: block;
7
+ box-sizing: border-box;
5
8
  container-type: inline-size;
6
9
  width: 100%;
7
10
  }
8
11
 
12
+ // On the host rather than the row, so the hover fill and edit actions stay hugging the field
13
+ :host([padded]) {
14
+ padding: var(--zn-spacing-large);
15
+ }
16
+
9
17
  .ai {
10
18
  --bleed: var(--zn-inline-edit-bleed, 10px);
11
19
  display: flex;
12
- width: 100%;
20
+ box-sizing: border-box;
21
+ // The hover fill hangs out past the value's left edge, so the text stays aligned with the column
22
+ width: calc(100% + var(--bleed));
23
+ margin-inline-start: calc(-1 * var(--bleed));
24
+ padding-inline-start: var(--bleed);
13
25
  flex-direction: row;
14
26
  gap: 10px;
15
27
  border-radius: var(--zn-border-radius-medium);
16
28
  transition: background-color var(--zn-transition-fast) ease;
17
29
 
18
- // The hover fill hangs out past the value's left edge, so the text stays aligned with the column
19
- &:not(.ai--padded) {
20
- box-sizing: border-box;
21
- width: calc(100% + var(--bleed));
22
- margin-inline-start: calc(-1 * var(--bleed));
23
- padding-inline-start: var(--bleed);
24
- }
25
-
26
30
  // The input's own padding takes over from the bleed, keeping its text roughly where the value was
27
- &--editing:not(.ai--padded) {
31
+ &--editing {
28
32
  padding-inline-start: 0;
29
33
  }
30
34
 
@@ -116,10 +120,6 @@
116
120
  }
117
121
  }
118
122
 
119
- &--padded {
120
- padding: var(--zn-spacing-large);
121
- }
122
-
123
123
  &--inline {
124
124
  display: flex;
125
125
  flex-direction: row;
@@ -160,7 +160,7 @@
160
160
  position: absolute;
161
161
  inset-block-start: 0;
162
162
  inset-inline-end: var(--action-inset);
163
- height: var(--zn-input-height-medium);
163
+ height: var(--ai-input-height);
164
164
  gap: 2px;
165
165
  }
166
166
 
@@ -238,7 +238,7 @@
238
238
 
239
239
  &__reveal {
240
240
  // Matches the height of the input it stands in for, so entering edit mode doesn't resize the row
241
- line-height: var(--zn-input-height-medium);
241
+ line-height: var(--ai-input-height);
242
242
  color: rgb(var(--zn-text));
243
243
 
244
244
  // Fill the row so hovering anywhere over the field reveals the value
@@ -312,10 +312,10 @@ zn-textarea::part(textarea) {
312
312
  resize: none;
313
313
  }
314
314
 
315
- :host([size="small"]) .ai__reveal {
316
- line-height: var(--zn-input-height-small);
315
+ :host([size="small"]) {
316
+ --ai-input-height: var(--zn-input-height-small);
317
317
  }
318
318
 
319
- :host([size="large"]) .ai__reveal {
320
- line-height: var(--zn-input-height-large);
319
+ :host([size="large"]) {
320
+ --ai-input-height: var(--zn-input-height-large);
321
321
  }
@@ -192,7 +192,7 @@ export default class ZnInput extends ZincElement implements ZincFormControl {
192
192
  /** Determines whether or no the password is currently visible. Only applies to password types **/
193
193
  @property({attribute: 'password-visible', type: Boolean}) passwordVisible: boolean = false;
194
194
 
195
- /** Hides the browsers built-in increment/decrement spin buttons for number inputs **/
195
+ /** Hides the increment/decrement buttons on number inputs **/
196
196
  @property({attribute: 'no-spin-buttons', type: Boolean}) noSpinButtons: boolean = false;
197
197
 
198
198
  /**
@@ -505,6 +505,27 @@ export default class ZnInput extends ZincElement implements ZincFormControl {
505
505
  }
506
506
  }
507
507
 
508
+ /** Whether the value has reached `limit`; `direction` 1 checks a maximum, -1 a minimum. */
509
+ private isAtLimit(limit: number | string | undefined, direction: 1 | -1): boolean {
510
+ const value = parseFloat(String(this.value));
511
+ const bound = parseFloat(String(limit));
512
+ return !isNaN(value) && !isNaN(bound) && (value - bound) * direction >= 0;
513
+ }
514
+
515
+ private handleStep(direction: 'up' | 'down') {
516
+ const previous: unknown = this.value;
517
+ if (direction === 'up') {
518
+ this.stepUp();
519
+ } else {
520
+ this.stepDown();
521
+ }
522
+ if (this.value === previous) return;
523
+ // stepUp()/stepDown() don't fire events, unlike the native spinner they replace
524
+ this.formControlController.updateValidity();
525
+ this.emit('zn-input');
526
+ this.emit('zn-change');
527
+ }
528
+
508
529
  private handlePasswordToggle() {
509
530
  this.passwordVisible = !this.passwordVisible;
510
531
  }
@@ -861,7 +882,6 @@ export default class ZnInput extends ZincElement implements ZincFormControl {
861
882
  'input--focused': this.hasFocus,
862
883
  'input--empty': !this.value,
863
884
  'input--range': isRange,
864
- 'input--no-spin-buttons': this.noSpinButtons && this.type !== 'currency',
865
885
  'input--color': this.type === 'color',
866
886
  'input--color-invalid': this.colorInvalid,
867
887
  })}>
@@ -973,6 +993,22 @@ export default class ZnInput extends ZincElement implements ZincFormControl {
973
993
  </button>`
974
994
  : ''}
975
995
 
996
+ ${this.type === 'currency' || (this.type === 'number' && !this.noSpinButtons)
997
+ ? html`
998
+ <span part="stepper" class="input__stepper">
999
+ <button class="input__stepper-button" type="button" tabindex="-1" aria-label="Decrement"
1000
+ ?disabled=${this.disabled || this.readonly || this.isAtLimit(this.min, -1)}
1001
+ @click=${() => this.handleStep('down')}>
1002
+ <zn-icon src="minus@lu" size="16"></zn-icon>
1003
+ </button>
1004
+ <button class="input__stepper-button" type="button" tabindex="-1" aria-label="Increment"
1005
+ ?disabled=${this.disabled || this.readonly || this.isAtLimit(this.max, 1)}
1006
+ @click=${() => this.handleStep('up')}>
1007
+ <zn-icon src="plus@lu" size="16"></zn-icon>
1008
+ </button>
1009
+ </span>`
1010
+ : ''}
1011
+
976
1012
  <span part="suffix" class="input__suffix">
977
1013
  <slot name="suffix"></slot>
978
1014
  </span>
@@ -386,17 +386,81 @@ input[type='text'].input__control[data-color-input] {
386
386
  display: none;
387
387
  }
388
388
 
389
- /* Hide the built-in number spinner */
390
- .input--no-spin-buttons input[type='number']::-webkit-outer-spin-button,
391
- .input--no-spin-buttons input[type='number']::-webkit-inner-spin-button {
389
+ /* The built-in spinner is replaced by .input__stepper */
390
+ input[type='number']::-webkit-outer-spin-button,
391
+ input[type='number']::-webkit-inner-spin-button {
392
392
  -webkit-appearance: none;
393
393
  display: none;
394
394
  }
395
395
 
396
- .input--no-spin-buttons input[type='number'] {
396
+ input[type='number'] {
397
397
  -moz-appearance: textfield;
398
398
  }
399
399
 
400
+ .input__stepper {
401
+ --stepper-button-size: calc(var(--zn-input-height-medium) - 10px);
402
+ display: flex;
403
+ flex: 0 0 auto;
404
+ align-items: center;
405
+ gap: 4px;
406
+ padding-inline: 4px 5px;
407
+ }
408
+
409
+ .input--x-small .input__stepper {
410
+ --stepper-button-size: calc(var(--zn-input-height-x-small) - 6px);
411
+ gap: 2px;
412
+ padding-inline: 2px 3px;
413
+ }
414
+
415
+ .input--small .input__stepper {
416
+ --stepper-button-size: calc(var(--zn-input-height-small) - 8px);
417
+ }
418
+
419
+ .input--large .input__stepper {
420
+ --stepper-button-size: calc(var(--zn-input-height-large) - 10px);
421
+ }
422
+
423
+ .input__stepper-button {
424
+ display: grid;
425
+ place-content: center;
426
+ flex: 0 0 auto;
427
+ width: var(--stepper-button-size);
428
+ height: var(--stepper-button-size);
429
+ padding: 0;
430
+ border: none;
431
+ border-radius: var(--zn-border-radius-small);
432
+ background: none;
433
+ cursor: pointer;
434
+ transition: var(--zn-transition-fast) background-color,
435
+ transform 160ms var(--zn-ease-out, cubic-bezier(0.23, 1, 0.32, 1));
436
+
437
+ zn-icon {
438
+ --icon-color: var(--zn-input-icon-color);
439
+ }
440
+
441
+ &:hover:not(:disabled) {
442
+ background-color: var(--zn-color-neutral-100);
443
+
444
+ zn-icon {
445
+ --icon-color: var(--zn-input-icon-color-hover);
446
+ }
447
+ }
448
+
449
+ // Deeper than zn-button's 0.97, which is imperceptible at this size
450
+ &:active:not(:disabled) {
451
+ background-color: var(--zn-color-neutral-200);
452
+ transform: scale(0.9);
453
+ }
454
+
455
+ &:disabled {
456
+ cursor: not-allowed;
457
+
458
+ zn-icon {
459
+ --icon-color: var(--zn-input-border-color);
460
+ }
461
+ }
462
+ }
463
+
400
464
  /* Replace the -webkit-calendar-picker icon */
401
465
  input[type='date']::-webkit-calendar-picker-indicator {
402
466
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="14" width="12.25" viewBox="0 0 448 512"><path fill="%236d7176" d="M128 0c13.3 0 24 10.7 24 24V64H296V24c0-13.3 10.7-24 24-24s24 10.7 24 24V64h40c35.3 0 64 28.7 64 64v16 48V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V192 144 128C0 92.7 28.7 64 64 64h40V24c0-13.3 10.7-24 24-24zM400 192H48V448c0 8.8 7.2 16 16 16H384c8.8 0 16-7.2 16-16V192zM112 256h96c8.8 0 16 7.2 16 16v96c0 8.8-7.2 16-16 16H112c-8.8 0-16-7.2-16-16V272c0-8.8 7.2-16 16-16z"/></svg>');
@@ -1,6 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
- import {expect, fixture, html, waitUntil} from '@open-wc/testing';
3
2
  import {clearFormStoreValues} from '../../utilities/form';
3
+ import {expect, fixture, html, waitUntil} from '@open-wc/testing';
4
4
  import type ZnInput from './input.component';
5
5
  import type ZnSlashMenu from '../slash-menu/slash-menu.component';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import {aTimeout, expect, fixture, html} from '@open-wc/testing';
3
- import {centre, drop, dragTo, startDrag} from '../../internal/test-drag';
3
+ import {centre, dragTo, drop, startDrag} from '../../internal/test-drag';
4
4
  import type ZnNavItem from '../nav-item';
5
5
  import type ZnNavList from './nav-list.component';
6
6
 
@@ -1,10 +1,10 @@
1
1
  import {classMap} from "lit/directives/class-map.js";
2
2
  import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
3
3
  import {ifDefined} from "lit/directives/if-defined.js";
4
+ import {type IndicatorEdges, slideTabIndicator, squishTabIndicator} from "../../internal/tab-indicator";
4
5
  import {MutationController} from '@lit-labs/observers/mutation-controller.js';
5
6
  import {property} from 'lit/decorators.js';
6
7
  import {ResizeController} from '@lit-labs/observers/resize-controller.js';
7
- import {type IndicatorEdges, slideTabIndicator, squishTabIndicator} from "../../internal/tab-indicator";
8
8
  import {Store} from "../../internal/storage";
9
9
  import ZincElement from '../../internal/zinc-element';
10
10
  import ZnDropdown from "../dropdown";
@@ -111,6 +111,7 @@ export default class ZnNavbar extends ZincElement {
111
111
  private _indicator: HTMLElement | null = null;
112
112
  private _indicatorFrame: number | null = null;
113
113
  private _indicatorTab: HTMLElement | null = null;
114
+ private _indicatorStripWidth = 0;
114
115
  private _indicatorTarget: IndicatorEdges | null = null;
115
116
  private _expandable: HTMLElement | null = null;
116
117
  private readonly _lightDomClones = new WeakMap<HTMLElement, HTMLElement>();
@@ -242,18 +243,23 @@ export default class ZnNavbar extends ZincElement {
242
243
 
243
244
  // The indicator scrolls with the tabs, so it is placed in the strip's content coordinates
244
245
  const box = container.getBoundingClientRect();
245
- const originLeft = box.left - container.scrollLeft;
246
- const target = {left: tab.left - originLeft, right: tab.right - originLeft};
246
+ // An ancestor mid scale-in (e.g. a modal opening) shrinks every rect, and its end fires no resize to re-measure
247
+ const scaleX = box.width / container.offsetWidth || 1;
248
+ const scaleY = box.height / container.offsetHeight || 1;
249
+ const toStrip = (x: number) => (x - box.left) / scaleX + container.scrollLeft;
250
+ const target = {left: toStrip(tab.left), right: toStrip(tab.right)};
247
251
  const previous = this._indicatorTarget;
248
252
  const tabChanged = active !== this._indicatorTab;
249
253
  const ready = indicator.classList.contains('navbar__indicator--ready');
250
254
  this._indicatorTab = active ?? null;
251
- if (tabChanged && active) {
252
- this._revealTab(active, ready);
255
+ const stripResized = container.clientWidth !== this._indicatorStripWidth;
256
+ this._indicatorStripWidth = container.clientWidth;
257
+ if (active && (tabChanged || stripResized)) {
258
+ this._revealTab(active, ready && tabChanged);
253
259
  }
254
260
  // Exposed for ::part(indicator) styles that fill the tab rather than underline it.
255
- setStyle(indicator, '--zn-navbar-indicator-top', `${tab.top - box.top}px`);
256
- setStyle(indicator, '--zn-navbar-indicator-height', `${tab.height}px`);
261
+ setStyle(indicator, '--zn-navbar-indicator-top', `${(tab.top - box.top) / scaleY}px`);
262
+ setStyle(indicator, '--zn-navbar-indicator-height', `${tab.height / scaleY}px`);
257
263
  indicator.classList.toggle('navbar__indicator--shown', true);
258
264
  if (previous && Math.abs(previous.left - target.left) < 0.5 && Math.abs(previous.right - target.right) < 0.5) {
259
265
  return;
@@ -271,7 +277,7 @@ export default class ZnNavbar extends ZincElement {
271
277
  indicator.style.width = `${target.right - target.left}px`;
272
278
  this._indicatorTarget = target;
273
279
  if (slide) {
274
- slideTabIndicator(indicator, {left: drawn.left - originLeft, right: drawn.right - originLeft}, target);
280
+ slideTabIndicator(indicator, {left: toStrip(drawn.left), right: toStrip(drawn.right)}, target);
275
281
  }
276
282
 
277
283
  // The first placement jumps into position; only later moves animate.
@@ -1,7 +1,7 @@
1
- import {ifDefined} from "lit/directives/if-defined.js";
2
1
  import {classMap} from "lit/directives/class-map.js";
3
2
  import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
4
3
  import {HasSlotController} from "../../internal/slot";
4
+ import {ifDefined} from "lit/directives/if-defined.js";
5
5
  import {property, state} from 'lit/decorators.js';
6
6
  import {registerCosmicBorder} from "../../internal/cosmic-border";
7
7
  import ZincElement from '../../internal/zinc-element';
@@ -1,6 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import {aTimeout, expect, fixture, html} from '@open-wc/testing';
3
- import {centre, drop, dragTo, startDrag} from '../../internal/test-drag';
3
+ import {centre, dragTo, drop, startDrag} from '../../internal/test-drag';
4
4
 
5
5
  describe('<zn-priority-list>', () => {
6
6
  it('should render a component', async () => {
@@ -4,8 +4,8 @@ import {on} from "../../utilities/on";
4
4
  import {Store} from "../../internal/storage";
5
5
  import ZincElement from '../../internal/zinc-element';
6
6
 
7
- import styles from './split-pane.scss';
8
7
  import { PropertyValues } from "@lit/reactive-element";
8
+ import styles from './split-pane.scss';
9
9
 
10
10
  type NavigationItem = {
11
11
  caption: string;
@@ -14,11 +14,11 @@ import {HasSlotController} from "../../internal/slot";
14
14
  import {ifDefined} from "lit/directives/if-defined.js";
15
15
  import {md5} from "../../utilities/md5";
16
16
  import {MutationController} from '@lit-labs/observers/mutation-controller.js';
17
+ import {onHoverIntent} from '../../internal/hover-intent';
18
+ import {pointerInitiated} from '../../internal/input-modality';
17
19
  import {property} from 'lit/decorators.js';
18
20
  import {Store} from "../../internal/storage";
19
21
  import ZincElement from '../../internal/zinc-element';
20
- import {onHoverIntent} from '../../internal/hover-intent';
21
- import {pointerInitiated} from '../../internal/input-modality';
22
22
 
23
23
  // Tabs load in such a way that the global sheet has to be re-applied inside the shadow root.
24
24
  import bootStyles from '../../../scss/boot.scss';