@canvas-components/react-components 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1128,16 +1128,31 @@ var operators = [
1128
1128
  ];
1129
1129
  function ReactFilterPanel(props) {
1130
1130
  const { request } = props;
1131
- const [draft, setDraft] = React.useState(request?.value ?? null);
1131
+ const [draft, setDraft] = React.useState(() => createDraft(request?.value));
1132
1132
  const [tab, setTab] = React.useState(resolveTab(request?.value));
1133
1133
  const [options, setOptions] = React.useState(request?.options ?? []);
1134
1134
  const [loading, setLoading] = React.useState(Boolean(request?.loadOptions));
1135
+ const conditionListRef = React.useRef(null);
1136
+ const [conditionListOverflowing, setConditionListOverflowing] = React.useState(false);
1135
1137
  React.useEffect(() => {
1136
- setDraft(request?.value ?? null);
1138
+ setDraft(createDraft(request?.value));
1137
1139
  setTab(resolveTab(request?.value));
1138
1140
  setOptions(request?.options ?? []);
1139
1141
  setLoading(Boolean(request?.loadOptions));
1140
1142
  }, [request]);
1143
+ React.useLayoutEffect(() => {
1144
+ const element = conditionListRef.current;
1145
+ if (!element || tab !== "condition") {
1146
+ setConditionListOverflowing(false);
1147
+ return;
1148
+ }
1149
+ const measure = () => setConditionListOverflowing(element.scrollHeight > element.clientHeight + 1);
1150
+ measure();
1151
+ if (typeof ResizeObserver === "undefined") return;
1152
+ const observer = new ResizeObserver(measure);
1153
+ observer.observe(element);
1154
+ return () => observer.disconnect();
1155
+ }, [draft?.conditions, tab]);
1141
1156
  React.useEffect(() => {
1142
1157
  if (!request?.loadOptions) return;
1143
1158
  const controller = new AbortController();
@@ -1172,6 +1187,20 @@ function ReactFilterPanel(props) {
1172
1187
  const allChecked = visibleKeys.length > 0 && selectedCount === visibleKeys.length;
1173
1188
  const allCount = request.options.find((item) => item.kind === "all")?.count ?? visibleOptions.filter((item) => item.kind === "value").reduce((sum, item) => sum + item.count, 0);
1174
1189
  const update = (patch) => setDraft((current) => ({ ...current ?? request.value, ...patch }));
1190
+ const updateConditions = (conditions) => {
1191
+ const first = conditions[0] ?? createEmptyCondition();
1192
+ const second = conditions[1] ?? createEmptyCondition();
1193
+ update({
1194
+ conditions,
1195
+ filterType: "condition",
1196
+ conditionOperator: first.operator,
1197
+ conditionValue: first.value ?? "",
1198
+ conditionSecondValue: first.secondValue ?? "",
1199
+ secondConditionOperator: second.operator,
1200
+ secondConditionValue: second.value ?? "",
1201
+ secondConditionSecondValue: second.secondValue ?? ""
1202
+ });
1203
+ };
1175
1204
  const toggle = (keys, checked) => {
1176
1205
  const next = new Set(draft.selectedKeys ?? []);
1177
1206
  keys.forEach((key) => checked ? next.add(key) : next.delete(key));
@@ -1278,97 +1307,138 @@ function ReactFilterPanel(props) {
1278
1307
  )
1279
1308
  }
1280
1309
  ) })
1281
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12, display: "grid", gap: 12, minHeight: 290, alignContent: "start", flex: "1 1 auto" }, children: [
1282
- /* @__PURE__ */ jsxRuntime.jsx(
1283
- ConditionEditor,
1284
- {
1285
- label: request.texts.firstConditionLabel,
1286
- operator: draft.conditionOperator ?? "none",
1287
- value: draft.conditionValue ?? "",
1288
- secondValue: draft.conditionSecondValue ?? "",
1289
- texts: request.texts,
1290
- onChange: (patch) => update(patch),
1291
- onSubmit: apply
1292
- }
1293
- ),
1294
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1295
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: fieldLabelStyle, children: request.texts.relationLabel }),
1296
- /* @__PURE__ */ jsxRuntime.jsx(
1297
- ReactSelect,
1298
- {
1299
- value: draft.conditionRelation === "or" ? "or" : "and",
1300
- allowClear: false,
1301
- options: [{ label: request.texts.relationAndLabel, value: "and" }, { label: request.texts.relationOrLabel, value: "or" }],
1302
- onChange: (value) => update({ conditionRelation: value, filterType: "condition" }),
1303
- style: { width: "100%" }
1304
- }
1305
- )
1306
- ] }),
1307
- /* @__PURE__ */ jsxRuntime.jsx(
1308
- ConditionEditor,
1310
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 12, display: "flex", flexDirection: "column", gap: 10, minHeight: 0, flex: "1 1 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1311
+ "div",
1312
+ {
1313
+ ref: conditionListRef,
1314
+ className: "react-filter-panel-condition-list",
1315
+ style: { ...conditionListStyle, overflowY: conditionListOverflowing ? "auto" : "hidden" },
1316
+ children: (draft.conditions ?? []).map((condition, index, conditions) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
1317
+ index > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "react-filter-panel-condition-relation", style: conditionRowStyle, children: [
1318
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: inlineLabelStyle, children: request.texts.relationLabel }),
1319
+ /* @__PURE__ */ jsxRuntime.jsx(
1320
+ ReactSelect,
1321
+ {
1322
+ value: draft.conditionRelation === "or" ? "or" : "and",
1323
+ allowClear: false,
1324
+ options: [{ label: request.texts.relationAndLabel, value: "and" }, { label: request.texts.relationOrLabel, value: "or" }],
1325
+ onChange: (value) => update({ conditionRelation: value, filterType: "condition" }),
1326
+ style: { width: "100%" }
1327
+ }
1328
+ ),
1329
+ /* @__PURE__ */ jsxRuntime.jsx("span", {})
1330
+ ] }) : null,
1331
+ /* @__PURE__ */ jsxRuntime.jsx(
1332
+ ConditionEditor,
1333
+ {
1334
+ label: resolveConditionLabel(request.texts, index),
1335
+ condition,
1336
+ texts: request.texts,
1337
+ canDelete: conditions.length > 1,
1338
+ onDelete: () => updateConditions(conditions.filter((_item, itemIndex) => itemIndex !== index)),
1339
+ onChange: (nextCondition) => updateConditions(conditions.map((item, itemIndex) => itemIndex === index ? nextCondition : item)),
1340
+ onSubmit: apply
1341
+ }
1342
+ )
1343
+ ] }, index))
1344
+ }
1345
+ ) }),
1346
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "react-filter-panel-footer", style: footerStyle, children: [
1347
+ tab === "condition" ? /* @__PURE__ */ jsxRuntime.jsxs(
1348
+ "button",
1309
1349
  {
1310
- second: true,
1311
- label: request.texts.secondConditionLabel,
1312
- operator: draft.secondConditionOperator ?? "none",
1313
- value: draft.secondConditionValue ?? "",
1314
- secondValue: draft.secondConditionSecondValue ?? "",
1315
- texts: request.texts,
1316
- onChange: (patch) => update(patch),
1317
- onSubmit: apply
1350
+ type: "button",
1351
+ style: addConditionStyle,
1352
+ onClick: () => updateConditions([...draft.conditions ?? [], createEmptyCondition()]),
1353
+ children: [
1354
+ "+ ",
1355
+ request.texts.addConditionAction ?? "\u65B0\u589E\u6761\u4EF6"
1356
+ ]
1318
1357
  }
1319
- )
1320
- ] }),
1321
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1322
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: actionStyle(false), onClick: () => {
1323
- request.onReset();
1324
- request.onClose();
1325
- }, children: request.texts.resetAction }),
1326
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: actionStyle(true), onClick: apply, children: request.texts.applyAction })
1358
+ ) : null,
1359
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "react-filter-panel-footer-actions", style: footerActionsStyle, children: [
1360
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: actionStyle(false), onClick: () => {
1361
+ request.onReset();
1362
+ request.onClose();
1363
+ }, children: request.texts.resetAction }),
1364
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: actionStyle(true), onClick: apply, children: request.texts.applyAction })
1365
+ ] })
1327
1366
  ] })
