@character-foundry/character-foundry 0.1.5 → 0.1.7

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.
Files changed (67) hide show
  1. package/dist/app-framework.cjs +143 -26
  2. package/dist/app-framework.cjs.map +1 -1
  3. package/dist/app-framework.d.cts +16 -1
  4. package/dist/app-framework.d.ts +16 -1
  5. package/dist/app-framework.js +151 -34
  6. package/dist/app-framework.js.map +1 -1
  7. package/dist/charx.cjs +70 -8
  8. package/dist/charx.cjs.map +1 -1
  9. package/dist/charx.js +70 -8
  10. package/dist/charx.js.map +1 -1
  11. package/dist/core.cjs +93 -6
  12. package/dist/core.cjs.map +1 -1
  13. package/dist/core.d.cts +42 -1
  14. package/dist/core.d.ts +42 -1
  15. package/dist/core.js +93 -6
  16. package/dist/core.js.map +1 -1
  17. package/dist/exporter.cjs +88 -8
  18. package/dist/exporter.cjs.map +1 -1
  19. package/dist/exporter.js +89 -9
  20. package/dist/exporter.js.map +1 -1
  21. package/dist/federation.cjs +1 -0
  22. package/dist/federation.cjs.map +1 -1
  23. package/dist/federation.js +1 -0
  24. package/dist/federation.js.map +1 -1
  25. package/dist/image-utils.cjs +249 -0
  26. package/dist/image-utils.cjs.map +1 -0
  27. package/dist/image-utils.d.cts +136 -0
  28. package/dist/image-utils.d.ts +136 -0
  29. package/dist/image-utils.js +226 -0
  30. package/dist/image-utils.js.map +1 -0
  31. package/dist/index.cjs +122 -18
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +123 -19
  34. package/dist/index.js.map +1 -1
  35. package/dist/loader.cjs +42 -14
  36. package/dist/loader.cjs.map +1 -1
  37. package/dist/loader.js +43 -15
  38. package/dist/loader.js.map +1 -1
  39. package/dist/lorebook.cjs +1 -0
  40. package/dist/lorebook.cjs.map +1 -1
  41. package/dist/lorebook.js +1 -0
  42. package/dist/lorebook.js.map +1 -1
  43. package/dist/media.cjs +1 -0
  44. package/dist/media.cjs.map +1 -1
  45. package/dist/media.js +1 -0
  46. package/dist/media.js.map +1 -1
  47. package/dist/normalizer.cjs +1 -0
  48. package/dist/normalizer.cjs.map +1 -1
  49. package/dist/normalizer.d.cts +1 -0
  50. package/dist/normalizer.d.ts +1 -0
  51. package/dist/normalizer.js +1 -0
  52. package/dist/normalizer.js.map +1 -1
  53. package/dist/png.cjs +19 -0
  54. package/dist/png.cjs.map +1 -1
  55. package/dist/png.js +19 -0
  56. package/dist/png.js.map +1 -1
  57. package/dist/schemas.cjs +80 -0
  58. package/dist/schemas.cjs.map +1 -1
  59. package/dist/schemas.d.cts +30 -0
  60. package/dist/schemas.d.ts +30 -0
  61. package/dist/schemas.js +80 -0
  62. package/dist/schemas.js.map +1 -1
  63. package/dist/voxta.cjs +14 -102
  64. package/dist/voxta.cjs.map +1 -1
  65. package/dist/voxta.js +15 -103
  66. package/dist/voxta.js.map +1 -1
  67. package/package.json +16 -5
@@ -408,13 +408,23 @@ function analyzeField(name, zodType) {
408
408
  constraints: extractConstraints(currentType)
409
409
  };
410
410
  }
