@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/resolver.cjs CHANGED
@@ -5553,7 +5553,14 @@ var decisionCardSchema = zod.z.object({
5553
5553
  zod.z.object({
5554
5554
  id: zod.z.string().optional(),
5555
5555
  label: zod.z.string(),
5556
- recommended: zod.z.boolean().optional()
5556
+ recommended: zod.z.boolean().optional(),
5557
+ value: zod.z.number().optional(),
5558
+ adjust: zod.z.object({
5559
+ min: zod.z.number(),
5560
+ max: zod.z.number(),
5561
+ step: zod.z.number().optional(),
5562
+ unit: zod.z.string().optional()
5563
+ }).optional()
5557
5564
  })
5558
5565
  ).min(1).max(5),
5559
5566
  source: zod.z.object({
@@ -5561,7 +5568,8 @@ var decisionCardSchema = zod.z.object({
5561
5568
  url: zod.z.string().optional()
5562
5569
  }).optional(),
5563
5570
  resolved: zod.z.object({
5564
- option: zod.z.string()
5571
+ option: zod.z.string(),
5572
+ value: zod.z.number().optional()
5565
5573
  }).optional()
5566
5574
  });
5567
5575
  var decisionCardTool = {
@@ -5604,13 +5612,28 @@ var decisionCardTool = {
5604
5612
  type: "array",
5605
5613
  minItems: 1,
5606
5614
  maxItems: 5,
5607
- description: "Mutually-exclusive resolution choices; mark one `recommended` to make it primary",
5615
+ 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.",
5608
5616
  items: {
5609
5617
  type: "object",
5610
5618
  properties: {
5611
5619
  id: { type: "string" },
5612
5620
  label: { type: "string" },
5613
- recommended: { type: "boolean" }
5621
+ recommended: { type: "boolean" },
5622
+ value: {
5623
+ type: "number",
5624
+ description: "Suggested numeric figure for this option (the starting point of the adjuster)"
5625
+ },
5626
+ adjust: {
5627
+ type: "object",
5628
+ description: "Allow the user to tune `value` within this inclusive range before resolving",
5629
+ properties: {
5630
+ min: { type: "number" },
5631
+ max: { type: "number" },
5632
+ step: { type: "number", description: "Increment granularity; defaults to a sensible step for the range" },
5633
+ unit: { type: "string", description: "Short unit suffix shown after the number, e.g. 'units', '%', 'hrs'" }
5634
+ },
5635
+ required: ["min", "max"]
5636
+ }
5614
5637
  },
5615
5638
  required: ["label"]
5616
5639
  }
@@ -5627,7 +5650,8 @@ var decisionCardTool = {
5627
5650
  type: "object",
5628
5651
  description: "Pre-resolved state for transcript replay",
5629
5652
  properties: {
5630
- option: { type: "string" }
5653
+ option: { type: "string" },
5654
+ value: { type: "number", description: "Adjusted figure the user confirmed, when the option was adjustable" }
5631
5655
  },
5632
5656
  required: ["option"]
5633
5657
  }
@@ -19918,8 +19942,15 @@ function useContainerWidth2(ref) {
19918
19942
  var KEYFRAMES2 = `
19919
19943
  @keyframes dcRise{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
19920
19944
  .dc-rise{animation:dcRise 0.28s cubic-bezier(0.22,1,0.36,1) both}
19945
+ .dc-range{-webkit-appearance:none;appearance:none;width:100%;height:4px;border-radius:9999px;outline:none;cursor:pointer;background:transparent}
19946
+ .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}
19947
+ .dc-range::-webkit-slider-thumb:hover{transform:scale(1.12)}
19948
+ .dc-range:active::-webkit-slider-thumb{cursor:grabbing}
19949
+ .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}
19950
+ .dc-range::-moz-range-track{height:4px;border-radius:9999px;background:transparent}
19921
19951
  @media (prefers-reduced-motion: reduce){
19922
19952
  .dc-rise{animation:none !important;opacity:1 !important;transform:none !important}
19953
+ .dc-range::-webkit-slider-thumb{transition:none}
19923
19954
  }`;
19924
19955
  function SparkCheck({ size, color }) {
19925
19956
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -19965,17 +19996,239 @@ function FlagPill({ flag }) {
19965
19996
  }
19966
19997
  );
19967
19998
  }
19999
+ function formatFigure(value, unit) {
20000
+ const body = Number.isInteger(value) ? `${value}` : `${Math.round(value * 100) / 100}`;
20001
+ if (!unit) return body;
20002
+ return unit === "%" ? `${body}%` : `${body} ${unit}`;
20003
+ }
20004
+ function clampToRange(raw, adjust) {
20005
+ var _a;
20006
+ const step = (_a = adjust.step) != null ? _a : Number.isInteger(adjust.min) && Number.isInteger(adjust.max) ? 1 : (adjust.max - adjust.min) / 100;
20007
+ const snapped = Math.round((raw - adjust.min) / step) * step + adjust.min;
20008
+ const fixed = Math.round(snapped * 1e6) / 1e6;
20009
+ return Math.min(adjust.max, Math.max(adjust.min, fixed));
20010
+ }
20011
+ function ValueAdjuster({
20012
+ option,
20013
+ accent,
20014
+ border,
20015
+ muted,
20016
+ onConfirm,
20017
+ onBack
20018
+ }) {
20019
+ var _a, _b;
20020
+ const adjust = option.adjust;
20021
+ const suggested = clampToRange((_a = option.value) != null ? _a : adjust.min, adjust);
20022
+ const [value, setValue] = React45__default.default.useState(suggested);
20023
+ const [text, setText] = React45__default.default.useState(String(suggested));
20024
+ const step = (_b = adjust.step) != null ? _b : Number.isInteger(adjust.min) && Number.isInteger(adjust.max) ? 1 : (adjust.max - adjust.min) / 100;
20025
+ const pct = adjust.max > adjust.min ? (value - adjust.min) / (adjust.max - adjust.min) * 100 : 0;
20026
+ const suggestedPct = adjust.max > adjust.min ? (suggested - adjust.min) / (adjust.max - adjust.min) * 100 : 0;
20027
+ const moved = value !== suggested;
20028
+ const commit = (raw) => {
20029
+ const v = clampToRange(raw, adjust);
20030
+ setValue(v);
20031
+ setText(String(v));
20032
+ };
20033
+ return /* @__PURE__ */ jsxRuntime.jsxs(
20034
+ "div",
20035
+ {
20036
+ className: "dc-rise",
20037
+ "data-testid": "decision-card-adjuster",
20038
+ style: {
20039
+ display: "flex",
20040
+ flexDirection: "column",
20041
+ gap: 12,
20042
+ border: `1px solid ${border}`,
20043
+ borderRadius: 10,
20044
+ padding: "12px 14px",
20045
+ background: "#fafafa"
20046
+ },
20047
+ children: [
20048
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 8 }, children: [
20049
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: muted }, children: [
20050
+ "Adjust \xB7 ",
20051
+ option.label
20052
+ ] }),
20053
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
20054
+ /* @__PURE__ */ jsxRuntime.jsx(
20055
+ "button",
20056
+ {
20057
+ type: "button",
20058
+ onClick: onBack,
20059
+ "data-testid": "decision-card-adjust-back",
20060
+ style: { border: "none", background: "none", padding: 0, cursor: "pointer", fontSize: 12, fontWeight: 600, color: muted },
20061
+ children: "Back"
20062
+ }
20063
+ )
20064
+ ] }),
20065
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: 6 }, children: [
20066
+ /* @__PURE__ */ jsxRuntime.jsx(
20067
+ "input",
20068
+ {
20069
+ value: text,
20070
+ onChange: (e) => {
20071
+ setText(e.target.value);
20072
+ const n = Number(e.target.value);
20073
+ if (Number.isFinite(n)) setValue(clampToRange(n, adjust));
20074
+ },
20075
+ onBlur: () => commit(Number(text) || suggested),
20076
+ onKeyDown: (e) => {
20077
+ if (e.key === "Enter") {
20078
+ const v = clampToRange(Number(text) || suggested, adjust);
20079
+ commit(v);
20080
+ onConfirm(v);
20081
+ } else if (e.key === "ArrowUp") {
20082
+ e.preventDefault();
20083
+ commit(value + step);
20084
+ } else if (e.key === "ArrowDown") {
20085
+ e.preventDefault();
20086
+ commit(value - step);
20087
+ }
20088
+ },
20089
+ inputMode: "decimal",
20090
+ "aria-label": `${option.label} value`,
20091
+ "data-testid": "decision-card-adjust-input",
20092
+ style: {
20093
+ width: `${Math.max(3, String(text).length + 1)}ch`,
20094
+ fontSize: 26,
20095
+ fontWeight: 700,
20096
+ fontVariantNumeric: "tabular-nums",
20097
+ letterSpacing: "-0.02em",
20098
+ color: "var(--foreground)",
20099
+ border: "none",
20100
+ borderBottom: `2px solid ${accent}`,
20101
+ background: "transparent",
20102
+ outline: "none",
20103
+ padding: 0
20104
+ }
20105
+ }
20106
+ ),
20107
+ adjust.unit && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13, fontWeight: 600, color: muted }, children: adjust.unit }),
20108
+ moved && /* @__PURE__ */ jsxRuntime.jsxs(
20109
+ "button",
20110
+ {
20111
+ type: "button",
20112
+ onClick: () => commit(suggested),
20113
+ "data-testid": "decision-card-adjust-reset",
20114
+ style: {
20115
+ marginLeft: 8,
20116
+ border: "none",
20117
+ background: "none",
20118
+ padding: 0,
20119
+ cursor: "pointer",
20120
+ fontSize: 11.5,
20121
+ fontWeight: 600,
20122
+ color: muted,
20123
+ textDecoration: "underline",
20124
+ textUnderlineOffset: 2
20125
+ },
20126
+ children: [
20127
+ "Reset to suggested (",
20128
+ formatFigure(suggested, adjust.unit),
20129
+ ")"
20130
+ ]
20131
+ }
20132
+ )
20133
+ ] }),
20134
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
20135
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", height: 16, display: "flex", alignItems: "center" }, children: [
20136
+ /* @__PURE__ */ jsxRuntime.jsx(
20137
+ "div",
20138
+ {
20139
+ "aria-hidden": "true",
20140
+ style: {
20141
+ position: "absolute",
20142
+ left: 0,
20143
+ right: 0,
20144
+ height: 4,
20145
+ borderRadius: 9999,
20146
+ background: `linear-gradient(to right, ${accent} ${pct}%, #e8e8e8 ${pct}%)`
20147
+ }
20148
+ }
20149
+ ),
20150
+ moved && /* @__PURE__ */ jsxRuntime.jsx(
20151
+ "div",
20152
+ {
20153
+ "aria-hidden": "true",
20154
+ title: `Suggested: ${formatFigure(suggested, adjust.unit)}`,
20155
+ style: {
20156
+ position: "absolute",
20157
+ left: `${suggestedPct}%`,
20158
+ transform: "translateX(-50%)",
20159
+ width: 2,
20160
+ height: 10,
20161
+ borderRadius: 1,
20162
+ background: muted,
20163
+ opacity: 0.55
20164
+ }
20165
+ }
20166
+ ),
20167
+ /* @__PURE__ */ jsxRuntime.jsx(
20168
+ "input",
20169
+ {
20170
+ type: "range",
20171
+ className: "dc-range",
20172
+ min: adjust.min,
20173
+ max: adjust.max,
20174
+ step,
20175
+ value,
20176
+ onChange: (e) => commit(Number(e.target.value)),
20177
+ "aria-label": `${option.label} slider`,
20178
+ "data-testid": "decision-card-adjust-slider",
20179
+ style: { position: "relative", ["--dc-thumb"]: accent }
20180
+ }
20181
+ )
20182
+ ] }),
20183
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 10.5, fontWeight: 600, color: muted, fontVariantNumeric: "tabular-nums" }, children: [
20184
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatFigure(adjust.min, adjust.unit) }),
20185
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatFigure(adjust.max, adjust.unit) })
20186
+ ] })
20187
+ ] }),
20188
+ /* @__PURE__ */ jsxRuntime.jsxs(
20189
+ "button",
20190
+ {
20191
+ type: "button",
20192
+ onClick: () => onConfirm(value),
20193
+ "data-testid": "decision-card-adjust-confirm",
20194
+ style: {
20195
+ alignSelf: "flex-start",
20196
+ border: `1px solid ${accent}`,
20197
+ background: accent,
20198
+ color: "white",
20199
+ borderRadius: 8,
20200
+ fontSize: 13,
20201
+ fontWeight: 600,
20202
+ padding: "9px 16px",
20203
+ cursor: "pointer",
20204
+ fontVariantNumeric: "tabular-nums",
20205
+ transition: "opacity 0.15s"
20206
+ },
20207
+ onMouseEnter: (e) => e.currentTarget.style.opacity = "0.88",
20208
+ onMouseLeave: (e) => e.currentTarget.style.opacity = "1",
20209
+ children: [
20210
+ option.label,
20211
+ " \xB7 ",
20212
+ formatFigure(value, adjust.unit)
20213
+ ]
20214
+ }
20215
+ )
20216
+ ]
20217
+ }
20218
+ );
20219
+ }
19968
20220
  function DecisionCardRenderer({
19969
20221
  data,
19970
20222
  resolved = null,
19971
20223
  onResolve,
19972
20224
  onOpenSource
19973
20225
  }) {
19974
- var _a, _b;
20226
+ var _a, _b, _c;
19975
20227
  const { BORDER: BORDER4, MUTED: MUTED2, ACCENT: ACCENT2, ACCENT_SOFT: ACCENT_SOFT2 } = useTheme();
19976
20228
  const rootRef = React45__default.default.useRef(null);
19977
20229
  const width = useContainerWidth2(rootRef);
19978
20230
  const stacked = width > 0 && width < STACK_BELOW;
20231
+ const [adjustingIdx, setAdjustingIdx] = React45__default.default.useState(null);
19979
20232
  const options = (_a = data.options) != null ? _a : [];
19980
20233
  const recommendedIdx = Math.max(
19981
20234
  0,
@@ -20130,7 +20383,20 @@ function DecisionCardRenderer({
20130
20383
  ]
20131
20384
  }
20132
20385
  ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20133
- /* @__PURE__ */ jsxRuntime.jsx(
20386
+ adjustingIdx != null && ((_b = options[adjustingIdx]) == null ? void 0 : _b.adjust) ? /* @__PURE__ */ jsxRuntime.jsx(
20387
+ ValueAdjuster,
20388
+ {
20389
+ option: options[adjustingIdx],
20390
+ accent: ACCENT2,
20391
+ border: BORDER4,
20392
+ muted: MUTED2,
20393
+ onBack: () => setAdjustingIdx(null),
20394
+ onConfirm: (value) => {
20395
+ setAdjustingIdx(null);
20396
+ onResolve == null ? void 0 : onResolve(options[adjustingIdx].label, value);
20397
+ }
20398
+ }
20399
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
20134
20400
  "div",
20135
20401
  {
20136
20402
  style: {
@@ -20140,15 +20406,21 @@ function DecisionCardRenderer({
20140
20406
  gap: 8
20141
20407
  },
20142
20408
  children: options.map((opt, idx) => {
20143
- var _a2;
20409
+ var _a2, _b2;
20144
20410
  const primary = idx === recommendedIdx;
20145
- return /* @__PURE__ */ jsxRuntime.jsx(
20411
+ const adjustable = Boolean(opt.adjust);
20412
+ return /* @__PURE__ */ jsxRuntime.jsxs(
20146
20413
  "button",
20147
20414
  {
20148
20415
  type: "button",
20149
- onClick: () => onResolve == null ? void 0 : onResolve(opt.label),
20416
+ onClick: () => adjustable ? setAdjustingIdx(idx) : onResolve == null ? void 0 : onResolve(opt.label),
20417
+ "data-adjustable": adjustable || void 0,
20150
20418
  style: {
20151
20419
  width: stacked ? "100%" : "auto",
20420
+ display: "inline-flex",
20421
+ alignItems: "center",
20422
+ justifyContent: stacked ? "flex-start" : "center",
20423
+ gap: 7,
20152
20424
  textAlign: stacked ? "left" : "center",
20153
20425
  border: `1px solid ${primary ? ACCENT2 : BORDER4}`,
20154
20426
  background: primary ? ACCENT2 : "white",
@@ -20176,7 +20448,28 @@ function DecisionCardRenderer({
20176
20448
  e.currentTarget.style.background = "white";
20177
20449
  }
20178
20450
  },
20179
- children: opt.label
20451
+ children: [
20452
+ opt.label,
20453
+ adjustable && typeof opt.value === "number" && /* @__PURE__ */ jsxRuntime.jsxs(
20454
+ "span",
20455
+ {
20456
+ style: {
20457
+ fontSize: 11.5,
20458
+ fontWeight: 700,
20459
+ fontVariantNumeric: "tabular-nums",
20460
+ padding: "1px 7px",
20461
+ borderRadius: 9999,
20462
+ background: primary ? "rgba(255,255,255,0.22)" : "#f2f2f2",
20463
+ color: primary ? "white" : MUTED2,
20464
+ lineHeight: 1.5
20465
+ },
20466
+ children: [
20467
+ formatFigure(opt.value, (_b2 = opt.adjust) == null ? void 0 : _b2.unit),
20468
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: { marginLeft: 4, opacity: 0.8 }, children: "\u270E" })
20469
+ ]
20470
+ }
20471
+ )
20472
+ ]
20180
20473
  },
20181
20474
  (_a2 = opt.id) != null ? _a2 : opt.label
20182
20475
  );
@@ -20215,7 +20508,7 @@ function DecisionCardRenderer({
20215
20508
  strokeLinejoin: "round"
20216
20509
  }
20217
20510
  ) }),
20218
- (_b = data.source.label) != null ? _b : "View source for this figure"
20511
+ (_c = data.source.label) != null ? _c : "View source for this figure"
20219
20512
  ]
20220
20513
  }
20221
20514
  )
@@ -20224,20 +20517,27 @@ function DecisionCardRenderer({
20224
20517
  }
20225
20518
  );
20226
20519
  }
20520
+ function composeResolution(p, option, value) {
20521
+ var _a, _b, _c;
20522
+ if (value == null) return option;
20523
+ const unit = (_c = (_b = ((_a = p.options) != null ? _a : []).find((o) => o.label === option)) == null ? void 0 : _b.adjust) == null ? void 0 : _c.unit;
20524
+ return `${option} \xB7 ${formatFigure(value, unit)}`;
20525
+ }
20227
20526
  function DecisionCardResolver(p) {
20228
- var _a, _b, _c, _d;
20527
+ var _a, _b;
20229
20528
  const { resolvedDecisions, onDecisionResolve, onDecisionSource } = useGenUIInteraction();
20230
20529
  const decisionId = (_a = p.id) != null ? _a : p.title;
20231
20530
  const hostResolved = resolvedDecisions == null ? void 0 : resolvedDecisions[decisionId];
20232
- const initial = (_c = hostResolved != null ? hostResolved : (_b = p.resolved) == null ? void 0 : _b.option) != null ? _c : null;
20531
+ const initial = hostResolved != null ? hostResolved : p.resolved ? composeResolution(p, p.resolved.option, p.resolved.value) : null;
20233
20532
  const [localResolved, setLocalResolved] = React45__default.default.useState(initial);
20234
20533
  React45__default.default.useEffect(() => {
20235
20534
  if (hostResolved !== void 0) setLocalResolved(hostResolved);
20236
20535
  }, [hostResolved]);
20237
20536
  const resolved = hostResolved != null ? hostResolved : localResolved;
20238
- const handleResolve = (option) => {
20239
- setLocalResolved(option);
20240
- onDecisionResolve == null ? void 0 : onDecisionResolve(decisionId, option);
20537
+ const handleResolve = (option, value) => {
20538
+ const resolution = composeResolution(p, option, value);
20539
+ setLocalResolved(resolution);
20540
+ onDecisionResolve == null ? void 0 : onDecisionResolve(decisionId, resolution);
20241
20541
  };
20242
20542
  const handleSource = () => {
20243
20543
  var _a2;
@@ -20246,7 +20546,7 @@ function DecisionCardResolver(p) {
20246
20546
  window.open(p.source.url, "_blank", "noopener,noreferrer");
20247
20547
  }
20248
20548
  };
20249
- return /* @__PURE__ */ jsxRuntime.jsx(ComponentActions, { filename: (_d = p.title) != null ? _d : "decision-card", children: /* @__PURE__ */ jsxRuntime.jsx(
20549
+ return /* @__PURE__ */ jsxRuntime.jsx(ComponentActions, { filename: (_b = p.title) != null ? _b : "decision-card", children: /* @__PURE__ */ jsxRuntime.jsx(
20250
20550
  DecisionCardRenderer,
20251
20551
  {
20252
20552
  data: p,