1328
1367
  ] })
1329
1368
  }
1330
1369
  );
1331
1370
  }
1332
1371
  function ConditionEditor(props) {
1333
- const prefix = props.second ? "secondCondition" : "condition";
1334
- const hidesValue = ["none", "blank", "notBlank"].includes(props.operator);
1335
- const needsSecond = ["between", "notBetween"].includes(props.operator);
1336
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1337
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: fieldLabelStyle, children: props.label }),
1338
- /* @__PURE__ */ jsxRuntime.jsx(
1339
- ReactSelect,
1340
- {
1341
- value: props.operator,
1342
- options: operators,
1343
- allowClear: false,
1344
- onChange: (value) => props.onChange({ [`${prefix}Operator`]: value, filterType: "condition" }),
1345
- style: { width: "100%" }
1346
- }
1347
- ),
1348
- hidesValue ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gap: 8, marginTop: 8 }, children: [
1372
+ const hidesValue = ["none", "blank", "notBlank"].includes(props.condition.operator);
1373
+ const needsSecond = ["between", "notBetween"].includes(props.condition.operator);
1374
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: conditionEditorStyle, children: [
1375
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "react-filter-panel-condition-row", style: conditionRowStyle, children: [
1376
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: inlineLabelStyle, children: props.label }),
1377
+ /* @__PURE__ */ jsxRuntime.jsx(
1378
+ ReactSelect,
1379
+ {
1380
+ value: props.condition.operator,
1381
+ options: operators,
1382
+ allowClear: false,
1383
+ onChange: (value) => props.onChange({ ...props.condition, operator: value }),
1384
+ style: { width: "100%" }
1385
+ }
1386
+ ),
1387
+ props.canDelete ? /* @__PURE__ */ jsxRuntime.jsx(
1388
+ "button",
1389
+ {
1390
+ type: "button",
1391
+ title: `${props.texts.deleteConditionAction ?? "\u5220\u9664\u6761\u4EF6"} ${props.label}`,
1392
+ "aria-label": `${props.texts.deleteConditionAction ?? "\u5220\u9664\u6761\u4EF6"} ${props.label}`,
1393
+ style: deleteConditionStyle,
1394
+ onClick: props.onDelete,
1395
+ children: "\xD7"
1396
+ }
1397
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", {})
1398
+ ] }),
1399
+ hidesValue ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: conditionValueStyle, children: [
1349
1400
  /* @__PURE__ */ jsxRuntime.jsx(
1350
1401
  ReactInput,
1351
1402
  {
1352
- value: props.value,
1403
+ value: props.condition.value ?? "",
1353
1404
  placeholder: props.texts.firstValuePlaceholder,
1354
1405
  onPressEnter: props.onSubmit,
1355
- onChange: (event) => props.onChange({ [`${prefix}Value`]: event.target.value, filterType: "condition" }),
1406
+ onChange: (event) => props.onChange({ ...props.condition, value: event.target.value }),
1356
1407
  style: { width: "100%" }
1357
1408
  }
1358
1409
  ),
1359
1410
  needsSecond ? /* @__PURE__ */ jsxRuntime.jsx(
1360
1411
  ReactInput,
1361
1412
  {
1362
- value: props.secondValue,
1413
+ value: props.condition.secondValue ?? "",
1363
1414
  placeholder: props.texts.secondValuePlaceholder,
1364
1415
  onPressEnter: props.onSubmit,
1365
- onChange: (event) => props.onChange({ [`${prefix}SecondValue`]: event.target.value, filterType: "condition" }),
1416
+ onChange: (event) => props.onChange({ ...props.condition, secondValue: event.target.value }),
1366
1417
  style: { width: "100%" }
1367
1418
  }
1368
1419
  ) : null
1369
1420
  ] })