411
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
412
+ function isSafeKey(key) {
413
+ return !DANGEROUS_KEYS.has(key);
414
+ }
411
415
  function getValueAtPath(obj, path) {
412
416
  const parts = path.split(".");
413
417
  let current = obj;
414
418
  for (const part of parts) {
419
+ if (!isSafeKey(part)) {
420
+ return void 0;
421
+ }
415
422
  if (current == null || typeof current !== "object") {
416
423
  return void 0;
417
424
  }
425
+ if (!Object.hasOwn(current, part)) {
426
+ return void 0;
427
+ }
418
428
  current = current[part];
419
429
  }
420
430
  return current;
@@ -424,12 +434,16 @@ function setValueAtPath(obj, path, value) {
424
434
  if (parts.length === 0 || parts[0] === void 0) {
425
435
  return obj;
426
436
  }
437
+ const first = parts[0];
438
+ if (!isSafeKey(first)) {
439
+ console.warn(`Rejected dangerous property key in path: ${first}`);
440
+ return obj;
441
+ }
427
442
  if (parts.length === 1) {
428
- return { ...obj, [parts[0]]: value };
443
+ return { ...obj, [first]: value };
429
444
  }
430
- const first = parts[0];
431
445
  const rest = parts.slice(1);
432
- const nested = obj[first] ?? {};
446
+ const nested = (Object.hasOwn(obj, first) ? obj[first] : {}) ?? {};
433
447
  return {
434
448
  ...obj,
435
449
  [first]: setValueAtPath(nested, rest.join("."), value)
@@ -821,11 +835,31 @@ function SearchableSelect({
821
835
  const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
822
836
  const [searchTerm, setSearchTerm] = (0, import_react3.useState)("");
823
837
  const [highlightedIndex, setHighlightedIndex] = (0, import_react3.useState)(-1);
824
- const options = hint?.options ?? [];
825
- const filteredOptions = searchTerm ? options.filter(
826
- (opt) => opt.label.toLowerCase().includes(searchTerm.toLowerCase()) || opt.value.toLowerCase().includes(searchTerm.toLowerCase())
827
- ) : options;
828
- const selectedOption = options.find((opt) => opt.value === value);
838
+ const rawOptions = hint?.options ?? [];
839
+ const indexedOptions = (0, import_react3.useMemo)(() => {
840
+ return rawOptions.map((opt) => ({
841
+ option: opt,
842
+ labelLower: opt.label.toLowerCase(),
843
+ valueLower: opt.value.toLowerCase()
844
+ }));
845
+ }, [rawOptions]);
846
+ const optionsByValue = (0, import_react3.useMemo)(() => {
847
+ const map = /* @__PURE__ */ new Map();
848
+ for (const opt of rawOptions) {
849
+ map.set(opt.value, opt);
850
+ }
851
+ return map;
852
+ }, [rawOptions]);
853
+ const filteredOptions = (0, import_react3.useMemo)(() => {
854
+ if (!searchTerm) {
855
+ return rawOptions;
856
+ }
857
+ const searchLower = searchTerm.toLowerCase();
858
+ return indexedOptions.filter(
859
+ (indexed) => indexed.labelLower.includes(searchLower) || indexed.valueLower.includes(searchLower)
860
+ ).map((indexed) => indexed.option);
861
+ }, [searchTerm, indexedOptions, rawOptions]);
862
+ const selectedOption = optionsByValue.get(value ?? "");
829
863
  const displayValue = selectedOption?.label ?? value ?? "";
830
864
  (0, import_react3.useEffect)(() => {
831
865
  function handleClickOutside(e) {
@@ -1466,33 +1500,84 @@ function AutoForm({
1466
1500
  extractDefaults(fieldInfoMap);
1467
1501
  return defaults;
1468
1502
  }, [fieldInfoMap]);
1503
+ const mergedDefaults = (0, import_react.useMemo)(() => {
1504
+ return deepMerge(
1505
+ deepMerge(schemaDefaults, defaultValues ?? {}),
1506
+ values ?? {}
1507
+ );
1508
+ }, [schemaDefaults, defaultValues, values]);
1469
1509
  const methods = (0, import_react_hook_form.useForm)({
1470
1510
  resolver: (0, import_zod2.zodResolver)(schema),
1471
- defaultValues: {
1472
- ...schemaDefaults,
1473
- ...defaultValues,
1474
- ...values
1475
- },
1511
+ defaultValues: mergedDefaults,
1476
1512
  mode: "onChange",
1477
1513
  shouldUnregister: true
1478
1514
  });
1479
- const { control, handleSubmit, watch, formState, reset } = methods;
1480
- const watchedValues = watch();
1515
+ const { control, handleSubmit, watch, formState, reset, getValues } = methods;
1481
1516
  const prevValuesRef = (0, import_react.useRef)(values);
1517
+ const conditionFields = (0, import_react.useMemo)(() => {
1518
+ const fields = /* @__PURE__ */ new Set();
1519
+ const extractConditionFields = (hints, prefix = "") => {
1520
+ for (const [key, value] of Object.entries(hints)) {
1521
+ if (!value || typeof value !== "object") continue;
1522
+ const hint = value;
1523
+ if (hint.condition && typeof hint.condition === "object") {
1524
+ const condition = hint.condition;
1525
+ if (condition.field) {
1526
+ fields.add(condition.field);
1527
+ }
1528
+ }
1529
+ if (!("widget" in hint) && !("label" in hint) && !("condition" in hint)) {
1530
+ extractConditionFields(hint, prefix ? `${prefix}.${key}` : key);
1531
+ }
1532
+ }
1533
+ };
1534
+ extractConditionFields(uiHints);
1535
+ return Array.from(fields);
1536
+ }, [uiHints]);
1537
+ const [conditionValues, setConditionValues] = (0, import_react.useState)({});
1538
+ (0, import_react.useEffect)(() => {
1539
+ if (conditionFields.length === 0) return;
1540
+ const initial = {};
1541
+ const currentValues = getValues();
1542
+ for (const field of conditionFields) {
1543
+ initial[field] = getValueAtPath(currentValues, field);
1544
+ }
1545
+ setConditionValues(initial);
1546
+ const subscription = watch((formValues, { name }) => {
1547
+ if (name && conditionFields.includes(name)) {
1548
+ setConditionValues((prev) => ({
1549
+ ...prev,
1550
+ [name]: getValueAtPath(formValues, name)
1551
+ }));
1552
+ }
1553
+ });
1554
+ return () => subscription.unsubscribe();
1555
+ }, [conditionFields, watch, getValues]);
1482
1556
  (0, import_react.useEffect)(() => {
1483
1557
  if (values && !shallowEqual(values, prevValuesRef.current)) {
1484
1558
  prevValuesRef.current = values;
1485
- reset({ ...schemaDefaults, ...defaultValues, ...values });
1559
+ const resetValues = deepMerge(
1560
+ deepMerge(schemaDefaults, defaultValues ?? {}),
1561
+ values ?? {}
1562
+ );
1563
+ reset(resetValues);
1486
1564
  }
1487
1565
  }, [values, reset, schemaDefaults, defaultValues]);
1566
+ const onChangeRef = (0, import_react.useRef)(onChange);
1567
+ onChangeRef.current = onChange;
1568
+ const schemaRef = (0, import_react.useRef)(schema);
1569
+ schemaRef.current = schema;
1488
1570
  (0, import_react.useEffect)(() => {
1489
- if (onChange) {
1490
- const result = schema.safeParse(watchedValues);
1491
- if (result.success) {
1492
- onChange(result.data);
1571
+ if (!onChangeRef.current) return;
1572
+ const subscription = watch((formValues, { type }) => {
1573
+ if (type !== "change") return;
1574
+ const result = schemaRef.current.safeParse(formValues);
1575
+ if (result.success && onChangeRef.current) {
1576
+ onChangeRef.current(result.data);
1493
1577
  }
1494
- }
1495
- }, [watchedValues, onChange, schema]);
1578
+ });
1579
+ return () => subscription.unsubscribe();
1580
+ }, [watch]);
1496
1581
  const HINT_KEYS = /* @__PURE__ */ new Set([
1497
1582
  "widget",
1498
1583
  "label",
@@ -1538,9 +1623,9 @@ function AutoForm({
1538
1623
  const isConditionMet = (0, import_react.useCallback)(
1539
1624
  (condition) => {
1540
1625
  if (!condition) return true;
1541
- const fieldValue = getValueAtPath(watchedValues, condition.field);
1626
+ const fieldValue = conditionFields.length > 0 ? conditionValues[condition.field] : getValueAtPath(getValues(), condition.field);
1542
1627
  if (condition.when) {
1543
- return condition.when(fieldValue, watchedValues);
1628
+ return condition.when(fieldValue, getValues());
1544
1629
  }
1545
1630
  if ("equals" in condition && condition.equals !== void 0) {
1546
1631
  return fieldValue === condition.equals;
@@ -1556,7 +1641,7 @@ function AutoForm({
1556
1641
  }
1557
1642
  return true;
1558
1643
  },
1559
- [watchedValues]
1644
+ [conditionFields, conditionValues, getValues]
1560
1645
  );
1561
1646
  const orderedFields = (0, import_react.useMemo)(() => {
1562
1647
  if (fieldOrder) {
@@ -1650,19 +1735,51 @@ function AutoForm({
1650
1735
  }
1651
1736
  return formContent;
1652
1737
  }
1738
+ function deepMerge(base, override) {
1739
+ const result = { ...base };
1740
+ for (const key of Object.keys(override)) {
1741
+ if (!isSafeKey2(key)) continue;
1742
+ const baseVal = base[key];
1743
+ const overrideVal = override[key];
1744
+ if (isPlainObject(baseVal) && isPlainObject(overrideVal)) {
1745
+ result[key] = deepMerge(
1746
+ baseVal,
1747
+ overrideVal
1748
+ );
1749
+ } else if (overrideVal !== void 0) {
1750
+ result[key] = overrideVal;
1751
+ }
1752
+ }
1753
+ return result;
1754
+ }
1755
+ function isPlainObject(val) {
1756
+ return val !== null && typeof val === "object" && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
1757
+ }
1758
+ var DANGEROUS_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1759
+ function isSafeKey2(key) {
1760
+ return !DANGEROUS_KEYS2.has(key);
1761
+ }
1653
1762
  function setNestedValue(obj, path, value) {
1654
1763
  const parts = path.split(".");
1655
1764
  let current = obj;
1656
1765
  for (let i = 0; i < parts.length - 1; i++) {
1657
1766
  const part = parts[i];
1658
1767
  if (part === void 0) continue;
1659
- if (!(part in current) || typeof current[part] !== "object") {
1768
+ if (!isSafeKey2(part)) {
1769
+ console.warn(`Rejected dangerous property key in path: ${part}`);
1770
+ return;
1771
+ }
1772
+ if (!Object.hasOwn(current, part) || typeof current[part] !== "object") {
1660
1773
  current[part] = {};
1661
1774
  }
1662
1775
  current = current[part];
1663
1776
  }
1664
1777
  const lastPart = parts[parts.length - 1];
1665
1778
  if (lastPart !== void 0) {
1779
+ if (!isSafeKey2(lastPart)) {
1780
+ console.warn(`Rejected dangerous property key in path: ${lastPart}`);
1781
+ return;
1782
+ }
1666
1783
  current[lastPart] = value;
1667
1784
  }
1668
1785
  }