@ctrliq/quantic-components 1.67.3 → 1.67.5

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.
@@ -194,6 +194,70 @@
194
194
  };
195
195
  }
196
196
 
197
+ /**
198
+ * Returns true if a slot has at least one non-whitespace node assigned.
199
+ */
200
+ function slotHasContent(slot) {
201
+ return slot
202
+ .assignedNodes({ flatten: true })
203
+ .some((n) => n.nodeType !== Node.TEXT_NODE || (n.textContent?.trim() ?? '') !== '');
204
+ }
205
+ /**
206
+ * Returns the concatenated trimmed text content of all assigned nodes.
207
+ */
208
+ function slotTextContent(slot) {
209
+ return slot
210
+ .assignedNodes({ flatten: true })
211
+ .map((n) => n.textContent?.trim() ?? '')
212
+ .filter(Boolean)
213
+ .join(' ');
214
+ }
215
+ /**
216
+ * Returns a slotchange event handler that calls `setter` with whether the slot
217
+ * has non-empty content.
218
+ *
219
+ * @example
220
+ * html`<slot @slotchange=${onSlotChange((has) => (this._hasLabel = has))}></slot>`
221
+ */
222
+ function onSlotChange(setter) {
223
+ return (e) => setter(slotHasContent(e.target));
224
+ }
225
+ /**
226
+ * Returns a slotchange event handler that calls `setter` with the slot's text
227
+ * content, falling back to `''` when empty.
228
+ *
229
+ * @example
230
+ * html`<slot @slotchange=${onSlotTextChange((t) => (this._labelText = t))}></slot>`
231
+ */
232
+ function onSlotTextChange(setter) {
233
+ return (e) => setter(slotTextContent(e.target));
234
+ }
235
+ /**
236
+ * Generates a CSSResult that applies `rules` when the host has a light DOM
237
+ * child with the given slot name. Two forms are emitted for cross-browser
238
+ * Declarative Shadow DOM compatibility:
239
+ *
240
+ * - `:host(:has([slot="name"])) { rules }` — standard shadow DOM (Safari, Firefox)
241
+ * - `@scope { :scope:has([slot="name"]) { rules } }` — Chrome DSD
242
+ *
243
+ * @example
244
+ * whenSlotPresent('action-icon', css`
245
+ * .main { padding-right: calc(var(--quantic-component-card-padding) * 2); }
246
+ * `)
247
+ */
248
+ function whenSlotPresent(slotName, rules) {
249
+ const r = rules.cssText;
250
+ const name = escapeCssString(slotName);
251
+ return r$6(`:host(:has([slot="${name}"])) { ${r} } ` +
252
+ `@scope { :scope:has([slot="${name}"]) { ${r} } }`);
253
+ }
254
+ /**
255
+ * Escapes a value for safe interpolation inside a double-quoted CSS string.
256
+ */
257
+ function escapeCssString(value) {
258
+ return value.replace(/["\\]/g, '\\$&');
259
+ }
260
+
197
261
  /**
198
262
  * Mixin that automatically hides empty named slots using CSS only.
199
263
  *
@@ -226,11 +290,13 @@
226
290
  ? style.cssText
227
291
  : Array.from(style.cssRules, (rule) => rule.cssText).join('\n');
228
292
  const slotRulesCSS = namedSlots
229
- .map((name) => [
230
- `slot[name="${name}"] { display: none; }`,
231
- `:host(:has([slot="${name}"])) slot[name="${name}"] { display: revert-layer; }`,
232
- `@scope { :scope:has([slot="${name}"]) slot[name="${name}"] { display: revert-layer; } }`,
233
- ].join(' '))
293
+ .map((name) => {
294
+ const escaped = escapeCssString(name);
295
+ return [
296
+ `slot[name="${escaped}"] { display: none; }`,
297
+ whenSlotPresent(name, r$6(`slot[name="${escaped}"] { display: revert-layer; }`)).cssText,
298
+ ].join(' ');
299
+ })
234
300
  .join(' ');
235
301
  class AutoHideEmptySlotsClass extends superClass {
236
302
  static finalizeStyles(styles) {
@@ -6028,45 +6094,6 @@
6028
6094
  quanticElement('quantic-button-group-item')
6029
6095
  ], exports.ButtonGroupItem);
6030
6096
 
6031
- /**
6032
- * Returns true if a slot has at least one non-whitespace node assigned.
6033
- */
6034
- function slotHasContent(slot) {
6035
- return slot
6036
- .assignedNodes({ flatten: true })
6037
- .some((n) => n.nodeType !== Node.TEXT_NODE || (n.textContent?.trim() ?? '') !== '');
6038
- }
6039
- /**
6040
- * Returns the concatenated trimmed text content of all assigned nodes.
6041
- */
6042
- function slotTextContent(slot) {
6043
- return slot
6044
- .assignedNodes({ flatten: true })
6045
- .map((n) => n.textContent?.trim() ?? '')
6046
- .filter(Boolean)
6047
- .join(' ');
6048
- }
6049
- /**
6050
- * Returns a slotchange event handler that calls `setter` with whether the slot
6051
- * has non-empty content.
6052
- *
6053
- * @example
6054
- * html`<slot @slotchange=${onSlotChange((has) => (this._hasLabel = has))}></slot>`
6055
- */
6056
- function onSlotChange(setter) {
6057
- return (e) => setter(slotHasContent(e.target));
6058
- }
6059
- /**
6060
- * Returns a slotchange event handler that calls `setter` with the slot's text
6061
- * content, falling back to `''` when empty.
6062
- *
6063
- * @example
6064
- * html`<slot @slotchange=${onSlotTextChange((t) => (this._labelText = t))}></slot>`
6065
- */
6066
- function onSlotTextChange(setter) {
6067
- return (e) => setter(slotTextContent(e.target));
6068
- }
6069
-
6070
6097
  exports.GridDensity = void 0;
6071
6098
  (function (GridDensity) {
6072
6099
  GridDensity["Dense"] = "dense";
@@ -6101,7 +6128,6 @@
6101
6128
  this.size = exports.CardSize.Medium;
6102
6129
  this.type = 'button';
6103
6130
  this.disabled = false;
6104
- this.hasActionIconSlot = false;
6105
6131
  }
6106
6132
  render() {
6107
6133
  const classes = e$3({
@@ -6110,7 +6136,6 @@
6110
6136
  [`size--${this.size}`]: true,
6111
6137
  ['is-interactive']: this.as === 'button' || this.as === 'a',
6112
6138
  ['is-disabled']: this.disabled,
6113
- [`has-action-icon`]: this.hasActionIconSlot,
6114
6139
  });
6115
6140
  const slots = u$2 `
6116
6141
  <slot
@@ -6131,7 +6156,6 @@
6131
6156
  name="action-icon"
6132
6157
  class="action-icon"
6133
6158
  data-description="Icon in the top-right corner; only visible on interactive cards."
6134
- @slotchange=${onSlotChange((has) => (this.hasActionIconSlot = has))}
6135
6159
  ></slot>
6136
6160
  `;
6137
6161
  if (this.as === 'button') {
@@ -6173,7 +6197,7 @@
6173
6197
  ],
6174
6198
  relatedTo: ['quantic-card-group', 'quantic-panel'],
6175
6199
  };
6176
- exports.Card.styles = i$7 `
6200
+ exports.Card.styles = [i$7 `
6177
6201
  :host {
6178
6202
  display: flex;
6179
6203
  flex-direction: column;
@@ -6296,10 +6320,6 @@
6296
6320
  margin-top: auto;
6297
6321
  }
6298
6322
 
6299
- .has-action-icon {
6300
- padding-right: calc(var(--quantic-component-card-padding) * 2);
6301
- }
6302
-
6303
6323
  .action-icon {
6304
6324
  position: absolute;
6305
6325
  inset: calc(var(--quantic-component-card-padding) / 2);
@@ -6347,7 +6367,13 @@
6347
6367
  [hidden] {
6348
6368
  display: none;
6349
6369
  }
6350
- `;
6370
+ `,
6371
+ whenSlotPresent('action-icon', i$7 `
6372
+ .main {
6373
+ padding-right: calc(var(--quantic-component-card-padding) * 2);
6374
+ }
6375
+ `),
6376
+ ];
6351
6377
  __decorate([
6352
6378
  prop({
6353
6379
  type: '"div" | "button" | "a"',
@@ -6398,9 +6424,6 @@
6398
6424
  __decorate([
6399
6425
  prop({ type: 'string', description: 'Download filename (as="a" only).' })
6400
6426
  ], exports.Card.prototype, "download", void 0);
6401
- __decorate([
6402
- r$2()
6403
- ], exports.Card.prototype, "hasActionIconSlot", void 0);
6404
6427
  exports.Card = __decorate([
6405
6428
  quanticElement('quantic-card')
6406
6429
  ], exports.Card);
@@ -11763,13 +11786,12 @@
11763
11786
  constructor() {
11764
11787
  super(...arguments);
11765
11788
  this.label = '';
11766
- this.hasLabelSlot = false;
11767
11789
  }
11768
11790
  render() {
11769
- const hasLabel = this.label || this.hasLabelSlot;
11791
+ const hasLabel = !!this.label;
11770
11792
  return b `
11771
11793
  <div class="label" id="group-label" ?hidden=${!hasLabel}>
11772
- <slot name="label" @slotchange=${onSlotChange((has) => (this.hasLabelSlot = has))}>${this.label}</slot>
11794
+ <slot name="label">${this.label}</slot>
11773
11795
  </div>
11774
11796
  <div
11775
11797
  class="items"
@@ -11797,7 +11819,7 @@
11797
11819
  },
11798
11820
  relatedTo: ['quantic-dropdown-menu', 'quantic-dropdown-menu-item'],
11799
11821
  };
11800
- exports.DropdownMenuGroup.styles = i$7 `
11822
+ exports.DropdownMenuGroup.styles = [i$7 `
11801
11823
  :host {
11802
11824
  display: block;
11803
11825
  padding: var(--quantic-spacing-4) 0 var(--quantic-spacing-2);
@@ -11830,7 +11852,13 @@
11830
11852
  flex-direction: column;
11831
11853
  gap: var(--quantic-spacing-0-5);
11832
11854
  }
11833
- `;
11855
+ `,
11856
+ whenSlotPresent('label', i$7 `
11857
+ .label {
11858
+ display: block;
11859
+ }
11860
+ `),
11861
+ ];
11834
11862
  __decorate([
11835
11863
  prop({
11836
11864
  type: 'string',
@@ -11838,9 +11866,6 @@
11838
11866
  description: 'Section heading displayed above the group items.',
11839
11867
  })
11840
11868
  ], exports.DropdownMenuGroup.prototype, "label", void 0);
11841
- __decorate([
11842
- r$2()
11843
- ], exports.DropdownMenuGroup.prototype, "hasLabelSlot", void 0);
11844
11869
  exports.DropdownMenuGroup = __decorate([
11845
11870
  quanticElement('quantic-dropdown-menu-group')
11846
11871
  ], exports.DropdownMenuGroup);
@@ -11881,14 +11906,11 @@
11881
11906
  this.required = false;
11882
11907
  this.size = FieldSize.Medium;
11883
11908
  this.hintPosition = FieldHintPosition.Below;
11884
- this.hasLabelSlot = false;
11885
- this.hasHintSlot = false;
11886
- this.hasMessageSlot = false;
11887
11909
  }
11888
11910
  render() {
11889
- const hasLabel = this.hasLabelSlot || !!this.label;
11890
- const hasHint = this.hasHintSlot || !!this.hint;
11891
- const hasMessage = this.hasMessageSlot || !!this.message;
11911
+ const hasLabel = !!this.label;
11912
+ const hasHint = !!this.hint;
11913
+ const hasMessage = !!this.message;
11892
11914
  const hintTemplate = b `
11893
11915
  <div
11894
11916
  id="hint-${this.fieldId}"
@@ -11896,7 +11918,6 @@
11896
11918
  >
11897
11919
  <slot
11898
11920
  name="hint"
11899
- @slotchange=${onSlotChange((v) => (this.hasHintSlot = v))}
11900
11921
  >${this.hint}</slot>
11901
11922
  </div>
11902
11923
  `;
@@ -11908,7 +11929,6 @@
11908
11929
  >
11909
11930
  <slot
11910
11931
  name="label"
11911
- @slotchange=${onSlotChange((v) => (this.hasLabelSlot = v))}
11912
11932
  >${this.label}</slot>
11913
11933
  ${this.required
11914
11934
  ? b `<span class="required-indicator">*</span>`
@@ -11929,7 +11949,6 @@
11929
11949
  >
11930
11950
  <slot
11931
11951
  name="message"
11932
- @slotchange=${onSlotChange((v) => (this.hasMessageSlot = v))}
11933
11952
  >${this.message}</slot>
11934
11953
  </div>
11935
11954
  </div>
@@ -11997,7 +12016,7 @@
11997
12016
  color: var(--quantic-text-secondary);
11998
12017
  }
11999
12018
 
12000
- /* Sections render unconditionally so their slots exist to fire slotchange. */
12019
+ /* Sections render unconditionally; is-empty hides them when no prop or slot content is present. */
12001
12020
  .is-empty {
12002
12021
  display: none;
12003
12022
  }
@@ -12085,6 +12104,21 @@
12085
12104
  color: var(--quantic-text-warning-primary);
12086
12105
  }
12087
12106
  `,
12107
+ whenSlotPresent('label', i$7 `
12108
+ .label.is-empty {
12109
+ display: revert;
12110
+ }
12111
+ `),
12112
+ whenSlotPresent('hint', i$7 `
12113
+ .hint.is-empty {
12114
+ display: revert;
12115
+ }
12116
+ `),
12117
+ whenSlotPresent('message', i$7 `
12118
+ .message.is-empty {
12119
+ display: revert;
12120
+ }
12121
+ `),
12088
12122
  ];
12089
12123
  __decorate([
12090
12124
  prop({ type: 'string', default: "''", description: 'ID linking the label and hint/message elements to the control for accessibility.' })
@@ -12110,15 +12144,6 @@
12110
12144
  __decorate([
12111
12145
  prop({ type: 'FieldHintPosition', options: Object.values(FieldHintPosition), default: FieldHintPosition.Below, attribute: 'hint-position', description: 'Position of the hint relative to the control.' })
12112
12146
  ], exports.Field.prototype, "hintPosition", void 0);
12113
- __decorate([
12114
- r$2()
12115
- ], exports.Field.prototype, "hasLabelSlot", void 0);
12116
- __decorate([
12117
- r$2()
12118
- ], exports.Field.prototype, "hasHintSlot", void 0);
12119
- __decorate([
12120
- r$2()
12121
- ], exports.Field.prototype, "hasMessageSlot", void 0);
12122
12147
  exports.Field = __decorate([
12123
12148
  quanticElement(`quantic-field`)
12124
12149
  ], exports.Field);
@@ -62139,7 +62164,6 @@
62139
62164
  this.label = '';
62140
62165
  this.active = false;
62141
62166
  this.invalid = false;
62142
- this.hasLabelSlot = false;
62143
62167
  this.handleActivateClick = () => {
62144
62168
  if (!this.active)
62145
62169
  this.activate();
@@ -62197,7 +62221,7 @@
62197
62221
  }
62198
62222
  }
62199
62223
  render() {
62200
- const hasLabel = this.hasLabelSlot || !!this.label;
62224
+ const hasLabel = !!this.label;
62201
62225
  return b `
62202
62226
  <div
62203
62227
  class="${e$3({
@@ -62210,7 +62234,7 @@
62210
62234
  @keydown=${this.handleTriggerKey}
62211
62235
  >
62212
62236
  <span class=${e$3({ label: true, 'is-empty': !hasLabel })}
62213
- ><slot name="label" @slotchange=${onSlotChange((has) => (this.hasLabelSlot = has))}>${this.label}</slot></span
62237
+ ><slot name="label">${this.label}</slot></span
62214
62238
  >
62215
62239
  <span class="value">
62216
62240
  <span class="display">
@@ -62240,7 +62264,7 @@
62240
62264
  editor: { description: 'Input element shown when the field is active.' },
62241
62265
  },
62242
62266
  };
62243
- exports.InlineEdit.styles = i$7 `
62267
+ exports.InlineEdit.styles = [i$7 `
62244
62268
  :host {
62245
62269
  display: block;
62246
62270
  padding: var(
@@ -62284,7 +62308,6 @@
62284
62308
  );
62285
62309
  }
62286
62310
 
62287
- /* The label renders unconditionally so its slot exists to fire slotchange. */
62288
62311
  .label.is-empty {
62289
62312
  display: none;
62290
62313
  }
@@ -62354,7 +62377,13 @@
62354
62377
  .is-active .editor {
62355
62378
  display: block;
62356
62379
  }
62357
- `;
62380
+ `,
62381
+ whenSlotPresent('label', i$7 `
62382
+ .label.is-empty {
62383
+ display: revert;
62384
+ }
62385
+ `),
62386
+ ];
62358
62387
  __decorate([
62359
62388
  prop({ type: 'string', default: '', description: 'Fallback label text when no label slot content is provided.' })
62360
62389
  ], exports.InlineEdit.prototype, "label", void 0);
@@ -62364,9 +62393,6 @@
62364
62393
  __decorate([
62365
62394
  prop({ type: 'boolean', default: 'false', description: 'When true, focusout does not close the editor. The editor stays open until the value is valid or the user cancels.' })
62366
62395
  ], exports.InlineEdit.prototype, "invalid", void 0);
62367
- __decorate([
62368
- r$2()
62369
- ], exports.InlineEdit.prototype, "hasLabelSlot", void 0);
62370
62396
  exports.InlineEdit = __decorate([
62371
62397
  event('cancel', { description: 'Fired on Escape. Consumer should revert optimistic state.' }),
62372
62398
  quanticElement('quantic-inline-edit')
@@ -63559,6 +63585,10 @@
63559
63585
  }
63560
63586
  connectedCallback() {
63561
63587
  super.connectedCallback();
63588
+ if (this.hasAttribute('ssr-has-sidebar')) {
63589
+ this.hasSidebarSlot = true;
63590
+ this.removeAttribute('ssr-has-sidebar');
63591
+ }
63562
63592
  document.addEventListener('keydown', this.handleKeydown);
63563
63593
  }
63564
63594
  disconnectedCallback() {
@@ -63585,16 +63615,60 @@
63585
63615
  ...base,
63586
63616
  r$6(`@layer quantic-header-visibility {
63587
63617
  .header { display: none; }
63588
- .header.collapsible,
63589
- :host(:has([slot="navbar"])) .header,
63590
- :host(:has([slot="titlebar"])) .header,
63591
- :host(:has([slot="logo"])) .header { display: revert-layer; }
63592
- @scope {
63593
- :scope:has([slot="navbar"]) .header,
63594
- :scope:has([slot="titlebar"]) .header,
63595
- :scope:has([slot="logo"]) .header { display: revert-layer; }
63618
+ .header.collapsible { display: revert-layer; }
63619
+ }`),
63620
+ whenSlotPresent('navbar', i$7 `
63621
+ @layer quantic-header-visibility {
63622
+ .header {
63623
+ display: revert-layer;
63624
+ }
63625
+ }
63626
+ `),
63627
+ whenSlotPresent('titlebar', i$7 `
63628
+ @layer quantic-header-visibility {
63629
+ .header {
63630
+ display: revert-layer;
63631
+ }
63596
63632
  }
63633
+ `),
63634
+ r$6(`@layer quantic-header-visibility {
63635
+ :host(:has([slot="logo"]):not(:has([slot="sidebar"]))) .header { display: revert-layer; }
63636
+ @scope { :scope:has([slot="logo"]):not(:has([slot="sidebar"])) .header { display: revert-layer; } }
63597
63637
  }`),
63638
+ whenSlotPresent('sidebar', i$7 `
63639
+ .aside { display: flex; }
63640
+ @media only screen and (min-width: 1024px) {
63641
+ .wrapper {
63642
+ display: grid;
63643
+ grid-template-areas: 'aside header' 'aside toolbar' 'aside main';
63644
+ grid-template-rows: auto auto 1fr;
63645
+ grid-template-columns: var(--quantic-component-layout-aside-width-collapsed) 1fr;
63646
+ }
63647
+ .is-aside-expanded.wrapper {
63648
+ grid-template-columns: var(--quantic-component-layout-aside-width) 1fr;
63649
+ }
63650
+ .aside {
63651
+ grid-area: aside;
63652
+ position: sticky;
63653
+ top: 0;
63654
+ align-self: start;
63655
+ transform: translateX(0);
63656
+ opacity: 1;
63657
+ pointer-events: auto;
63658
+ }
63659
+ .is-aside-expanded.wrapper .aside,
63660
+ .is-aside-expanded.wrapper .logo {
63661
+ width: var(--quantic-component-layout-aside-width);
63662
+ }
63663
+ .wrapper:not(.is-aside-expanded) .aside,
63664
+ .wrapper:not(.is-aside-expanded) .logo {
63665
+ width: var(--quantic-component-layout-aside-width-collapsed);
63666
+ padding-inline: 0;
63667
+ }
63668
+ .toolbar { grid-area: toolbar; min-width: 0; }
63669
+ .body { grid-area: main; min-width: 0; }
63670
+ }
63671
+ `),
63598
63672
  ];
63599
63673
  }
63600
63674
  get isAsideCollapsible() {
@@ -63662,7 +63736,6 @@
63662
63736
 
63663
63737
  <aside
63664
63738
  class="aside"
63665
- ?hidden=${!this.hasSidebar}
63666
63739
  tabindex="-1"
63667
63740
  ?inert=${this.isSmallViewport && !this.isAsideExpanded}
63668
63741
  >
@@ -63803,7 +63876,7 @@
63803
63876
  padding-block: var(--quantic-component-layout-aside-padding-block);
63804
63877
  border-right: 1px solid var(--quantic-component-layout-aside-border);
63805
63878
  overflow: visible;
63806
- display: flex;
63879
+ display: none;
63807
63880
  flex-direction: column;
63808
63881
  gap: var(--quantic-component-layout-aside-padding-block);
63809
63882
  transform: translateX(
@@ -67305,7 +67378,7 @@
67305
67378
  prop({ type: 'TableVariant', options: Object.values(exports.TableVariant), default: exports.TableVariant.Outline, reflect: true, description: 'Visual style. "outline" adds row borders; "striped" alternates row backgrounds; "minimal" removes borders.' })
67306
67379
  ], exports.Table.prototype, "variant", void 0);
67307
67380
  __decorate([
67308
- prop({ type: 'boolean', default: 'false', description: 'Reduces cell padding for compact display.' })
67381
+ prop({ type: 'boolean', default: 'false', reflect: true, description: 'Reduces cell padding for compact display.' })
67309
67382
  ], exports.Table.prototype, "dense", void 0);
67310
67383
  __decorate([
67311
67384
  prop({ type: 'string', attribute: 'aria-label', description: 'Accessible label for the table.' })