1370
1421
  ] });
1371
1422
  }
1423
+ function createDraft(value) {
1424
+ if (!value) return null;
1425
+ return { ...value, conditions: resolveDraftConditions(value) };
1426
+ }
1427
+ function resolveDraftConditions(value) {
1428
+ if (value.conditions?.length) return value.conditions.map((condition) => ({ ...condition }));
1429
+ return [
1430
+ { operator: value.conditionOperator ?? "none", value: value.conditionValue ?? "", secondValue: value.conditionSecondValue ?? "" },
1431
+ { operator: value.secondConditionOperator ?? "none", value: value.secondConditionValue ?? "", secondValue: value.secondConditionSecondValue ?? "" }
1432
+ ];
1433
+ }
1434
+ function createEmptyCondition() {
1435
+ return { operator: "none", value: "", secondValue: "" };
1436
+ }
1437
+ function resolveConditionLabel(texts, index) {
1438
+ if (index === 0) return texts.firstConditionLabel;
1439
+ if (index === 1) return texts.secondConditionLabel;
1440
+ return `${texts.conditionLabel ?? "\u6761\u4EF6"} ${index + 1}`;
1441
+ }
1372
1442
  function resolveTab(value) {
1373
1443
  return value?.filterType === "condition" || value?.conditionOperator && value.conditionOperator !== "none" ? "condition" : "value";
1374
1444
  }
