@reliability-design/web 1.3.0 → 1.3.1

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/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  };
11
11
 
12
12
  // src/index.ts
13
- import "./vars-I2PC74XD.css";
13
+ import "./vars-F54UGCHM.css";
14
14
 
15
15
  // src/components/rel-accordion/rel-accordion.ts
16
16
  import "./vars-v2-VMGWTFJZ.css";
@@ -445,7 +445,7 @@ RelAlert.styles = css2`
445
445
 
446
446
  .rel-alert {
447
447
  display: flex;
448
- align-items: center;
448
+ align-items: flex-start;
449
449
  gap: var(--rel-spacing-space-xs);
450
450
  padding: var(--rel-spacing-space-sm) var(--rel-spacing-space-md);
451
451
  border-radius: var(--rel-borders-radius-border-radius-200);
@@ -471,9 +471,15 @@ RelAlert.styles = css2`
471
471
  opacity: 0.9;
472
472
  }
473
473
 
474
+ .rel-icon {
475
+ margin-top: 2px;
476
+ flex-shrink: 0;
477
+ }
478
+
474
479
  .rel-icon svg {
475
480
  width: 16px;
476
481
  height: 16px;
482
+ display: block;
477
483
  }
478
484
 
479
485
  .rel-link {
@@ -486,6 +492,7 @@ RelAlert.styles = css2`
486
492
 
487
493
  .rel-close {
488
494
  margin-left: auto;
495
+ align-self: flex-start;
489
496
  background: none;
490
497
  border: none;
491
498
  cursor: pointer;
@@ -835,46 +842,63 @@ RelBadge.styles = css4`
835
842
  display: inline-flex;
836
843
  align-items: center;
837
844
  justify-content: center;
845
+ box-sizing: border-box;
838
846
  font-family: var(--rel-fontFamilies-primary, sans-serif);
839
847
  font-weight: var(--rel-fontWeights-semibold, 600);
840
848
  background-color: var(--rel-badge-bg, var(--rel-error-surface-default));
841
849
  color: var(--rel-badge-color, #ffffff);
842
- border-radius: 50%;
850
+ /* Large fixed radius (not 50%) — stays a perfect circle for 1-2 digits,
851
+ but becomes a pill instead of clipping when content like "999+"
852
+ is wider than the minimum circle. */
853
+ border-radius: 999px;
854
+ white-space: nowrap;
843
855
  }
844
856
 
845
- /* NUMBER BADGES */
857
+ /* NUMBER BADGES — min-width (not width) lets multi-character content
858
+ like "999+" grow the badge into a pill instead of overflowing a fixed circle. */
846
859
  .badge-sm {
847
- width: 20px;
860
+ min-width: 20px;
848
861
  height: 20px;
862
+ padding: 0 4px;
849
863
  font-size: var(--rel-fontSizes-xs, 12px);
850
864
  }
851
865
 
852
866
  .badge-md {
853
- width: 28px;
867
+ min-width: 28px;
854
868
  height: 28px;
869
+ padding: 0 6px;
855
870
  font-size: var(--rel-fontSizes-sm, 14px);
856
871
  }
857
872
 
858
873
  .badge-lg {
859
- width: 40px;
874
+ min-width: 40px;
860
875
  height: 40px;
876
+ padding: 0 8px;
861
877
  font-size: var(--rel-fontSizes-lg, 20px);
862
878
  }
863
879
 
864
- /* DOT BADGES */
880
+ /* DOT BADGES — explicitly reset min-width/padding inherited from the
881
+ number-badge size rules above; otherwise min-width wins over this
882
+ smaller width and the dot gets stretched into a squashed pill. */
865
883
  .badge-dot.badge-sm {
866
884
  width: 8px;
867
885
  height: 8px;
886
+ min-width: 0;
887
+ padding: 0;
868
888
  }
869
889
 
870
890
  .badge-dot.badge-md {
871
891
  width: 10px;
872
892
  height: 10px;
893
+ min-width: 0;
894
+ padding: 0;
873
895
  }
874
896
 
875
897
  .badge-dot.badge-lg {
876
898
  width: 12px;
877
899
  height: 12px;
900
+ min-width: 0;
901
+ padding: 0;
878
902
  }
879
903
  `;
880
904
  __decorateClass([
@@ -1015,6 +1039,8 @@ var RelButton = class extends LitElement6 {
1015
1039
  this.border = "corner-radius";
1016
1040
  this.disabled = false;
1017
1041
  this.ariaLabel = null;
1042
+ this.badge = "";
1043
+ this.badgeDot = false;
1018
1044
  this.handleHostClick = (e) => {
1019
1045
  if (this.disabled) {
1020
1046
  e.preventDefault();
@@ -1046,6 +1072,7 @@ var RelButton = class extends LitElement6 {
1046
1072
  }
1047
1073
  }
1048
1074
  render() {
1075
+ const showBadge = this.badgeDot || !!this.badge;
1049
1076
  return html6`
1050
1077
  <button
1051
1078
  class="${this.classes}"
@@ -1056,12 +1083,36 @@ var RelButton = class extends LitElement6 {
1056
1083
  >
1057
1084
  <slot></slot>
1058
1085
  </button>
1086
+ ${showBadge ? html6`<rel-badge
1087
+ class="btn-badge"
1088
+ variant=${this.badgeDot ? "dot" : "number"}
1089
+ number=${this.badge}
1090
+ size="sm"
1091
+ ></rel-badge>` : null}
1059
1092
  `;
1060
1093
  }
1061
1094
  };
1062
1095
  RelButton.styles = css6`
1063
1096
  :host {
1064
1097
  display: inline-flex;
1098
+ position: relative;
1099
+ }
1100
+
1101
+ .btn-badge {
1102
+ position: absolute;
1103
+ top: -6px;
1104
+ right: -6px;
1105
+ pointer-events: none;
1106
+ }
1107
+
1108
+ /* Icon buttons are a 40px circle (radius 20). An 8px dot's center needs to
1109
+ land ON that circle's edge at the 45° corner point — which works out to
1110
+ roughly 2px inward from each edge (R - R/√2 - dot-radius ≈ 1.9px), not
1111
+ outside it like the numbered badge. Negative offsets here would float
1112
+ the dot in the empty bounding-box corner outside the visible circle. */
1113
+ .btn-badge[variant="dot"] {
1114
+ top: 2px;
1115
+ right: 2px;
1065
1116
  }
1066
1117
 
1067
1118
  button {
@@ -1302,6 +1353,12 @@ __decorateClass([
1302
1353
  __decorateClass([
1303
1354
  property6({ type: String, attribute: "aria-label" })
1304
1355
  ], RelButton.prototype, "ariaLabel", 2);
1356
+ __decorateClass([
1357
+ property6({ type: String })
1358
+ ], RelButton.prototype, "badge", 2);
1359
+ __decorateClass([
1360
+ property6({ type: Boolean, attribute: "badge-dot" })
1361
+ ], RelButton.prototype, "badgeDot", 2);
1305
1362
  RelButton = __decorateClass([
1306
1363
  customElement6("rel-button")
1307
1364
  ], RelButton);
@@ -2738,6 +2795,9 @@ var RelDatePicker = class extends LitElement12 {
2738
2795
  this.label = null;
2739
2796
  this.disabled = false;
2740
2797
  this.required = false;
2798
+ this.readonly = false;
2799
+ this.minDate = null;
2800
+ this.maxDate = null;
2741
2801
  this.min = null;
2742
2802
  this.max = null;
2743
2803
  this.weekStart = "sun";
@@ -2745,6 +2805,10 @@ var RelDatePicker = class extends LitElement12 {
2745
2805
  this.open = false;
2746
2806
  this.closeOnSelect = true;
2747
2807
  this.clearable = false;
2808
+ this.error = false;
2809
+ this.size = "medium";
2810
+ this.variant = "outlined";
2811
+ this.fullWidth = false;
2748
2812
  this.viewYear = (/* @__PURE__ */ new Date()).getFullYear();
2749
2813
  this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
2750
2814
  this.focusedDate = null;
@@ -2763,6 +2827,13 @@ var RelDatePicker = class extends LitElement12 {
2763
2827
  }
2764
2828
  };
2765
2829
  }
2830
+ // minDate/maxDate supersede the deprecated min/max aliases
2831
+ get minBound() {
2832
+ return this.minDate ?? this.min;
2833
+ }
2834
+ get maxBound() {
2835
+ return this.maxDate ?? this.max;
2836
+ }
2766
2837
  connectedCallback() {
2767
2838
  super.connectedCallback();
2768
2839
  if (this.value) {
@@ -2823,12 +2894,12 @@ var RelDatePicker = class extends LitElement12 {
2823
2894
  }
2824
2895
  isDateDisabledInternal(date) {
2825
2896
  if (this.disabled) return true;
2826
- if (this.min) {
2827
- const minDate = this.parseISODate(this.min);
2897
+ if (this.minBound) {
2898
+ const minDate = this.parseISODate(this.minBound);
2828
2899
  if (minDate && date < minDate) return true;
2829
2900
  }
2830
- if (this.max) {
2831
- const maxDate = this.parseISODate(this.max);
2901
+ if (this.maxBound) {
2902
+ const maxDate = this.parseISODate(this.maxBound);
2832
2903
  if (maxDate && date > maxDate) return true;
2833
2904
  }
2834
2905
  if (this.isDateDisabled && this.isDateDisabled(date)) {
@@ -2913,12 +2984,12 @@ var RelDatePicker = class extends LitElement12 {
2913
2984
  const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
2914
2985
  let startYear = currentYear - 100;
2915
2986
  let endYear = currentYear + 10;
2916
- if (this.min) {
2917
- const minDate = this.parseISODate(this.min);
2987
+ if (this.minBound) {
2988
+ const minDate = this.parseISODate(this.minBound);
2918
2989
  if (minDate) startYear = Math.max(startYear, minDate.getFullYear() - 5);
2919
2990
  }
2920
- if (this.max) {
2921
- const maxDate = this.parseISODate(this.max);
2991
+ if (this.maxBound) {
2992
+ const maxDate = this.parseISODate(this.maxBound);
2922
2993
  if (maxDate) endYear = Math.min(endYear, maxDate.getFullYear() + 5);
2923
2994
  }
2924
2995
  const years = [];
@@ -2928,7 +2999,7 @@ var RelDatePicker = class extends LitElement12 {
2928
2999
  return years.reverse();
2929
3000
  }
2930
3001
  toggleCalendarPopover() {
2931
- if (this.disabled) return;
3002
+ if (this.disabled || this.readonly) return;
2932
3003
  this.open = !this.open;
2933
3004
  this.dispatchEvent(
2934
3005
  new CustomEvent("rel-open-change", {
@@ -2968,7 +3039,7 @@ var RelDatePicker = class extends LitElement12 {
2968
3039
  }
2969
3040
  }
2970
3041
  selectDay(day) {
2971
- if (day.isDisabled || this.disabled) return;
3042
+ if (day.isDisabled || this.disabled || this.readonly) return;
2972
3043
  const newValue = this.formatISODate(day.date);
2973
3044
  this.value = newValue;
2974
3045
  this.dispatchEvent(
@@ -3102,12 +3173,25 @@ var RelDatePicker = class extends LitElement12 {
3102
3173
  const weekdayLabels = this.getWeekdayLabels();
3103
3174
  const months = this.getMonthsList();
3104
3175
  const years = this.getYearsList();
3176
+ const isError = this.error;
3177
+ const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
3178
+ const describedById = belowText ? "date-picker-desc" : void 0;
3179
+ const wrapperClasses = [
3180
+ "date-picker-wrapper",
3181
+ this.size,
3182
+ isError ? "error" : "",
3183
+ this.readonly ? "readonly" : ""
3184
+ ].filter(Boolean).join(" ");
3105
3185
  return html12`
3106
- <div class="date-picker-wrapper">
3107
- ${this.label ? html12`<label class="label" id="date-picker-label">${this.label}</label>` : nothing3}
3186
+ <div class="${wrapperClasses}">
3187
+ ${this.label ? html12`<label class="label" id="date-picker-label"
3188
+ >${this.label}${this.required ? html12`<span class="required-mark" aria-hidden="true">*</span>` : nothing3}</label
3189
+ >` : nothing3}
3190
+
3191
+ ${this.name ? html12`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` : nothing3}
3108
3192
 
3109
3193
  <button
3110
- class="trigger-button"
3194
+ class="trigger-button ${isError ? "error" : ""}"
3111
3195
  type="button"
3112
3196
  ?disabled=${this.disabled}
3113
3197
  @click=${this.toggleCalendarPopover}
@@ -3116,6 +3200,9 @@ var RelDatePicker = class extends LitElement12 {
3116
3200
  aria-haspopup="dialog"
3117
3201
  aria-expanded=${this.open}
3118
3202
  aria-required=${this.required}
3203
+ aria-invalid=${isError}
3204
+ aria-readonly=${this.readonly}
3205
+ aria-describedby=${describedById ?? nothing3}
3119
3206
  aria-labelledby=${this.label ? "date-picker-label" : nothing3}
3120
3207
  >
3121
3208
  <span class="trigger-content ${this.value ? "" : "placeholder"}">
@@ -3264,17 +3351,27 @@ var RelDatePicker = class extends LitElement12 {
3264
3351
  )}
3265
3352
  </div>
3266
3353
  </div>
3354
+
3355
+ ${belowText ? html12`<div class="date-picker-support ${isError ? "error" : ""}" id=${describedById}>
3356
+ ${belowText}
3357
+ </div>` : nothing3}
3267
3358
  </div>
3268
3359
  `;
3269
3360
  }
3270
3361
  };
3271
3362
  RelDatePicker.styles = css12`
3272
3363
  :host {
3273
- display: inline-block;
3364
+ display: block;
3274
3365
  font-family: var(--rel-fontFamilies-primary);
3275
3366
  position: relative;
3276
3367
  }
3277
3368
 
3369
+ /* Intrinsic width by default; set fullWidth to fill the parent. */
3370
+ :host(:not([fullwidth])) {
3371
+ display: inline-block;
3372
+ width: auto;
3373
+ }
3374
+
3278
3375
  :host([disabled]) {
3279
3376
  opacity: 0.6;
3280
3377
  cursor: not-allowed;
@@ -3284,6 +3381,10 @@ RelDatePicker.styles = css12`
3284
3381
  pointer-events: none;
3285
3382
  }
3286
3383
 
3384
+ :host([readonly]) .trigger-button {
3385
+ cursor: default;
3386
+ }
3387
+
3287
3388
  .date-picker-wrapper {
3288
3389
  display: flex;
3289
3390
  flex-direction: column;
@@ -3296,6 +3397,21 @@ RelDatePicker.styles = css12`
3296
3397
  color: var(--rel-text-heading);
3297
3398
  }
3298
3399
 
3400
+ .required-mark {
3401
+ color: var(--rel-error-surface-default, #cc0000);
3402
+ margin-left: 2px;
3403
+ }
3404
+
3405
+ /* Support text (helperText / errorMessage) */
3406
+ .date-picker-support {
3407
+ font-size: var(--rel-fontSizes-xs);
3408
+ color: var(--rel-color-text-placeholder);
3409
+ }
3410
+
3411
+ .date-picker-support.error {
3412
+ color: var(--rel-error-surface-default, #cc0000);
3413
+ }
3414
+
3299
3415
  .trigger-button {
3300
3416
  width: 100%;
3301
3417
  min-width: 200px;
@@ -3330,6 +3446,28 @@ RelDatePicker.styles = css12`
3330
3446
  background: var(--rel-surface-secondary);
3331
3447
  }
3332
3448
 
3449
+ /* Error state */
3450
+ .trigger-button.error,
3451
+ .trigger-button.error:hover:not(:disabled) {
3452
+ border-color: var(--rel-error-surface-default, #cc0000);
3453
+ }
3454
+
3455
+ .trigger-button.error:focus-visible {
3456
+ outline-color: var(--rel-error-surface-default, #cc0000);
3457
+ border-color: var(--rel-error-surface-default, #cc0000);
3458
+ }
3459
+
3460
+ /* Size variants (medium is the base .trigger-button style) */
3461
+ .date-picker-wrapper.small .trigger-button {
3462
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
3463
+ font-size: var(--rel-fontSizes-sm);
3464
+ }
3465
+
3466
+ .date-picker-wrapper.large .trigger-button {
3467
+ padding: var(--rel-spacing-space-md) var(--rel-spacing-space-md);
3468
+ font-size: var(--rel-fontSizes-lg);
3469
+ }
3470
+
3333
3471
  .trigger-content {
3334
3472
  flex: 1;
3335
3473
  color: var(--rel-text-primary);
@@ -3580,6 +3718,15 @@ __decorateClass([
3580
3718
  __decorateClass([
3581
3719
  property11({ type: Boolean })
3582
3720
  ], RelDatePicker.prototype, "required", 2);
3721
+ __decorateClass([
3722
+ property11({ type: Boolean, reflect: true })
3723
+ ], RelDatePicker.prototype, "readonly", 2);
3724
+ __decorateClass([
3725
+ property11({ type: String, attribute: "min-date" })
3726
+ ], RelDatePicker.prototype, "minDate", 2);
3727
+ __decorateClass([
3728
+ property11({ type: String, attribute: "max-date" })
3729
+ ], RelDatePicker.prototype, "maxDate", 2);
3583
3730
  __decorateClass([
3584
3731
  property11({ type: String })
3585
3732
  ], RelDatePicker.prototype, "min", 2);
@@ -3601,6 +3748,27 @@ __decorateClass([
3601
3748
  __decorateClass([
3602
3749
  property11({ type: Boolean })
3603
3750
  ], RelDatePicker.prototype, "clearable", 2);
3751
+ __decorateClass([
3752
+ property11({ type: Boolean })
3753
+ ], RelDatePicker.prototype, "error", 2);
3754
+ __decorateClass([
3755
+ property11({ type: String, attribute: "error-message" })
3756
+ ], RelDatePicker.prototype, "errorMessage", 2);
3757
+ __decorateClass([
3758
+ property11({ type: String, attribute: "helper-text" })
3759
+ ], RelDatePicker.prototype, "helperText", 2);
3760
+ __decorateClass([
3761
+ property11({ type: String })
3762
+ ], RelDatePicker.prototype, "name", 2);
3763
+ __decorateClass([
3764
+ property11({ type: String })
3765
+ ], RelDatePicker.prototype, "size", 2);
3766
+ __decorateClass([
3767
+ property11({ type: String, reflect: true })
3768
+ ], RelDatePicker.prototype, "variant", 2);
3769
+ __decorateClass([
3770
+ property11({ type: Boolean, reflect: true })
3771
+ ], RelDatePicker.prototype, "fullWidth", 2);
3604
3772
  __decorateClass([
3605
3773
  state3()
3606
3774
  ], RelDatePicker.prototype, "viewYear", 2);
@@ -13379,6 +13547,7 @@ var RelTabItem = class extends LitElement51 {
13379
13547
  this.label = "";
13380
13548
  this.value = "";
13381
13549
  this.badge = 0;
13550
+ this.badgeDot = false;
13382
13551
  this.disabled = false;
13383
13552
  }
13384
13553
  render() {
@@ -13394,6 +13563,9 @@ __decorateClass([
13394
13563
  __decorateClass([
13395
13564
  property44({ type: Number })
13396
13565
  ], RelTabItem.prototype, "badge", 2);
13566
+ __decorateClass([
13567
+ property44({ type: Boolean, attribute: "badge-dot" })
13568
+ ], RelTabItem.prototype, "badgeDot", 2);
13397
13569
  __decorateClass([
13398
13570
  property44({ type: Boolean })
13399
13571
  ], RelTabItem.prototype, "disabled", 2);
@@ -13416,6 +13588,7 @@ var RelTab = class extends LitElement51 {
13416
13588
  return this._tabItems.map((item) => ({
13417
13589
  label: item.label,
13418
13590
  badge: item.badge || 0,
13591
+ badgeDot: item.badgeDot,
13419
13592
  disabled: item.disabled
13420
13593
  }));
13421
13594
  }
@@ -13472,7 +13645,7 @@ var RelTab = class extends LitElement51 {
13472
13645
  @keydown="${(e) => this.onKeyDown(e, i)}"
13473
13646
  >
13474
13647
  <span class="tab-label">${tab.label}</span>
13475
- ${tab.badge ? html51`<rel-badge number="${tab.badge}" variant="number" size="sm"></rel-badge>` : ""}
13648
+ ${tab.badgeDot ? html51`<rel-badge variant="dot" size="sm"></rel-badge>` : tab.badge ? html51`<rel-badge number="${tab.badge}" variant="number" size="sm"></rel-badge>` : ""}
13476
13649
  <span class="tab-underline"></span>
13477
13650
  </div>
13478
13651
  `
@@ -13569,7 +13742,7 @@ RelTab = __decorateClass([
13569
13742
  ], RelTab);
13570
13743
 
13571
13744
  // src/components/rel-textfield/rel-textfield.ts
13572
- import { LitElement as LitElement52, html as html52, css as css49 } from "lit";
13745
+ import { LitElement as LitElement52, html as html52, css as css49, nothing as nothing6 } from "lit";
13573
13746
  import { customElement as customElement52, property as property45, state as state19 } from "lit/decorators.js";
13574
13747
  import "./vars-v2-VMGWTFJZ.css";
13575
13748
  import "@reliability-design/icons/rel-icon";
@@ -13583,10 +13756,31 @@ var RelTextfield = class extends LitElement52 {
13583
13756
  this.size = "medium";
13584
13757
  this.floatingLabel = false;
13585
13758
  this.variant = "default";
13759
+ this.disabled = false;
13760
+ this.required = false;
13761
+ this.multiline = false;
13762
+ this.rows = 3;
13763
+ this.error = false;
13764
+ this.readonly = false;
13765
+ this.fullWidth = true;
13586
13766
  this.internalState = "default";
13587
13767
  this._wasError = false;
13588
13768
  this.uniqueId = `textfield-${Math.random().toString(36).slice(2)}`;
13589
13769
  }
13770
+ get isDisabled() {
13771
+ return this.disabled || this.state === "disabled";
13772
+ }
13773
+ get isError() {
13774
+ return this.error || this.state === "error";
13775
+ }
13776
+ // prefixIcon supersedes the deprecated leftIcon
13777
+ get leadingIcon() {
13778
+ return this.prefixIcon ?? this.leftIcon;
13779
+ }
13780
+ // helperText supersedes the deprecated supportingText
13781
+ get helper() {
13782
+ return this.helperText ?? this.supportingText;
13783
+ }
13590
13784
  connectedCallback() {
13591
13785
  super.connectedCallback();
13592
13786
  const leftIcon = this.getAttribute("lefticon") || this.getAttribute("leftIcon");
@@ -13595,7 +13789,7 @@ var RelTextfield = class extends LitElement52 {
13595
13789
  if (rightIcon) this.rightIcon = rightIcon;
13596
13790
  }
13597
13791
  handleFocus() {
13598
- if (this.state !== "disabled") {
13792
+ if (!this.isDisabled) {
13599
13793
  this.internalState = "focused";
13600
13794
  }
13601
13795
  }
@@ -13623,51 +13817,108 @@ var RelTextfield = class extends LitElement52 {
13623
13817
  );
13624
13818
  }
13625
13819
  render() {
13820
+ const isError = this.isError;
13821
+ const stateClass = this.isDisabled ? "disabled" : isError ? "error" : "default";
13822
+ const leadingIcon = this.leadingIcon;
13823
+ const inputId = this.id || this.uniqueId;
13824
+ const legendId = `${inputId}-legend`;
13825
+ const belowText = isError && this.errorMessage ? this.errorMessage : this.helper;
13826
+ const describedById = belowText ? `${inputId}-desc` : void 0;
13626
13827
  const wrapperClasses = [
13627
13828
  "textfield-wrapper",
13628
13829
  this.size,
13629
- this.state,
13830
+ stateClass,
13831
+ this.readonly ? "readonly" : "",
13630
13832
  this.internalState === "focused" ? "focused" : "",
13631
13833
  this.value ? "has-value" : ""
13632
13834
  ].filter(Boolean).join(" ");
13633
13835
  const inputClasses = [
13634
13836
  "textfield-input",
13635
- this.leftIcon ? "has-left-icon" : "",
13636
- this.rightIcon ? "has-right-icon" : ""
13837
+ !this.multiline && leadingIcon ? "has-left-icon" : "",
13838
+ !this.multiline && this.rightIcon && this.variant !== "legend" ? "has-right-icon" : ""
13637
13839
  ].filter(Boolean).join(" ");
13840
+ const labelTemplate = (extraClass = "") => {
13841
+ if (!this.label) return "";
13842
+ return html52`<label
13843
+ class="textfield-label${extraClass}"
13844
+ for="${inputId}"
13845
+ >${this.label}${this.required ? html52`<span class="required-mark" aria-hidden="true">*</span>` : ""}</label>`;
13846
+ };
13847
+ const ariaLabelledby = this.variant === "legend" && this.label ? legendId : void 0;
13848
+ const inputTemplate = this.multiline ? html52`<textarea
13849
+ id="${inputId}"
13850
+ class="${inputClasses}"
13851
+ placeholder="${this.placeholder}"
13852
+ .value="${this.value}"
13853
+ rows="${this.rows}"
13854
+ name="${this.name ?? nothing6}"
13855
+ maxlength="${this.maxLength ?? nothing6}"
13856
+ ?disabled="${this.isDisabled}"
13857
+ ?readonly="${this.readonly}"
13858
+ ?required="${this.required}"
13859
+ aria-invalid="${isError}"
13860
+ aria-required="${this.required}"
13861
+ aria-labelledby="${ariaLabelledby ?? nothing6}"
13862
+ aria-describedby="${describedById ?? nothing6}"
13863
+ @focus="${this.handleFocus}"
13864
+ @blur="${this.handleBlur}"
13865
+ @input="${this.handleInput}"
13866
+ ></textarea>` : html52`<input
13867
+ type="${this.type}"
13868
+ id="${inputId}"
13869
+ class="${inputClasses}"
13870
+ placeholder="${this.placeholder}"
13871
+ .value="${this.value}"
13872
+ name="${this.name ?? nothing6}"
13873
+ maxlength="${this.maxLength ?? nothing6}"
13874
+ ?disabled="${this.isDisabled}"
13875
+ ?readonly="${this.readonly}"
13876
+ ?required="${this.required}"
13877
+ aria-invalid="${isError}"
13878
+ aria-required="${this.required}"
13879
+ aria-labelledby="${ariaLabelledby ?? nothing6}"
13880
+ aria-describedby="${describedById ?? nothing6}"
13881
+ @focus="${this.handleFocus}"
13882
+ @blur="${this.handleBlur}"
13883
+ @input="${this.handleInput}"
13884
+ />`;
13885
+ const leadingIconTemplate = leadingIcon ? html52`<rel-icon
13886
+ name="${leadingIcon}"
13887
+ size="sm"
13888
+ class="icon left"
13889
+ role="${this.prefixIconAriaLabel ? "img" : nothing6}"
13890
+ aria-label="${this.prefixIconAriaLabel ?? nothing6}"
13891
+ aria-hidden="${this.prefixIconAriaLabel ? nothing6 : "true"}"
13892
+ ></rel-icon>` : "";
13893
+ const belowTemplate = belowText ? html52`<div class="textfield-support ${isError ? "error" : ""}" id="${describedById}">
13894
+ ${belowText}
13895
+ </div>` : "";
13896
+ if (this.variant === "legend") {
13897
+ const fieldsetClass = ["field-fieldset", this.multiline ? "multiline" : ""].filter(Boolean).join(" ");
13898
+ return html52`
13899
+ <div class="${wrapperClasses}">
13900
+ <fieldset class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${this.label ?? ""}${this.required ? html52`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
13901
+ ${!this.multiline ? leadingIconTemplate : ""}
13902
+ ${inputTemplate}
13903
+ </fieldset>
13904
+ ${belowTemplate}
13905
+ </div>
13906
+ `;
13907
+ }
13638
13908
  return html52`
13639
13909
  <div class="${wrapperClasses}">
13640
- ${!this.floatingLabel && this.label ? html52`<label class="textfield-label" for="${this.uniqueId}">${this.label}</label>` : ""}
13910
+ ${!this.floatingLabel ? labelTemplate() : ""}
13641
13911
 
13642
- <div
13643
- class="textfield-container ${this.state === "disabled" ? "disabled" : ""}"
13644
- >
13645
- ${this.floatingLabel && this.label ? html52`<label
13646
- class="textfield-label${this.leftIcon ? " has-left-icon" : ""}"
13647
- for="${this.uniqueId}">${this.label}</label>` : ""}
13648
- ${this.leftIcon ? html52`<rel-icon name="${this.leftIcon}" size="sm" class="icon left"></rel-icon>` : ""}
13912
+ <div class="${["textfield-container", this.isDisabled ? "disabled" : "", this.multiline ? "multiline" : ""].filter(Boolean).join(" ")}">
13913
+ ${this.floatingLabel ? labelTemplate(leadingIcon && !this.multiline ? " has-left-icon" : "") : ""}
13914
+ ${!this.multiline ? leadingIconTemplate : ""}
13649
13915
 
13650
- <input
13651
- type="${this.type}"
13652
- id="${this.uniqueId}"
13653
- class="${inputClasses}"
13654
- placeholder="${this.placeholder}"
13655
- .value="${this.value}"
13656
- ?disabled="${this.state === "disabled"}"
13657
- aria-invalid="${this.state === "error"}"
13658
- @focus="${this.handleFocus}"
13659
- @blur="${this.handleBlur}"
13660
- @input="${this.handleInput}"
13661
- />
13916
+ ${inputTemplate}
13662
13917
 
13663
- ${this.rightIcon ? html52`<rel-icon name="${this.rightIcon}" size="sm" class="icon right"></rel-icon>` : ""}
13918
+ ${!this.multiline && this.rightIcon ? html52`<rel-icon name="${this.rightIcon}" size="sm" class="icon right"></rel-icon>` : ""}
13664
13919
  </div>
13665
13920
 
13666
- ${this.supportingText ? html52`<div
13667
- class="textfield-support ${this.state === "error" ? "error" : ""}"
13668
- >
13669
- ${this.supportingText}
13670
- </div>` : ""}
13921
+ ${belowTemplate}
13671
13922
  </div>
13672
13923
  `;
13673
13924
  }
@@ -13693,6 +13944,11 @@ RelTextfield.styles = css49`
13693
13944
  color: var(--rel-color-text-heading);
13694
13945
  }
13695
13946
 
13947
+ .required-mark {
13948
+ color: var(--rel-error-surface-default, #CC0000);
13949
+ margin-left: 2px;
13950
+ }
13951
+
13696
13952
  /* Floating label variant */
13697
13953
  :host([floatinglabel]) .textfield-wrapper {
13698
13954
  gap: 0;
@@ -13721,6 +13977,12 @@ RelTextfield.styles = css49`
13721
13977
  white-space: nowrap;
13722
13978
  }
13723
13979
 
13980
+ /* Floating label in multiline mode anchors to top instead of vertical center */
13981
+ :host([floatinglabel][multiline]) .textfield-label {
13982
+ top: var(--rel-spacing-space-sm);
13983
+ transform: none;
13984
+ }
13985
+
13724
13986
  :host([floatinglabel]) .textfield-wrapper.focused .textfield-label,
13725
13987
  :host([floatinglabel]) .textfield-wrapper.has-value .textfield-label {
13726
13988
  top: 0;
@@ -13784,6 +14046,11 @@ RelTextfield.styles = css49`
13784
14046
  cursor: not-allowed;
13785
14047
  }
13786
14048
 
14049
+ .textfield-container.multiline {
14050
+ overflow: visible;
14051
+ align-items: flex-start;
14052
+ }
14053
+
13787
14054
  /* Input */
13788
14055
  .textfield-input {
13789
14056
  flex: 1;
@@ -13801,6 +14068,25 @@ RelTextfield.styles = css49`
13801
14068
  color: var(--rel-color-text-disabled);
13802
14069
  }
13803
14070
 
14071
+ /* Textarea */
14072
+ textarea.textfield-input {
14073
+ resize: vertical;
14074
+ min-height: 80px;
14075
+ width: 100%;
14076
+ box-sizing: border-box;
14077
+ }
14078
+
14079
+ textarea.textfield-input::-webkit-scrollbar {
14080
+ width: 4px;
14081
+ }
14082
+ textarea.textfield-input::-webkit-scrollbar-track {
14083
+ background: transparent;
14084
+ }
14085
+ textarea.textfield-input::-webkit-scrollbar-thumb {
14086
+ background: var(--rel-color-text-placeholder);
14087
+ border-radius: 2px;
14088
+ }
14089
+
13804
14090
  .icon {
13805
14091
  position: absolute;
13806
14092
  display: flex;
@@ -13855,7 +14141,8 @@ RelTextfield.styles = css49`
13855
14141
  box-shadow: 0 0 0 1px var(--rel-color-text-primary);
13856
14142
  }
13857
14143
 
13858
- .textfield-wrapper.error .textfield-container {
14144
+ .textfield-wrapper.error .textfield-container,
14145
+ .textfield-wrapper.error .textfield-container:focus-within {
13859
14146
  border-color: var(--rel-error-surface-default);
13860
14147
  box-shadow: none;
13861
14148
  }
@@ -13864,6 +14151,10 @@ RelTextfield.styles = css49`
13864
14151
  border-color: var(--rel-color-border-default);
13865
14152
  }
13866
14153
 
14154
+ .textfield-wrapper.disabled .textfield-label {
14155
+ color: var(--rel-color-text-disabled);
14156
+ }
14157
+
13867
14158
  /* Supporting text */
13868
14159
  .textfield-support {
13869
14160
  font-size: var(--rel-fontSizes-xs);
@@ -13932,6 +14223,104 @@ RelTextfield.styles = css49`
13932
14223
  background: var(--rel-color-bg-tertiary);
13933
14224
  border-bottom-color: var(--rel-color-border-default);
13934
14225
  }
14226
+
14227
+ /* ── Full-width control (fullWidth defaults true → block, fills parent) ── */
14228
+ :host(:not([fullwidth])) {
14229
+ display: inline-block;
14230
+ width: auto;
14231
+ }
14232
+ :host(:not([fullwidth])) .textfield-wrapper {
14233
+ width: auto;
14234
+ }
14235
+
14236
+ /* ── Legend (notched-outline) variant ──
14237
+ Native <fieldset>/<legend>: the legend notches the top border and stays
14238
+ fixed in place across all states (no float/animation). */
14239
+ :host([variant="legend"]) .field-fieldset {
14240
+ position: relative;
14241
+ margin: 0;
14242
+ min-inline-size: 0;
14243
+ padding: 0;
14244
+ border: 1px solid var(--rel-color-border-default);
14245
+ border-radius: var(--rel-borders-radius-border-radius-100);
14246
+ background: var(--rel-color-bg-primary);
14247
+ transition: border-color 0.2s;
14248
+ }
14249
+
14250
+ :host([variant="legend"]) .field-legend {
14251
+ margin-left: var(--rel-spacing-space-sm);
14252
+ padding: 0 var(--rel-spacing-space-2xs);
14253
+ font-size: var(--rel-fontSizes-sm);
14254
+ font-weight: var(--rel-fontWeights-medium);
14255
+ line-height: 1;
14256
+ color: var(--rel-color-text-heading);
14257
+ }
14258
+
14259
+ :host([variant="legend"]) .textfield-input {
14260
+ width: 100%;
14261
+ box-sizing: border-box;
14262
+ }
14263
+
14264
+ /* Prefix icon (legend): vertically centered; input inset via has-left-icon */
14265
+ :host([variant="legend"]) .icon.left {
14266
+ top: 50%;
14267
+ left: var(--rel-spacing-space-sm);
14268
+ transform: translateY(-50%);
14269
+ }
14270
+ :host([variant="legend"]) .field-fieldset.multiline .icon.left {
14271
+ top: var(--rel-spacing-space-sm);
14272
+ transform: none;
14273
+ }
14274
+
14275
+ /* Hover */
14276
+ :host([variant="legend"]) .field-fieldset:hover {
14277
+ border-color: var(--rel-color-text-placeholder);
14278
+ }
14279
+
14280
+ /* Focus — border-color only. A box-shadow/outline ring hugs the full
14281
+ rectangular border-box and would draw across the notch, appearing to
14282
+ strike through the legend. The legend also recolors on focus (below). */
14283
+ :host([variant="legend"]) .textfield-wrapper.focused .field-fieldset,
14284
+ :host([variant="legend"]) .field-fieldset:focus-within {
14285
+ border-color: var(--rel-color-border-focus);
14286
+ box-shadow: none;
14287
+ }
14288
+ :host([variant="legend"]) .textfield-wrapper.focused .field-legend,
14289
+ :host([variant="legend"]) .field-fieldset:focus-within .field-legend {
14290
+ color: var(--rel-color-border-focus);
14291
+ }
14292
+
14293
+ /* Error */
14294
+ :host([variant="legend"]) .textfield-wrapper.error .field-fieldset {
14295
+ border-color: var(--rel-error-surface-default);
14296
+ box-shadow: none;
14297
+ }
14298
+ :host([variant="legend"]) .textfield-wrapper.error .field-legend {
14299
+ color: var(--rel-error-surface-default);
14300
+ }
14301
+
14302
+ /* Disabled */
14303
+ :host([variant="legend"]) .textfield-wrapper.disabled .field-fieldset {
14304
+ background: var(--rel-color-bg-tertiary);
14305
+ border-color: var(--rel-color-border-default);
14306
+ cursor: not-allowed;
14307
+ }
14308
+ :host([variant="legend"]) .textfield-wrapper.disabled .field-legend {
14309
+ color: var(--rel-color-text-disabled);
14310
+ }
14311
+
14312
+ /* Readonly */
14313
+ :host([variant="legend"]) .textfield-wrapper.readonly .field-fieldset {
14314
+ cursor: default;
14315
+ }
14316
+
14317
+ /* Legend font tracks size */
14318
+ :host([variant="legend"]) .textfield-wrapper.small .field-legend {
14319
+ font-size: var(--rel-fontSizes-xs);
14320
+ }
14321
+ :host([variant="legend"]) .textfield-wrapper.large .field-legend {
14322
+ font-size: var(--rel-fontSizes-md);
14323
+ }
13935
14324
  `;
13936
14325
  __decorateClass([
13937
14326
  property45({ type: String })
@@ -13966,6 +14355,45 @@ __decorateClass([
13966
14355
  __decorateClass([
13967
14356
  property45({ type: String, reflect: true })
13968
14357
  ], RelTextfield.prototype, "variant", 2);
14358
+ __decorateClass([
14359
+ property45({ type: Boolean })
14360
+ ], RelTextfield.prototype, "disabled", 2);
14361
+ __decorateClass([
14362
+ property45({ type: Boolean })
14363
+ ], RelTextfield.prototype, "required", 2);
14364
+ __decorateClass([
14365
+ property45({ type: Boolean, reflect: true })
14366
+ ], RelTextfield.prototype, "multiline", 2);
14367
+ __decorateClass([
14368
+ property45({ type: Number })
14369
+ ], RelTextfield.prototype, "rows", 2);
14370
+ __decorateClass([
14371
+ property45({ type: String })
14372
+ ], RelTextfield.prototype, "prefixIcon", 2);
14373
+ __decorateClass([
14374
+ property45({ type: String })
14375
+ ], RelTextfield.prototype, "prefixIconAriaLabel", 2);
14376
+ __decorateClass([
14377
+ property45({ type: Boolean })
14378
+ ], RelTextfield.prototype, "error", 2);
14379
+ __decorateClass([
14380
+ property45({ type: String })
14381
+ ], RelTextfield.prototype, "errorMessage", 2);
14382
+ __decorateClass([
14383
+ property45({ type: String })
14384
+ ], RelTextfield.prototype, "helperText", 2);
14385
+ __decorateClass([
14386
+ property45({ type: Boolean })
14387
+ ], RelTextfield.prototype, "readonly", 2);
14388
+ __decorateClass([
14389
+ property45({ type: Number })
14390
+ ], RelTextfield.prototype, "maxLength", 2);
14391
+ __decorateClass([
14392
+ property45({ type: String })
14393
+ ], RelTextfield.prototype, "name", 2);
14394
+ __decorateClass([
14395
+ property45({ type: Boolean, reflect: true })
14396
+ ], RelTextfield.prototype, "fullWidth", 2);
13969
14397
  __decorateClass([
13970
14398
  state19()
13971
14399
  ], RelTextfield.prototype, "internalState", 2);