@adoptai/genui-components 0.1.59 → 0.1.60

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/renderer.js CHANGED
@@ -5525,7 +5525,14 @@ var decisionCardSchema = z.object({
5525
5525
  z.object({
5526
5526
  id: z.string().optional(),
5527
5527
  label: z.string(),
5528
- recommended: z.boolean().optional()
5528
+ recommended: z.boolean().optional(),
5529
+ value: z.number().optional(),
5530
+ adjust: z.object({
5531
+ min: z.number(),
5532
+ max: z.number(),
5533
+ step: z.number().optional(),
5534
+ unit: z.string().optional()
5535
+ }).optional()
5529
5536
  })
5530
5537
  ).min(1).max(5),
5531
5538
  source: z.object({
@@ -5533,7 +5540,8 @@ var decisionCardSchema = z.object({
5533
5540
  url: z.string().optional()
5534
5541
  }).optional(),
5535
5542
  resolved: z.object({
5536
- option: z.string()
5543
+ option: z.string(),
5544
+ value: z.number().optional()
5537
5545
  }).optional()
5538
5546
  });
5539
5547
  var decisionCardTool = {
@@ -5576,13 +5584,28 @@ var decisionCardTool = {
5576
5584
  type: "array",
5577
5585
  minItems: 1,
5578
5586
  maxItems: 5,
5579
- description: "Mutually-exclusive resolution choices; mark one `recommended` to make it primary",
5587
+ description: "Mutually-exclusive resolution choices; mark one `recommended` to make it primary. An option that carries a numeric figure the user may want to tune (e.g. 'Book reserve' at 50 units) should set `value` (your suggested figure) and `adjust` ({min,max[,step,unit]}) \u2014 the card then lets the user fine-tune the number within that range before confirming.",
5580
5588
  items: {
5581
5589
  type: "object",
5582
5590
  properties: {
5583
5591
  id: { type: "string" },
5584
5592
  label: { type: "string" },
5585
- recommended: { type: "boolean" }
5593
+ recommended: { type: "boolean" },
5594
+ value: {
5595
+ type: "number",
5596
+ description: "Suggested numeric figure for this option (the starting point of the adjuster)"
5597
+ },
5598
+ adjust: {
5599
+ type: "object",
5600
+ description: "Allow the user to tune `value` within this inclusive range before resolving",
5601
+ properties: {
5602
+ min: { type: "number" },
5603
+ max: { type: "number" },
5604
+ step: { type: "number", description: "Increment granularity; defaults to a sensible step for the range" },
5605
+ unit: { type: "string", description: "Short unit suffix shown after the number, e.g. 'units', '%', 'hrs'" }
5606
+ },
5607
+ required: ["min", "max"]
5608
+ }
5586
5609
  },
5587
5610
  required: ["label"]
5588
5611
  }
@@ -5599,7 +5622,8 @@ var decisionCardTool = {
5599
5622
  type: "object",
5600
5623
  description: "Pre-resolved state for transcript replay",
5601
5624
  properties: {
5602
- option: { type: "string" }
5625
+ option: { type: "string" },
5626
+ value: { type: "number", description: "Adjusted figure the user confirmed, when the option was adjustable" }
5603
5627
  },
5604
5628
  required: ["option"]
5605
5629
  }
@@ -19890,8 +19914,15 @@ function useContainerWidth2(ref) {
19890
19914
  var KEYFRAMES2 = `
19891
19915
  @keyframes dcRise{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
19892
19916
  .dc-rise{animation:dcRise 0.28s cubic-bezier(0.22,1,0.36,1) both}
19917
+ .dc-range{-webkit-appearance:none;appearance:none;width:100%;height:4px;border-radius:9999px;outline:none;cursor:pointer;background:transparent}
19918
+ .dc-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:white;border:2px solid var(--dc-thumb,#ff5000);box-shadow:0 1px 3px rgba(0,0,0,0.18);cursor:grab;transition:transform 0.12s}
19919
+ .dc-range::-webkit-slider-thumb:hover{transform:scale(1.12)}
19920
+ .dc-range:active::-webkit-slider-thumb{cursor:grabbing}
19921
+ .dc-range::-moz-range-thumb{width:12px;height:12px;border-radius:50%;background:white;border:2px solid var(--dc-thumb,#ff5000);box-shadow:0 1px 3px rgba(0,0,0,0.18);cursor:grab}
19922
+ .dc-range::-moz-range-track{height:4px;border-radius:9999px;background:transparent}
19893
19923
  @media (prefers-reduced-motion: reduce){
19894
19924
  .dc-rise{animation:none !important;opacity:1 !important;transform:none !important}
19925
+ .dc-range::-webkit-slider-thumb{transition:none}
19895
19926
  }`;
19896
19927
  function SparkCheck({ size, color }) {
19897
19928
  return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
@@ -19937,17 +19968,239 @@ function FlagPill({ flag }) {
19937
19968
  }
19938
19969
  );
19939
19970
  }
19971
+ function formatFigure(value, unit) {
19972
+ const body = Number.isInteger(value) ? `${value}` : `${Math.round(value * 100) / 100}`;
19973
+ if (!unit) return body;
19974
+ return unit === "%" ? `${body}%` : `${body} ${unit}`;
19975
+ }
19976
+ function clampToRange(raw, adjust) {
19977
+ var _a;
19978
+ const step = (_a = adjust.step) != null ? _a : Number.isInteger(adjust.min) && Number.isInteger(adjust.max) ? 1 : (adjust.max - adjust.min) / 100;
19979
+ const snapped = Math.round((raw - adjust.min) / step) * step + adjust.min;
19980
+ const fixed = Math.round(snapped * 1e6) / 1e6;
19981
+ return Math.min(adjust.max, Math.max(adjust.min, fixed));
19982
+ }
19983
+ function ValueAdjuster({
19984
+ option,
19985
+ accent,
19986
+ border,
19987
+ muted,
19988
+ onConfirm,
19989
+ onBack
19990
+ }) {
19991
+ var _a, _b;
19992
+ const adjust = option.adjust;
19993
+ const suggested = clampToRange((_a = option.value) != null ? _a : adjust.min, adjust);
19994
+ const [value, setValue] = React45.useState(suggested);
19995
+ const [text, setText] = React45.useState(String(suggested));
19996
+ const step = (_b = adjust.step) != null ? _b : Number.isInteger(adjust.min) && Number.isInteger(adjust.max) ? 1 : (adjust.max - adjust.min) / 100;
19997
+ const pct = adjust.max > adjust.min ? (value - adjust.min) / (adjust.max - adjust.min) * 100 : 0;
19998
+ const suggestedPct = adjust.max > adjust.min ? (suggested - adjust.min) / (adjust.max - adjust.min) * 100 : 0;
19999
+ const moved = value !== suggested;
20000
+ const commit = (raw) => {
20001
+ const v = clampToRange(raw, adjust);
20002
+ setValue(v);
20003
+ setText(String(v));
20004
+ };
20005
+ return /* @__PURE__ */ jsxs(
20006
+ "div",
20007
+ {
20008
+ className: "dc-rise",
20009
+ "data-testid": "decision-card-adjuster",
20010
+ style: {
20011
+ display: "flex",
20012
+ flexDirection: "column",
20013
+ gap: 12,
20014
+ border: `1px solid ${border}`,
20015
+ borderRadius: 10,
20016
+ padding: "12px 14px",
20017
+ background: "#fafafa"
20018
+ },
20019
+ children: [
20020
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 8 }, children: [
20021
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: muted }, children: [
20022
+ "Adjust \xB7 ",
20023
+ option.label
20024
+ ] }),
20025
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
20026
+ /* @__PURE__ */ jsx(
20027
+ "button",
20028
+ {
20029
+ type: "button",
20030
+ onClick: onBack,
20031
+ "data-testid": "decision-card-adjust-back",
20032
+ style: { border: "none", background: "none", padding: 0, cursor: "pointer", fontSize: 12, fontWeight: 600, color: muted },
20033
+ children: "Back"
20034
+ }
20035
+ )
20036
+ ] }),
20037
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 6 }, children: [
20038
+ /* @__PURE__ */ jsx(
20039
+ "input",
20040
+ {
20041
+ value: text,
20042
+ onChange: (e) => {
20043
+ setText(e.target.value);
20044
+ const n = Number(e.target.value);
20045
+ if (Number.isFinite(n)) setValue(clampToRange(n, adjust));
20046
+ },
20047
+ onBlur: () => commit(Number(text) || suggested),
20048
+ onKeyDown: (e) => {
20049
+ if (e.key === "Enter") {
20050
+ const v = clampToRange(Number(text) || suggested, adjust);
20051
+ commit(v);
20052
+ onConfirm(v);
20053
+ } else if (e.key === "ArrowUp") {
20054
+ e.preventDefault();
20055
+ commit(value + step);
20056
+ } else if (e.key === "ArrowDown") {
20057
+ e.preventDefault();
20058
+ commit(value - step);
20059
+ }
20060
+ },
20061
+ inputMode: "decimal",
20062
+ "aria-label": `${option.label} value`,
20063
+ "data-testid": "decision-card-adjust-input",
20064
+ style: {
20065
+ width: `${Math.max(3, String(text).length + 1)}ch`,
20066
+ fontSize: 26,
20067
+ fontWeight: 700,
20068
+ fontVariantNumeric: "tabular-nums",
20069
+ letterSpacing: "-0.02em",
20070
+ color: "var(--foreground)",
20071
+ border: "none",
20072
+ borderBottom: `2px solid ${accent}`,
20073
+ background: "transparent",
20074
+ outline: "none",
20075
+ padding: 0
20076
+ }
20077
+ }
20078
+ ),
20079
+ adjust.unit && /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: muted }, children: adjust.unit }),
20080
+ moved && /* @__PURE__ */ jsxs(
20081
+ "button",
20082
+ {
20083
+ type: "button",
20084
+ onClick: () => commit(suggested),
20085
+ "data-testid": "decision-card-adjust-reset",
20086
+ style: {
20087
+ marginLeft: 8,
20088
+ border: "none",
20089
+ background: "none",
20090
+ padding: 0,
20091
+ cursor: "pointer",
20092
+ fontSize: 11.5,
20093
+ fontWeight: 600,
20094
+ color: muted,
20095
+ textDecoration: "underline",
20096
+ textUnderlineOffset: 2
20097
+ },
20098
+ children: [
20099
+ "Reset to suggested (",
20100
+ formatFigure(suggested, adjust.unit),
20101
+ ")"
20102
+ ]
20103
+ }
20104
+ )
20105
+ ] }),
20106
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
20107
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", height: 16, display: "flex", alignItems: "center" }, children: [
20108
+ /* @__PURE__ */ jsx(
20109
+ "div",
20110
+ {
20111
+ "aria-hidden": "true",
20112
+ style: {
20113
+ position: "absolute",
20114
+ left: 0,
20115
+ right: 0,
20116
+ height: 4,
20117
+ borderRadius: 9999,
20118
+ background: `linear-gradient(to right, ${accent} ${pct}%, #e8e8e8 ${pct}%)`
20119
+ }
20120
+ }
20121
+ ),
20122
+ moved && /* @__PURE__ */ jsx(
20123
+ "div",
20124
+ {
20125
+ "aria-hidden": "true",
20126
+ title: `Suggested: ${formatFigure(suggested, adjust.unit)}`,
20127
+ style: {
20128
+ position: "absolute",
20129
+ left: `${suggestedPct}%`,
20130
+ transform: "translateX(-50%)",
20131
+ width: 2,
20132
+ height: 10,
20133
+ borderRadius: 1,
20134
+ background: muted,
20135
+ opacity: 0.55
20136
+ }
20137
+ }
20138
+ ),
20139
+ /* @__PURE__ */ jsx(
20140
+ "input",
20141
+ {
20142
+ type: "range",
20143
+ className: "dc-range",
20144
+ min: adjust.min,
20145
+ max: adjust.max,
20146
+ step,
20147
+ value,
20148
+ onChange: (e) => commit(Number(e.target.value)),
20149
+ "aria-label": `${option.label} slider`,
20150
+ "data-testid": "decision-card-adjust-slider",
20151
+ style: { position: "relative", ["--dc-thumb"]: accent }
20152
+ }
20153
+ )
20154
+ ] }),
20155
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 10.5, fontWeight: 600, color: muted, fontVariantNumeric: "tabular-nums" }, children: [
20156
+ /* @__PURE__ */ jsx("span", { children: formatFigure(adjust.min, adjust.unit) }),
20157
+ /* @__PURE__ */ jsx("span", { children: formatFigure(adjust.max, adjust.unit) })
20158
+ ] })
20159
+ ] }),
20160
+ /* @__PURE__ */ jsxs(
20161
+ "button",
20162
+ {
20163
+ type: "button",
20164
+ onClick: () => onConfirm(value),
20165
+ "data-testid": "decision-card-adjust-confirm",
20166
+ style: {
20167
+ alignSelf: "flex-start",
20168
+ border: `1px solid ${accent}`,
20169
+ background: accent,
20170
+ color: "white",
20171
+ borderRadius: 8,
20172
+ fontSize: 13,
20173
+ fontWeight: 600,
20174
+ padding: "9px 16px",
20175
+ cursor: "pointer",
20176
+ fontVariantNumeric: "tabular-nums",
20177
+ transition: "opacity 0.15s"
20178
+ },
20179
+ onMouseEnter: (e) => e.currentTarget.style.opacity = "0.88",
20180
+ onMouseLeave: (e) => e.currentTarget.style.opacity = "1",
20181
+ children: [
20182
+ option.label,
20183
+ " \xB7 ",
20184
+ formatFigure(value, adjust.unit)
20185
+ ]
20186
+ }
20187
+ )
20188
+ ]
20189
+ }
20190
+ );
20191
+ }
19940
20192
  function DecisionCardRenderer({
19941
20193
  data,
19942
20194
  resolved = null,
19943
20195
  onResolve,
19944
20196
  onOpenSource
19945
20197
  }) {
19946
- var _a, _b;
20198
+ var _a, _b, _c;
19947
20199
  const { BORDER: BORDER4, MUTED: MUTED2, ACCENT: ACCENT2, ACCENT_SOFT: ACCENT_SOFT2 } = useTheme();
19948
20200
  const rootRef = React45.useRef(null);
19949
20201
  const width = useContainerWidth2(rootRef);
19950
20202
  const stacked = width > 0 && width < STACK_BELOW;
20203
+ const [adjustingIdx, setAdjustingIdx] = React45.useState(null);
19951
20204
  const options = (_a = data.options) != null ? _a : [];
19952
20205
  const recommendedIdx = Math.max(
19953
20206
  0,
@@ -20102,7 +20355,20 @@ function DecisionCardRenderer({
20102
20355
  ]
20103
20356
  }
20104
20357
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
20105
- /* @__PURE__ */ jsx(
20358
+ adjustingIdx != null && ((_b = options[adjustingIdx]) == null ? void 0 : _b.adjust) ? /* @__PURE__ */ jsx(
20359
+ ValueAdjuster,
20360
+ {
20361
+ option: options[adjustingIdx],
20362
+ accent: ACCENT2,
20363
+ border: BORDER4,
20364
+ muted: MUTED2,
20365
+ onBack: () => setAdjustingIdx(null),
20366
+ onConfirm: (value) => {
20367
+ setAdjustingIdx(null);
20368
+ onResolve == null ? void 0 : onResolve(options[adjustingIdx].label, value);
20369
+ }
20370
+ }
20371
+ ) : /* @__PURE__ */ jsx(
20106
20372
  "div",
20107
20373
  {
20108
20374
  style: {
@@ -20112,15 +20378,21 @@ function DecisionCardRenderer({
20112
20378
  gap: 8
20113
20379
  },
20114
20380
  children: options.map((opt, idx) => {
20115
- var _a2;
20381
+ var _a2, _b2;
20116
20382
  const primary = idx === recommendedIdx;
20117
- return /* @__PURE__ */ jsx(
20383
+ const adjustable = Boolean(opt.adjust);
20384
+ return /* @__PURE__ */ jsxs(
20118
20385
  "button",
20119
20386
  {
20120
20387
  type: "button",
20121
- onClick: () => onResolve == null ? void 0 : onResolve(opt.label),
20388
+ onClick: () => adjustable ? setAdjustingIdx(idx) : onResolve == null ? void 0 : onResolve(opt.label),
20389
+ "data-adjustable": adjustable || void 0,
20122
20390
  style: {
20123
20391
  width: stacked ? "100%" : "auto",
20392
+ display: "inline-flex",
20393
+ alignItems: "center",
20394
+ justifyContent: stacked ? "flex-start" : "center",
20395
+ gap: 7,
20124
20396
  textAlign: stacked ? "left" : "center",
20125
20397
  border: `1px solid ${primary ? ACCENT2 : BORDER4}`,
20126
20398
  background: primary ? ACCENT2 : "white",
@@ -20148,7 +20420,28 @@ function DecisionCardRenderer({
20148
20420
  e.currentTarget.style.background = "white";
20149
20421
  }
20150
20422
  },
20151
- children: opt.label
20423
+ children: [
20424
+ opt.label,
20425
+ adjustable && typeof opt.value === "number" && /* @__PURE__ */ jsxs(
20426
+ "span",
20427
+ {
20428
+ style: {
20429
+ fontSize: 11.5,
20430
+ fontWeight: 700,
20431
+ fontVariantNumeric: "tabular-nums",
20432
+ padding: "1px 7px",
20433
+ borderRadius: 9999,
20434
+ background: primary ? "rgba(255,255,255,0.22)" : "#f2f2f2",
20435
+ color: primary ? "white" : MUTED2,
20436
+ lineHeight: 1.5
20437
+ },
20438
+ children: [
20439
+ formatFigure(opt.value, (_b2 = opt.adjust) == null ? void 0 : _b2.unit),
20440
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { marginLeft: 4, opacity: 0.8 }, children: "\u270E" })
20441
+ ]
20442
+ }
20443
+ )
20444
+ ]
20152
20445
  },
20153
20446
  (_a2 = opt.id) != null ? _a2 : opt.label
20154
20447
  );
@@ -20187,7 +20480,7 @@ function DecisionCardRenderer({
20187
20480
  strokeLinejoin: "round"
20188
20481
  }
20189
20482
  ) }),
20190
- (_b = data.source.label) != null ? _b : "View source for this figure"
20483
+ (_c = data.source.label) != null ? _c : "View source for this figure"
20191
20484
  ]
20192
20485
  }
20193
20486
  )
@@ -20196,20 +20489,27 @@ function DecisionCardRenderer({
20196
20489
  }
20197
20490
  );
20198
20491
  }
20492
+ function composeResolution(p, option, value) {
20493
+ var _a, _b, _c;
20494
+ if (value == null) return option;
20495
+ const unit = (_c = (_b = ((_a = p.options) != null ? _a : []).find((o) => o.label === option)) == null ? void 0 : _b.adjust) == null ? void 0 : _c.unit;
20496
+ return `${option} \xB7 ${formatFigure(value, unit)}`;
20497
+ }
20199
20498
  function DecisionCardResolver(p) {
20200
- var _a, _b, _c, _d;
20499
+ var _a, _b;
20201
20500
  const { resolvedDecisions, onDecisionResolve, onDecisionSource } = useGenUIInteraction();
20202
20501
  const decisionId = (_a = p.id) != null ? _a : p.title;
20203
20502
  const hostResolved = resolvedDecisions == null ? void 0 : resolvedDecisions[decisionId];
20204
- const initial = (_c = hostResolved != null ? hostResolved : (_b = p.resolved) == null ? void 0 : _b.option) != null ? _c : null;
20503
+ const initial = hostResolved != null ? hostResolved : p.resolved ? composeResolution(p, p.resolved.option, p.resolved.value) : null;
20205
20504
  const [localResolved, setLocalResolved] = React45.useState(initial);
20206
20505
  React45.useEffect(() => {
20207
20506
  if (hostResolved !== void 0) setLocalResolved(hostResolved);
20208
20507
  }, [hostResolved]);
20209
20508
  const resolved = hostResolved != null ? hostResolved : localResolved;
20210
- const handleResolve = (option) => {
20211
- setLocalResolved(option);
20212
- onDecisionResolve == null ? void 0 : onDecisionResolve(decisionId, option);
20509
+ const handleResolve = (option, value) => {
20510
+ const resolution = composeResolution(p, option, value);
20511
+ setLocalResolved(resolution);
20512
+ onDecisionResolve == null ? void 0 : onDecisionResolve(decisionId, resolution);
20213
20513
  };
20214
20514
  const handleSource = () => {
20215
20515
  var _a2;
@@ -20218,7 +20518,7 @@ function DecisionCardResolver(p) {
20218
20518
  window.open(p.source.url, "_blank", "noopener,noreferrer");
20219
20519
  }
20220
20520
  };
20221
- return /* @__PURE__ */ jsx(ComponentActions, { filename: (_d = p.title) != null ? _d : "decision-card", children: /* @__PURE__ */ jsx(
20521
+ return /* @__PURE__ */ jsx(ComponentActions, { filename: (_b = p.title) != null ? _b : "decision-card", children: /* @__PURE__ */ jsx(
20222
20522
  DecisionCardRenderer,
20223
20523
  {
20224
20524
  data: p,