twico-ui 1.36.0 → 1.37.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to Twico UI are documented here (newest first). This file is generated automatically on each release by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit it by hand. The same notes appear on each [GitHub Release](https://github.com/EncryptionL/twico-ui/releases).
4
4
 
5
+ # [1.37.0](https://github.com/EncryptionL/twico-ui/compare/v1.36.1...v1.37.0) (2026-09-11)
6
+
7
+
8
+ ### Features
9
+
10
+ * **overlay:** safer Dialog/Drawer defaults + Popover height clamp ([#371](https://github.com/EncryptionL/twico-ui/issues/371), [#372](https://github.com/EncryptionL/twico-ui/issues/372), [#373](https://github.com/EncryptionL/twico-ui/issues/373)) ([0f7a386](https://github.com/EncryptionL/twico-ui/commit/0f7a38656956bb9ef7cdd0331d2b5e6776aa7d7c))
11
+
12
+ ## [1.36.1](https://github.com/EncryptionL/twico-ui/compare/v1.36.0...v1.36.1) (2026-09-09)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **datatable:** runtime combine into a valueGetter/renderCell target ([#369](https://github.com/EncryptionL/twico-ui/issues/369)) ([6363f38](https://github.com/EncryptionL/twico-ui/commit/6363f38ffd2b754dc7be9ddb6f47cbfc9bab10e2)), closes [#338](https://github.com/EncryptionL/twico-ui/issues/338)
18
+
5
19
  # [1.36.0](https://github.com/EncryptionL/twico-ui/compare/v1.35.0...v1.36.0) (2026-09-09)
6
20
 
7
21
 
package/dist/index.cjs CHANGED
@@ -9381,7 +9381,7 @@ function Datatable({
9381
9381
  for (const c of columnsProp) if (c && c.field != null) byField[c.field] = c;
9382
9382
  return [DIFF_OP_COLUMN, ...columnsProp.map((c) => {
9383
9383
  const cfg = combineCfg(userCombine[c.field] || c.combine);
9384
- const base = cfg && !c.valueGetter ? { ...c, valueGetter: combineValueGetter(combineSrcCols(cfg, byField), cfg) } : c;
9384
+ const base = cfg && (!!userCombine[c.field] || !c.valueGetter) ? { ...c, valueGetter: combineValueGetter(combineSrcCols(cfg, byField), cfg) } : c;
9385
9385
  return { ...base, renderCell: (val, row) => renderDiffCell(base, val, row) };
9386
9386
  })];
9387
9387
  },
@@ -9432,15 +9432,17 @@ function Datatable({
9432
9432
  const cfg = combineCfg(userCombine[c.field] || c.combine);
9433
9433
  if (!cfg) continue;
9434
9434
  const srcCols = combineSrcCols(cfg, byField);
9435
+ const explicit = !!userCombine[c.field];
9436
+ const useJoin = explicit || !c.valueGetter;
9435
9437
  out[i] = {
9436
9438
  ...c,
9437
9439
  editable: false,
9438
9440
  // the combined value is synthetic — inline editing would write to a phantom field
9439
- valueGetter: c.valueGetter || combineValueGetter(srcCols, cfg),
9440
- // Consumer renderCell wins. If they overrode only valueGetter, fall back to the DEFAULT cell render
9441
- // (renderCell: undefined) so the cell shows THEIR value — keeping display in sync with what
9442
- // sort/filter/search/export use — rather than the auto source-join. Otherwise render the combined cell.
9443
- renderCell: c.renderCell || (c.valueGetter ? void 0 : ((value, row) => renderCombined(srcCols, row, cfg)))
9441
+ valueGetter: useJoin ? combineValueGetter(srcCols, cfg) : c.valueGetter,
9442
+ // In diff mode the diff `columns` memo already wrapped renderCell in renderDiffCell (over the enriched
9443
+ // combined value) — keep that wrapper, or an EXPLICIT combine's `!explicit` short-circuit would strip the
9444
+ // diff arrows / old→new styling (#369 review). Declarative diff already preserved it (c.renderCell truthy).
9445
+ renderCell: isDiff ? c.renderCell : !explicit && c.renderCell || (useJoin ? ((value, row) => renderCombined(srcCols, row, cfg)) : void 0)
9444
9446
  };
9445
9447
  }
9446
9448
  return out;
@@ -15431,6 +15433,10 @@ var DIALOG_CSS = `
15431
15433
  .twc-dialog__close svg { width: 18px; height: 18px; }
15432
15434
  .twc-dialog__body { padding: var(--space-4) var(--space-5); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
15433
15435
  .twc-dialog__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: 0 var(--space-5) var(--space-5); }
15436
+ /* #372: header/body and body/footer rules (on by default). Per-region data-divider so the rule is
15437
+ suppressed when the region is empty (no footer; a header with only a close button and no title/description). */
15438
+ .twc-dialog__header[data-divider="true"] { padding-bottom: var(--space-4); border-bottom: var(--border-thin) solid var(--color-divider); }
15439
+ .twc-dialog__footer[data-divider="true"] { padding-top: var(--space-4); border-top: var(--border-thin) solid var(--color-divider); }
15434
15440
  `;
15435
15441
  function Dialog({
15436
15442
  open,
@@ -15440,7 +15446,8 @@ function Dialog({
15440
15446
  footer,
15441
15447
  children,
15442
15448
  size = "md",
15443
- scrollBody = false,
15449
+ scrollBody = true,
15450
+ dividers = true,
15444
15451
  closeOnBackdrop = true,
15445
15452
  className = "",
15446
15453
  ...rest
@@ -15481,7 +15488,7 @@ function Dialog({
15481
15488
  }, children: [
15482
15489
  __twcStyles,
15483
15490
  /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: dialogRef, className: `twc-dialog ${className}`, "data-state": state, "data-size": size, "data-scroll-body": scrollBody ? "true" : void 0, role: "dialog", "aria-modal": "true", tabIndex: -1, "aria-labelledby": title ? titleId : void 0, "aria-label": !title ? "Dialog" : void 0, "aria-describedby": description ? descId : void 0, ...rest, children: [
15484
- title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dialog__header", children: [
15491
+ title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dialog__header", "data-divider": dividers && (title || description) && (children || footer) ? "true" : void 0, children: [
15485
15492
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-dialog__titles", children: [
15486
15493
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__title", id: titleId, children: title }) : null,
15487
15494
  description ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__desc", id: descId, children: description }) : null
@@ -15489,7 +15496,7 @@ function Dialog({
15489
15496
  onClose ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: "twc-dialog__close", onClick: onClose, "aria-label": "Close", type: "button", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15490
15497
  ] }) : null,
15491
15498
  children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__body", children }) : null,
15492
- footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__footer", children: footer }) : null
15499
+ footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-dialog__footer", "data-divider": dividers && children ? "true" : void 0, children: footer }) : null
15493
15500
  ] })
15494
15501
  ] });
15495
15502
  return renderPortal(overlay);
@@ -15589,7 +15596,11 @@ var DRAWER_CSS = `
15589
15596
  .twc-drawer__close:hover { background: var(--color-surface-sunken); color: var(--color-text); }
15590
15597
  .twc-drawer__close svg { width: 18px; height: 18px; }
15591
15598
  .twc-drawer__body { flex: 1; overflow-y: auto; padding: var(--space-2) var(--space-5) var(--space-5); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
15592
- .twc-drawer__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-4) var(--space-5); border-top: var(--border-thin) solid var(--color-divider); }
15599
+ .twc-drawer__footer { display: flex; gap: var(--space-2); justify-content: flex-end; padding: var(--space-4) var(--space-5); }
15600
+ /* #372: header/footer rules (on by default), consistent with Dialog. Per-region data-divider so an empty
15601
+ region grows no stray line (no footer; a header with only a close button and no title/description). */
15602
+ .twc-drawer__header[data-divider="true"] { border-bottom: var(--border-thin) solid var(--color-divider); }
15603
+ .twc-drawer__footer[data-divider="true"] { border-top: var(--border-thin) solid var(--color-divider); }
15593
15604
  @media (prefers-reduced-motion: reduce) {
15594
15605
  .twc-drawer__overlay[data-state], .twc-drawer[data-state] { animation-duration: 1ms; }
15595
15606
  }
@@ -15605,6 +15616,7 @@ function Drawer({
15605
15616
  width,
15606
15617
  height,
15607
15618
  closeOnBackdrop = true,
15619
+ dividers = true,
15608
15620
  children,
15609
15621
  className = "",
15610
15622
  style,
@@ -15650,7 +15662,7 @@ function Drawer({
15650
15662
  }, children: [
15651
15663
  __twcStyles,
15652
15664
  /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: panelRef, className: `twc-drawer ${className}`, "data-side": side, "data-state": state, role: "dialog", "aria-modal": "true", tabIndex: -1, "aria-labelledby": title ? titleId : void 0, "aria-label": !title ? "Drawer" : void 0, "aria-describedby": description ? descId : void 0, style: { ...dim ? sizeVar : null, ...style }, ...rest, children: [
15653
- title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drawer__header", children: [
15665
+ title || description || onClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drawer__header", "data-divider": dividers && (title || description) ? "true" : void 0, children: [
15654
15666
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-drawer__titles", children: [
15655
15667
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__title", id: titleId, children: title }) : null,
15656
15668
  description ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__desc", id: descId, children: description }) : null
@@ -15658,7 +15670,7 @@ function Drawer({
15658
15670
  onClose ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: "twc-drawer__close", onClick: onClose, "aria-label": "Close", type: "button", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }) : null
15659
15671
  ] }) : null,
15660
15672
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__body", children }),
15661
- footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__footer", children: footer }) : null
15673
+ footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-drawer__footer", "data-divider": dividers ? "true" : void 0, children: footer }) : null
15662
15674
  ] })
15663
15675
  ] });
15664
15676
  return renderPortal(overlay);
@@ -16359,7 +16371,9 @@ var POPOVER_CSS = `
16359
16371
  .twc-popover__arrow { position: absolute; width: 11px; height: 11px; background: var(--color-surface-raised);
16360
16372
  border-left: var(--border-thin) solid var(--color-border); border-top: var(--border-thin) solid var(--color-border);
16361
16373
  transform: rotate(45deg); }
16362
- .twc-popover__inner { padding: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); }
16374
+ /* #373: cap the panel to the room place() measured and scroll INSIDE the inner (not the panel \u2014 the panel
16375
+ must keep overflow visible so the absolutely-positioned arrow isn't clipped) so a tall panel's footer stays reachable. */
16376
+ .twc-popover__inner { padding: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); line-height: var(--leading-normal); overflow-y: auto; }
16363
16377
  .twc-popover__title { font-size: var(--text-sm); font-weight: var(--font-bold); color: var(--color-text); margin-bottom: 4px; }
16364
16378
  `;
16365
16379
  function Popover({
@@ -16421,7 +16435,7 @@ function Popover({
16421
16435
  l = Math.max(M, Math.min(l, vw - w - M));
16422
16436
  left = l;
16423
16437
  arrow = { left: Math.min(Math.max(r2.left + r2.width / 2 - l - 5.5, 10), w - 20), [flip ? "bottom" : "top"]: -6 };
16424
- return setPos({ top, bottom, left, width: w, flip, arrow });
16438
+ return setPos({ top, bottom, left, width: w, flip, arrow, maxHeight: flip ? spaceAbove : spaceBelow });
16425
16439
  }
16426
16440
  const ph = popRef.current ? popRef.current.offsetHeight : 120;
16427
16441
  let onRight = placement === "right";
@@ -16433,7 +16447,7 @@ function Popover({
16433
16447
  const cy = r2.top + r2.height / 2;
16434
16448
  top = Math.max(M, Math.min(cy - ph / 2, vh - ph - M));
16435
16449
  arrow = { top: Math.min(Math.max(cy - top - 5.5, 8), ph - 18), [onRight ? "left" : "right"]: -6 };
16436
- setPos({ top, left, width: w, flip: false, arrow });
16450
+ setPos({ top, left, width: w, flip: false, arrow, maxHeight: vh - 2 * M });
16437
16451
  }, [placement, align, width]);
16438
16452
  React51__namespace.default.useEffect(() => {
16439
16453
  if (!open) return;
@@ -16499,7 +16513,7 @@ function Popover({
16499
16513
  children: [
16500
16514
  __twcStyles,
16501
16515
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "twc-popover__arrow", style: pos.arrow, "aria-hidden": "true" }),
16502
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-popover__inner", children: [
16516
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "twc-popover__inner", style: { maxHeight: pos.maxHeight }, children: [
16503
16517
  title ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twc-popover__title", id: `${popId}-title`, children: title }) : null,
16504
16518
  children
16505
16519
  ] })