twico-ui 1.46.0 → 1.47.0

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
@@ -5880,6 +5880,17 @@ var Checkbox = /* @__PURE__ */ React55__default.forwardRef(function Checkbox2({
5880
5880
  .twc-field__req { color: var(--color-danger); }
5881
5881
  .twc-check__desc { font-size: var(--text-xs); color: var(--color-text-muted); }
5882
5882
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
5883
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
5884
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
5885
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
5886
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
5887
+ The lane is held open by generated content rather than a fixed height so it matches the real
5888
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
5889
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
5890
+ whether a footer is already there, so no component has to report it. */
5891
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
5892
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
5893
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
5883
5894
  .twc-field__error { font-size: var(--text-xs); color: var(--color-danger-subtle-fg); font-weight: var(--font-medium); }
5884
5895
  `);
5885
5896
  const autoId = React55__default.useId();
@@ -5934,6 +5945,17 @@ var Checkbox = /* @__PURE__ */ React55__default.forwardRef(function Checkbox2({
5934
5945
  });
5935
5946
  var FIELD_CSS = `
5936
5947
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
5948
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
5949
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
5950
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
5951
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
5952
+ The lane is held open by generated content rather than a fixed height so it matches the real
5953
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
5954
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
5955
+ whether a footer is already there, so no component has to report it. */
5956
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
5957
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
5958
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
5937
5959
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
5938
5960
  .twc-field__req { color: var(--color-danger); }
5939
5961
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -6045,6 +6067,7 @@ function ColorPicker({
6045
6067
  tone = "primary",
6046
6068
  onChange,
6047
6069
  className = "",
6070
+ reserveFooter = false,
6048
6071
  ...rest
6049
6072
  }) {
6050
6073
  const __twcStyles0 = useScopedStyles("twc-field-styles", FIELD_CSS);
@@ -6184,7 +6207,7 @@ function ColorPicker({
6184
6207
  e.preventDefault();
6185
6208
  commit({ ...hsv, a: Math.round(a * 100) / 100 });
6186
6209
  };
6187
- return /* @__PURE__ */ jsxs("div", { className: `twc-cp twc-field ${className}`, ref: wrapRef, ...rest, children: [
6210
+ return /* @__PURE__ */ jsxs("div", { className: `twc-cp twc-field ${className}`, ref: wrapRef, ...rest, "data-reserve-footer": reserveFooter || void 0, children: [
6188
6211
  __twcStyles0,
6189
6212
  __twcStyles1,
6190
6213
  label ? /* @__PURE__ */ jsxs("span", { className: "twc-field__label", id: labelId, children: [
@@ -6347,6 +6370,17 @@ function ColorPicker({
6347
6370
  }
6348
6371
  var COMBO_CSS = `
6349
6372
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
6373
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
6374
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
6375
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
6376
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
6377
+ The lane is held open by generated content rather than a fixed height so it matches the real
6378
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
6379
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
6380
+ whether a footer is already there, so no component has to report it. */
6381
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
6382
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
6383
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
6350
6384
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
6351
6385
  .twc-field__req { color: var(--color-danger); }
6352
6386
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -6485,6 +6519,7 @@ var Combobox = /* @__PURE__ */ React55__default.forwardRef(function Combobox2({
6485
6519
  className = "",
6486
6520
  onFocus,
6487
6521
  onKeyDown,
6522
+ reserveFooter = false,
6488
6523
  ...rest
6489
6524
  }, ref) {
6490
6525
  const __twcStyles = useScopedStyles("twc-combo-styles", COMBO_CSS);
@@ -6772,7 +6807,7 @@ var Combobox = /* @__PURE__ */ React55__default.forwardRef(function Combobox2({
6772
6807
  popEl = /* @__PURE__ */ jsx("div", { className: "twc-pop", ref: popRef, "data-placement": placement === "top" ? "top" : void 0, children: popInner });
6773
6808
  }
6774
6809
  }
6775
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, ref: wrapRef, children: [
6810
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, ref: wrapRef, "data-reserve-footer": reserveFooter || void 0, children: [
6776
6811
  __twcStyles,
6777
6812
  name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "" }) : null,
6778
6813
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
@@ -7092,6 +7127,17 @@ function clampPrecision(str, precision) {
7092
7127
  }
7093
7128
  var CUR_CSS = `
7094
7129
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
7130
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
7131
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
7132
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
7133
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
7134
+ The lane is held open by generated content rather than a fixed height so it matches the real
7135
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
7136
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
7137
+ whether a footer is already there, so no component has to report it. */
7138
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
7139
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
7140
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
7095
7141
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
7096
7142
  .twc-field__req { color: var(--color-danger); }
7097
7143
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -7161,6 +7207,7 @@ function Currency({
7161
7207
  id,
7162
7208
  placeholder = "0.00",
7163
7209
  className = "",
7210
+ reserveFooter = false,
7164
7211
  ...rest
7165
7212
  }) {
7166
7213
  const __twcStyles = useScopedStyles("twc-cur-styles", CUR_CSS);
@@ -7209,7 +7256,7 @@ function Currency({
7209
7256
  if (!controlled) setInternal(fixed);
7210
7257
  onValueChange?.(Number(fixed), fixed);
7211
7258
  }
7212
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, children: [
7259
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, "data-reserve-footer": reserveFooter || void 0, children: [
7213
7260
  __twcStyles,
7214
7261
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
7215
7262
  label,
@@ -7261,6 +7308,17 @@ function useFallbackName(ref, hasOwnName) {
7261
7308
  }
7262
7309
  var SELECT_CSS = `
7263
7310
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
7311
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
7312
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
7313
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
7314
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
7315
+ The lane is held open by generated content rather than a fixed height so it matches the real
7316
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
7317
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
7318
+ whether a footer is already there, so no component has to report it. */
7319
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
7320
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
7321
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
7264
7322
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
7265
7323
  .twc-field__req { color: var(--color-danger); }
7266
7324
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -7397,6 +7455,7 @@ var Select = /* @__PURE__ */ React55__default.forwardRef(function Select2({
7397
7455
  renderOption,
7398
7456
  id,
7399
7457
  className = "",
7458
+ reserveFooter = false,
7400
7459
  onClick,
7401
7460
  onKeyDown,
7402
7461
  ...rest
@@ -7742,7 +7801,7 @@ var Select = /* @__PURE__ */ React55__default.forwardRef(function Select2({
7742
7801
  popEl = /* @__PURE__ */ jsx("div", { className: "twc-pop", ref: popRef, "data-state": popState, "data-placement": placement === "top" ? "top" : "bottom", children: popInner });
7743
7802
  }
7744
7803
  }
7745
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, ref: wrapRef, children: [
7804
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, ref: wrapRef, "data-reserve-footer": reserveFooter || void 0, children: [
7746
7805
  __twcStyles,
7747
7806
  name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "" }) : null,
7748
7807
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
@@ -7867,6 +7926,7 @@ function CurrencyField({
7867
7926
  id,
7868
7927
  placeholder = "0.00",
7869
7928
  className = "",
7929
+ reserveFooter = false,
7870
7930
  ...rest
7871
7931
  }) {
7872
7932
  const __twcStyles = useScopedStyles("twc-curf-styles", CURF_CSS);
@@ -7929,7 +7989,7 @@ function CurrencyField({
7929
7989
  onValueChange?.(Number(fixed), fixed, cur);
7930
7990
  }
7931
7991
  const curLabel = typeof label === "string" && label ? `${label} currency` : "Currency";
7932
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, children: [
7992
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, "data-reserve-footer": reserveFooter || void 0, children: [
7933
7993
  __twcStyles,
7934
7994
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
7935
7995
  label,
@@ -7976,6 +8036,17 @@ function CurrencyField({
7976
8036
  }
7977
8037
  var FIELD_CSS2 = `
7978
8038
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
8039
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
8040
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
8041
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
8042
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
8043
+ The lane is held open by generated content rather than a fixed height so it matches the real
8044
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
8045
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
8046
+ whether a footer is already there, so no component has to report it. */
8047
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
8048
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
8049
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
7979
8050
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
7980
8051
  .twc-field__req { color: var(--color-danger); }
7981
8052
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -8065,6 +8136,7 @@ var Input = /* @__PURE__ */ React55__default.forwardRef(function Input2({
8065
8136
  disabled = false,
8066
8137
  id,
8067
8138
  className = "",
8139
+ reserveFooter = false,
8068
8140
  ...rest
8069
8141
  }, ref) {
8070
8142
  const __twcStyles = useScopedStyles("twc-field-styles", FIELD_CSS2);
@@ -8134,7 +8206,7 @@ var Input = /* @__PURE__ */ React55__default.forwardRef(function Input2({
8134
8206
  ] })
8135
8207
  }
8136
8208
  ) : null;
8137
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, children: [
8209
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, "data-reserve-footer": reserveFooter || void 0, children: [
8138
8210
  __twcStyles,
8139
8211
  __twcExtra,
8140
8212
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
@@ -8179,6 +8251,17 @@ var Input = /* @__PURE__ */ React55__default.forwardRef(function Input2({
8179
8251
  });
8180
8252
  var MULTI_CSS = `
8181
8253
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
8254
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
8255
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
8256
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
8257
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
8258
+ The lane is held open by generated content rather than a fixed height so it matches the real
8259
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
8260
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
8261
+ whether a footer is already there, so no component has to report it. */
8262
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
8263
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
8264
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
8182
8265
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
8183
8266
  .twc-field__req { color: var(--color-danger); }
8184
8267
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -8324,6 +8407,7 @@ var MultiSelect = /* @__PURE__ */ React55__default.forwardRef(function MultiSele
8324
8407
  className = "",
8325
8408
  onFocus,
8326
8409
  onKeyDown,
8410
+ reserveFooter = false,
8327
8411
  ...rest
8328
8412
  }, ref) {
8329
8413
  const __twcStyles = useScopedStyles("twc-ms-styles", MULTI_CSS);
@@ -8597,7 +8681,7 @@ var MultiSelect = /* @__PURE__ */ React55__default.forwardRef(function MultiSele
8597
8681
  }
8598
8682
  }
8599
8683
  const msFallbackName = useFallbackName(inputRef, Boolean(label || rest["aria-label"] || rest["aria-labelledby"])) ? placeholder : void 0;
8600
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, ref: wrapRef, children: [
8684
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, ref: wrapRef, "data-reserve-footer": reserveFooter || void 0, children: [
8601
8685
  __twcStyles,
8602
8686
  name ? selected.map((v, i) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v }, i)) : null,
8603
8687
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
@@ -14829,6 +14913,17 @@ function DiffTable({
14829
14913
  }
14830
14914
  var FIELD_CSS3 = `
14831
14915
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
14916
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
14917
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
14918
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
14919
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
14920
+ The lane is held open by generated content rather than a fixed height so it matches the real
14921
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
14922
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
14923
+ whether a footer is already there, so no component has to report it. */
14924
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
14925
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
14926
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
14832
14927
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
14833
14928
  .twc-field__req { color: var(--color-danger); }
14834
14929
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -14961,6 +15056,7 @@ function DatePicker({
14961
15056
  onChange,
14962
15057
  id,
14963
15058
  className = "",
15059
+ reserveFooter = false,
14964
15060
  ...rest
14965
15061
  }) {
14966
15062
  const __twcStyles0 = useScopedStyles("twc-field-styles", FIELD_CSS3);
@@ -15163,7 +15259,7 @@ function DatePicker({
15163
15259
  setFocusDate(next);
15164
15260
  if (next.getFullYear() !== y || next.getMonth() !== m) setView(new Date(next.getFullYear(), next.getMonth(), 1));
15165
15261
  };
15166
- return /* @__PURE__ */ jsxs("div", { className: `twc-dp twc-field ${className}`, ref: wrapRef, ...rest, children: [
15262
+ return /* @__PURE__ */ jsxs("div", { className: `twc-dp twc-field ${className}`, ref: wrapRef, ...rest, "data-reserve-footer": reserveFooter || void 0, children: [
15167
15263
  __twcStyles0,
15168
15264
  __twcStyles1,
15169
15265
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", id: `${fieldId}-label`, children: [
@@ -15387,6 +15483,17 @@ function DatePicker({
15387
15483
  }
15388
15484
  var FIELD_CSS4 = `
15389
15485
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
15486
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
15487
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
15488
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
15489
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
15490
+ The lane is held open by generated content rather than a fixed height so it matches the real
15491
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
15492
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
15493
+ whether a footer is already there, so no component has to report it. */
15494
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
15495
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
15496
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
15390
15497
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
15391
15498
  .twc-field__req { color: var(--color-danger); }
15392
15499
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -15522,6 +15629,7 @@ function DateRangePicker({
15522
15629
  parse,
15523
15630
  onChange,
15524
15631
  className = "",
15632
+ reserveFooter = false,
15525
15633
  ...rest
15526
15634
  }) {
15527
15635
  const __twcStyles0 = useScopedStyles("twc-field-styles", FIELD_CSS4);
@@ -15766,7 +15874,7 @@ function DateRangePicker({
15766
15874
  return t > Math.min(s, e) && t < Math.max(s, e);
15767
15875
  };
15768
15876
  const text = range2.start ? format ? format(range2) : `${fmtD(range2.start, locale)} \u2013 ${range2.end ? fmtD(range2.end, locale) : "\u2026"}` : "";
15769
- return /* @__PURE__ */ jsxs("div", { className: `twc-drp twc-field ${className}`, ref: wrapRef, ...rest, children: [
15877
+ return /* @__PURE__ */ jsxs("div", { className: `twc-drp twc-field ${className}`, ref: wrapRef, ...rest, "data-reserve-footer": reserveFooter || void 0, children: [
15770
15878
  __twcStyles0,
15771
15879
  __twcStyles1,
15772
15880
  label ? /* @__PURE__ */ jsxs("span", { className: "twc-field__label", id: labelId, children: [
@@ -15994,6 +16102,17 @@ function DateRangePicker({
15994
16102
  }
15995
16103
  var FIELD_CSS5 = `
15996
16104
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
16105
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
16106
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
16107
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
16108
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
16109
+ The lane is held open by generated content rather than a fixed height so it matches the real
16110
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
16111
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
16112
+ whether a footer is already there, so no component has to report it. */
16113
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
16114
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
16115
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
15997
16116
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
15998
16117
  .twc-field__req { color: var(--color-danger); }
15999
16118
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -16092,6 +16211,7 @@ function TimePicker({
16092
16211
  onChange,
16093
16212
  id,
16094
16213
  className = "",
16214
+ reserveFooter = false,
16095
16215
  ...rest
16096
16216
  }) {
16097
16217
  const __twcStyles0 = useScopedStyles("twc-field-styles", FIELD_CSS5);
@@ -16306,7 +16426,7 @@ function TimePicker({
16306
16426
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
16307
16427
  /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
16308
16428
  ] });
16309
- return /* @__PURE__ */ jsxs("div", { className: `twc-tp twc-field ${className}`, ref: wrapRef, ...rest, children: [
16429
+ return /* @__PURE__ */ jsxs("div", { className: `twc-tp twc-field ${className}`, ref: wrapRef, ...rest, "data-reserve-footer": reserveFooter || void 0, children: [
16310
16430
  __twcStyles0,
16311
16431
  __twcStyles1,
16312
16432
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", id: `${fieldId}-label`, children: [
@@ -16463,6 +16583,17 @@ function TimePicker({
16463
16583
  }
16464
16584
  var FIELD_CSS6 = `
16465
16585
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
16586
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
16587
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
16588
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
16589
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
16590
+ The lane is held open by generated content rather than a fixed height so it matches the real
16591
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
16592
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
16593
+ whether a footer is already there, so no component has to report it. */
16594
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
16595
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
16596
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
16466
16597
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
16467
16598
  .twc-field__req { color: var(--color-danger); }
16468
16599
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -16501,6 +16632,7 @@ function DateTimePicker({
16501
16632
  onChange,
16502
16633
  id,
16503
16634
  className = "",
16635
+ reserveFooter = false,
16504
16636
  ...rest
16505
16637
  }) {
16506
16638
  const __twcStyles0 = useScopedStyles("twc-field-styles", FIELD_CSS6);
@@ -16525,7 +16657,7 @@ function DateTimePicker({
16525
16657
  commitVal(new Date(b ? b.getFullYear() : t.getFullYear(), b ? b.getMonth() : t.getMonth(), b ? b.getDate() : t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds(), 0));
16526
16658
  };
16527
16659
  const childTone = error ? "danger" : tone;
16528
- return /* @__PURE__ */ jsxs("div", { className: `twc-dtp twc-field ${className}`, id: fieldId, ...rest, children: [
16660
+ return /* @__PURE__ */ jsxs("div", { className: `twc-dtp twc-field ${className}`, id: fieldId, ...rest, "data-reserve-footer": reserveFooter || void 0, children: [
16529
16661
  __twcStyles0,
16530
16662
  __twcStyles1,
16531
16663
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", id: `${fieldId}-label`, children: [
@@ -17103,6 +17235,17 @@ function EmptyState({
17103
17235
  }
17104
17236
  var FIELD_CSS7 = `
17105
17237
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
17238
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
17239
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
17240
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
17241
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
17242
+ The lane is held open by generated content rather than a fixed height so it matches the real
17243
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
17244
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
17245
+ whether a footer is already there, so no component has to report it. */
17246
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
17247
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
17248
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
17106
17249
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
17107
17250
  .twc-field__req { color: var(--color-danger); }
17108
17251
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -17117,6 +17260,7 @@ function Field({
17117
17260
  size = "md",
17118
17261
  id,
17119
17262
  className = "",
17263
+ reserveFooter = false,
17120
17264
  children,
17121
17265
  ...rest
17122
17266
  }) {
@@ -17129,7 +17273,7 @@ function Field({
17129
17273
  "aria-describedby": [children.props["aria-describedby"], hasMsg ? descId : null].filter(Boolean).join(" ") || void 0,
17130
17274
  "aria-invalid": children.props["aria-invalid"] !== void 0 ? children.props["aria-invalid"] : error ? true : void 0
17131
17275
  }) : children;
17132
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, "data-size": size, ...rest, children: [
17276
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, "data-size": size, ...rest, "data-reserve-footer": reserveFooter || void 0, children: [
17133
17277
  __twcStyles,
17134
17278
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor, children: [
17135
17279
  label,
@@ -18185,6 +18329,17 @@ var Radio = /* @__PURE__ */ React55__default.forwardRef(function Radio2({
18185
18329
  .twc-field__req { color: var(--color-danger); }
18186
18330
  .twc-radio__desc { font-size: var(--text-xs); color: var(--color-text-muted); }
18187
18331
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
18332
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
18333
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
18334
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
18335
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
18336
+ The lane is held open by generated content rather than a fixed height so it matches the real
18337
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
18338
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
18339
+ whether a footer is already there, so no component has to report it. */
18340
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
18341
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
18342
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
18188
18343
  .twc-field__error { font-size: var(--text-xs); color: var(--color-danger-subtle-fg); font-weight: var(--font-medium); }
18189
18344
  `);
18190
18345
  const autoId = React55__default.useId();
@@ -18234,6 +18389,17 @@ var Radio = /* @__PURE__ */ React55__default.forwardRef(function Radio2({
18234
18389
  });
18235
18390
  var RADIO_GROUP_CSS = `
18236
18391
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
18392
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
18393
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
18394
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
18395
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
18396
+ The lane is held open by generated content rather than a fixed height so it matches the real
18397
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
18398
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
18399
+ whether a footer is already there, so no component has to report it. */
18400
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
18401
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
18402
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
18237
18403
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
18238
18404
  .twc-field__req { color: var(--color-danger); }
18239
18405
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -18258,6 +18424,7 @@ function RadioGroup({
18258
18424
  tone = "primary",
18259
18425
  id,
18260
18426
  className = "",
18427
+ reserveFooter = false,
18261
18428
  ...rest
18262
18429
  }) {
18263
18430
  const __twcStyles = useScopedStyles("twc-radio-group-styles", RADIO_GROUP_CSS);
@@ -18305,6 +18472,7 @@ function RadioGroup({
18305
18472
  "div",
18306
18473
  {
18307
18474
  className: `twc-field ${className}`,
18475
+ "data-reserve-footer": reserveFooter || void 0,
18308
18476
  role: "radiogroup",
18309
18477
  "aria-labelledby": label ? labelId : void 0,
18310
18478
  "aria-describedby": error || hint ? descId : void 0,
@@ -19329,6 +19497,17 @@ var Switch = /* @__PURE__ */ React55__default.forwardRef(function Switch2({
19329
19497
  .twc-field__req { color: var(--color-danger); }
19330
19498
  .twc-switch__desc { font-size: var(--text-xs); color: var(--color-text-muted); }
19331
19499
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
19500
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
19501
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
19502
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
19503
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
19504
+ The lane is held open by generated content rather than a fixed height so it matches the real
19505
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
19506
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
19507
+ whether a footer is already there, so no component has to report it. */
19508
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
19509
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
19510
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
19332
19511
  .twc-field__error { font-size: var(--text-xs); color: var(--color-danger-subtle-fg); font-weight: var(--font-medium); }
19333
19512
  `);
19334
19513
  const autoId = React55__default.useId();
@@ -19768,6 +19947,17 @@ function Tag({
19768
19947
  var useIso = typeof document !== "undefined" ? React55__default.useLayoutEffect : React55__default.useEffect;
19769
19948
  var TEXTAREA_CSS = `
19770
19949
  .twc-field { display: flex; flex-direction: column; gap: var(--space-1-5); font-family: var(--font-sans); }
19950
+ /* #494: reserve the footer lane. A row of fields aligned with align="flex-end" splits in two the
19951
+ moment ONE of them has a hint, because that field grows a footer and the others do not - and the
19952
+ caption then moves a DIFFERENT control, which nothing in the JSX suggests. align= cannot fix it:
19953
+ flex-start lines up the labels instead, so the controls part company as soon as one label wraps.
19954
+ The lane is held open by generated content rather than a fixed height so it matches the real
19955
+ footer by construction - same font metrics, same flex gap - instead of by a number that drifts.
19956
+ Whitespace-only generated content is not announced, and :has() lets the CSS tell on its own
19957
+ whether a footer is already there, so no component has to report it. */
19958
+ .twc-field[data-reserve-footer]:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after,
19959
+ [data-reserve-footer] .twc-field:not(:has(> .twc-field__footer, > .twc-field__hint, > .twc-field__error))::after {
19960
+ content: "\\00a0"; font-size: var(--text-xs); user-select: none; }
19771
19961
  .twc-field__label { font-size: var(--text-sm); font-weight: var(--font-semibold); color: var(--color-text); display: flex; gap: 4px; align-items: center; }
19772
19962
  .twc-field__req { color: var(--color-danger); }
19773
19963
  .twc-field__hint { font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -19830,6 +20020,7 @@ var Textarea = /* @__PURE__ */ React55__default.forwardRef(function Textarea2({
19830
20020
  disabled = false,
19831
20021
  id,
19832
20022
  className = "",
20023
+ reserveFooter = false,
19833
20024
  ...rest
19834
20025
  }, ref) {
19835
20026
  const __twcStyles = useScopedStyles("twc-textarea-styles", TEXTAREA_CSS);
@@ -19887,7 +20078,7 @@ var Textarea = /* @__PURE__ */ React55__default.forwardRef(function Textarea2({
19887
20078
  const showClear = clearable && count > 0 && !disabled && !rest.readOnly;
19888
20079
  const ariaInvalid = invalid ? true : rest["aria-invalid"] ?? void 0;
19889
20080
  const describedBy = [error || hint ? descId : null, showCounter ? countId : null, rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
19890
- return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, children: [
20081
+ return /* @__PURE__ */ jsxs("div", { className: `twc-field ${className}`, "data-reserve-footer": reserveFooter || void 0, children: [
19891
20082
  __twcStyles,
19892
20083
  label ? /* @__PURE__ */ jsxs("label", { className: "twc-field__label", htmlFor: fieldId, children: [
19893
20084
  label,