@@ -1380,7 +1450,15 @@ function optionLabel(item, texts) {
1380
1450
  var checkLabelStyle = { display: "inline-flex", alignItems: "center", gap: 8, color: "#374151", fontSize: 14 };
1381
1451
  var optionStyle = { display: "flex", alignItems: "center", gap: 8, minHeight: 40, padding: "4px 8px", borderRadius: 6, color: "#374151", fontSize: 14, cursor: "pointer", boxSizing: "border-box" };
1382
1452
  var optionTextStyle = { display: "block", flex: "1 1 0", width: 0, minWidth: 0, overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" };
1383
- var fieldLabelStyle = { marginBottom: 6, fontSize: 12, color: "#6b7280" };
1453
+ var conditionListStyle = { display: "grid", gap: 8, minHeight: 0, flex: "1 1 auto", alignContent: "start", paddingRight: 3 };
1454
+ var conditionEditorStyle = { display: "grid", gap: 8 };
1455
+ var conditionRowStyle = { display: "grid", gridTemplateColumns: "56px minmax(0, 1fr) 22px", alignItems: "center", gap: 8 };
1456
+ var inlineLabelStyle = { color: "#6b7280", fontSize: 12, whiteSpace: "nowrap" };
1457
+ var conditionValueStyle = { display: "grid", gap: 8, marginLeft: 64, marginRight: 30 };
1458
+ var addConditionStyle = { height: 32, padding: "0 10px", border: "1px solid #91caff", borderRadius: 6, background: "#f0f7ff", color: "#1677ff", cursor: "pointer", whiteSpace: "nowrap" };
1459
+ var deleteConditionStyle = { width: 22, height: 22, padding: 0, border: 0, borderRadius: 4, background: "transparent", color: "#9ca3af", fontSize: 18, lineHeight: "20px", cursor: "pointer" };
1460
+ var footerStyle = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 };
1461
+ var footerActionsStyle = { display: "flex", gap: 8, marginLeft: "auto" };
1384
1462
  function tabStyle(active) {
1385
1463
  return { flex: 1, height: 32, border: active ? "1px solid #1677ff" : "1px solid #d9d9d9", borderRadius: 6, background: active ? "#e6f4ff" : "#fff", color: active ? "#1677ff" : "#374151", cursor: "pointer" };
1386
1464
  }