hs-uix 1.1.0 → 1.2.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/dist/index.mjs CHANGED
@@ -84,12 +84,19 @@ var computeAutoWidths = (columns, data) => {
84
84
  columns.forEach((col) => {
85
85
  if (col.width && col.cellWidth) return;
86
86
  const values = sample.map((row) => row[col.field]).filter((v) => v != null);
87
- const strings = values.map((v) => String(v));
87
+ const strings = values.map((v) => {
88
+ const s = String(v);
89
+ const truncLen = typeof col.truncate === "number" ? col.truncate : col.truncate && typeof col.truncate === "object" ? col.truncate.maxLength : null;
90
+ return truncLen && s.length > truncLen ? s.slice(0, truncLen) : s;
91
+ });
88
92
  let widthHint = null;
89
93
  let cellWidthHint = null;
90
94
  if (col.editable && col.editType && NARROW_EDIT_TYPES.has(col.editType)) {
91
95
  cellWidthHint = "min";
92
96
  }
97
+ if (col.truncate === true) {
98
+ cellWidthHint = cellWidthHint || "min";
99
+ }
93
100
  if (strings.length > 0) {
94
101
  const lengths = strings.map((s) => s.length);
95
102
  const maxLen = Math.max(...lengths);
@@ -863,20 +870,26 @@ var DataTable = ({
863
870
  const rawStr = String(rawValue ?? "");
864
871
  if (col.truncate && rawStr.length > 0) {
865
872
  if (col.truncate === true) {
866
- const content2 = col.renderCell ? col.renderCell(rawValue, row) : rawStr;
873
+ if (col.renderCell) {
874
+ const content2 = col.renderCell(rawValue, row);
875
+ if (col.editable) {
876
+ return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
877
+ }
878
+ return content2;
879
+ }
867
880
  if (col.editable) {
868
- return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--"));
881
+ return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, rawStr || "--"));
869
882
  }
870
- return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, content2);
883
+ return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, rawStr);
871
884
  }
872
- const maxLen = col.truncate.maxLength || 100;
885
+ const maxLen = typeof col.truncate === "number" ? col.truncate : col.truncate.maxLength || 100;
873
886
  if (rawStr.length > maxLen) {
874
887
  const truncatedStr = rawStr.slice(0, maxLen) + "\u2026";
875
- const truncatedContent = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
888
+ const content2 = col.renderCell ? col.renderCell(truncatedStr, row) : truncatedStr;
876
889
  if (col.editable) {
877
- return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, truncatedContent || "--");
890
+ return /* @__PURE__ */ React.createElement(Link, { variant: "dark", onClick: () => startEditing(rowId, col.field, rawValue) }, content2 || "--");
878
891
  }
879
- return /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, truncatedContent || "--");
892
+ return col.renderCell ? content2 : /* @__PURE__ */ React.createElement(Text, { truncate: { tooltipText: rawStr } }, content2 || "--");
880
893
  }
881
894
  }
882
895
  const content = col.renderCell ? col.renderCell(rawValue, row) : rawValue;
@@ -1222,7 +1235,6 @@ var runDefaultFieldValidator = (value, field, allValues) => {
1222
1235
  if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
1223
1236
  break;
1224
1237
  case "datetime": {
1225
- if (isDateValueObject(value)) break;
1226
1238
  if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
1227
1239
  const hasDate = value.date !== void 0;
1228
1240
  const hasTime = value.time !== void 0;
@@ -1289,7 +1301,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
1289
1301
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
1290
1302
  const includeCustomValidators = options.includeCustomValidators !== false;
1291
1303
  const msg = options.messages || {};
1292
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
1304
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
1293
1305
  const isRequired = resolveRequired(field, allValues);
1294
1306
  const plugin = fieldTypes && fieldTypes[field.type];
1295
1307
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -1436,6 +1448,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1436
1448
  // (values, { reset, rawValues }) => void | Promise
1437
1449
  transformValues,
1438
1450
  // (values) => values — reshape before submit
1451
+ transformInitialValues,
1452
+ // (rawInitialValues) => values — reshape raw data on load
1439
1453
  onBeforeSubmit,
1440
1454
  // (values) => boolean | Promise<boolean> — intercept submit
1441
1455
  onSubmitSuccess,
@@ -1599,12 +1613,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1599
1613
  prevSuccessRef.current = formSuccess;
1600
1614
  }, [addAlert, formSuccess, successTitle]);
1601
1615
  const computeInitialValues = () => {
1616
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
1602
1617
  const vals = {};
1603
1618
  for (const field of fields) {
1604
1619
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
1620
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1621
+ for (const item of field.items) {
1622
+ const subFields = field.fields(item);
1623
+ for (const sf of subFields) {
1624
+ const plugin2 = fieldTypes && fieldTypes[sf.type];
1625
+ const emptyValue2 = plugin2 && plugin2.getEmptyValue ? plugin2.getEmptyValue() : getEmptyValue(sf);
1626
+ let init2 = resolved && resolved[sf.name] !== void 0 ? resolved[sf.name] : sf.defaultValue !== void 0 ? sf.defaultValue : emptyValue2;
1627
+ if (sf.transformIn) init2 = sf.transformIn(init2);
1628
+ vals[sf.name] = init2;
1629
+ }
1630
+ }
1631
+ continue;
1632
+ }
1605
1633
  const plugin = fieldTypes && fieldTypes[field.type];
1606
1634
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
1607
- const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1635
+ let init = resolved && resolved[field.name] !== void 0 ? resolved[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
1636
+ if (field.transformIn) init = field.transformIn(init);
1608
1637
  vals[field.name] = init;
1609
1638
  }
1610
1639
  return vals;
@@ -1637,7 +1666,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1637
1666
  formErrorsRef.current = formErrors;
1638
1667
  const fieldByName = useMemo2(() => {
1639
1668
  const map = /* @__PURE__ */ new Map();
1640
- for (const field of fields) map.set(field.name, field);
1669
+ for (const field of fields) {
1670
+ map.set(field.name, field);
1671
+ if (field.type === "fieldGroup" && field.items && field.fields) {
1672
+ for (const item of field.items) {
1673
+ for (const sf of field.fields(item)) map.set(sf.name, sf);
1674
+ }
1675
+ }
1676
+ }
1641
1677
  return map;
1642
1678
  }, [fields]);
1643
1679
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -2059,23 +2095,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2059
2095
  );
2060
2096
  const handleFieldInput = useCallback2(
2061
2097
  (name, value) => {
2098
+ handleFieldChange(name, value);
2062
2099
  if (!validateOnChange) return;
2063
2100
  const err = validateField(name, value);
2064
2101
  updateErrors({ [name]: err });
2065
2102
  },
2066
- [validateOnChange, validateField, updateErrors]
2103
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
2067
2104
  );
2068
2105
  const handleFieldBlur = useCallback2(
2069
2106
  (name, value) => {
2070
- if (!validateOnBlur) return;
2071
2107
  const resolvedValue = value != null ? value : formValuesRef.current[name];
2108
+ if (value != null && value !== formValuesRef.current[name]) {
2109
+ handleFieldChange(name, value);
2110
+ }
2111
+ if (!validateOnBlur) return;
2072
2112
  const err = validateField(name, resolvedValue);
2073
2113
  updateErrors({ [name]: err });
2074
2114
  if (!err) {
2075
2115
  triggerAsyncValidation(name, resolvedValue);
2076
2116
  }
2077
2117
  },
2078
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
2118
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
2079
2119
  );
2080
2120
  const handleSubmit = useCallback2(
2081
2121
  async (e) => {
@@ -2108,8 +2148,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2108
2148
  const rawValues = {};
2109
2149
  for (const key of Object.keys(formValues)) {
2110
2150
  const f = fieldByName.get(key);
2111
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
2112
- rawValues[key] = formValues[key];
2151
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
2152
+ rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
2153
+ }
2154
+ for (const f of fields) {
2155
+ if (f.type !== "fieldGroup" || !f.items || !f.fields) continue;
2156
+ for (const item of f.items) {
2157
+ for (const sf of f.fields(item)) {
2158
+ if (formValues[sf.name] === void 0) continue;
2159
+ rawValues[sf.name] = sf.transformOut ? sf.transformOut(formValues[sf.name]) : formValues[sf.name];
2160
+ }
2161
+ }
2113
2162
  }
2114
2163
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
2115
2164
  if (onBeforeSubmit) {
@@ -2252,10 +2301,125 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2252
2301
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
2253
2302
  if (field.type === "display") {
2254
2303
  if (field.render) {
2255
- return field.render({ allValues: formValues });
2304
+ return field.render({
2305
+ allValues: formValues,
2306
+ setFieldValue: (name, value) => handleFieldChange(name, value),
2307
+ setFieldError: (name, message) => updateErrors({ [name]: message })
2308
+ });
2256
2309
  }
2257
2310
  return null;
2258
2311
  }
2312
+ if (field.type === "fieldGroup") {
2313
+ const items = field.items || [];
2314
+ const fieldsFn = field.fields;
2315
+ if (!fieldsFn) return null;
2316
+ const groupColumns = field.columns || 1;
2317
+ const showItemLabel = field.showItemLabel !== false;
2318
+ return /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React2.createElement(Text2, { format: { fontWeight: "demibold" } }, field.label), field.description && /* @__PURE__ */ React2.createElement(Text2, { variant: "microcopy" }, field.description), items.map((item, itemIdx) => {
2319
+ const subFields = fieldsFn(item);
2320
+ return /* @__PURE__ */ React2.createElement(Flex2, { key: item.key || itemIdx, direction: "row", gap: "xs", align: "end" }, showItemLabel && item.label && /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, itemIdx === 0 ? /* @__PURE__ */ React2.createElement(
2321
+ Input2,
2322
+ {
2323
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
2324
+ label: "\xA0",
2325
+ value: item.label,
2326
+ readOnly: true,
2327
+ disabled: true
2328
+ }
2329
+ ) : /* @__PURE__ */ React2.createElement(
2330
+ Input2,
2331
+ {
2332
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
2333
+ value: item.label,
2334
+ readOnly: true,
2335
+ disabled: true
2336
+ }
2337
+ )), subFields.map((sf) => {
2338
+ const sfValue = formValues[sf.name];
2339
+ const sfError = formErrors[sf.name] || null;
2340
+ const sfLabel = itemIdx === 0 ? sf.label : void 0;
2341
+ const sfReadOnly = sf.readOnly || formReadOnly;
2342
+ const sfDisabled = disabled || sf.disabled || formReadOnly;
2343
+ const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
2344
+ const sfProps = {
2345
+ name: sf.name,
2346
+ label: sfLabel,
2347
+ placeholder: sf.placeholder,
2348
+ description: itemIdx === 0 ? sf.description : void 0,
2349
+ readOnly: sfReadOnly,
2350
+ disabled: sfDisabled,
2351
+ error: !!sfError,
2352
+ validationMessage: sfError || void 0,
2353
+ ...sf.fieldProps || {}
2354
+ };
2355
+ let sfElement;
2356
+ switch (sf.type) {
2357
+ case "select":
2358
+ sfElement = /* @__PURE__ */ React2.createElement(
2359
+ Select2,
2360
+ {
2361
+ ...sfProps,
2362
+ value: sfValue,
2363
+ options: resolveOptions(sf, formValues),
2364
+ onChange: sfOnChange
2365
+ }
2366
+ );
2367
+ break;
2368
+ case "number":
2369
+ sfElement = /* @__PURE__ */ React2.createElement(
2370
+ NumberInput2,
2371
+ {
2372
+ ...sfProps,
2373
+ value: sfValue,
2374
+ onChange: sfOnChange,
2375
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2376
+ }
2377
+ );
2378
+ break;
2379
+ case "toggle":
2380
+ sfElement = /* @__PURE__ */ React2.createElement(
2381
+ Toggle2,
2382
+ {
2383
+ name: sf.name,
2384
+ label: sfLabel || sf.label,
2385
+ checked: !!sfValue,
2386
+ size: sf.size || "md",
2387
+ labelDisplay: sf.labelDisplay || "top",
2388
+ readonly: sfReadOnly,
2389
+ disabled: sfDisabled,
2390
+ onChange: sfOnChange,
2391
+ ...sf.fieldProps || {}
2392
+ }
2393
+ );
2394
+ break;
2395
+ case "time":
2396
+ sfElement = /* @__PURE__ */ React2.createElement(
2397
+ TimeInput2,
2398
+ {
2399
+ ...sfProps,
2400
+ value: sfValue,
2401
+ interval: sf.interval,
2402
+ onChange: sfOnChange,
2403
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2404
+ }
2405
+ );
2406
+ break;
2407
+ default:
2408
+ sfElement = /* @__PURE__ */ React2.createElement(
2409
+ Input2,
2410
+ {
2411
+ ...sfProps,
2412
+ value: sfValue || "",
2413
+ onChange: sfOnChange,
2414
+ onInput: (v) => handleFieldInput(sf.name, v),
2415
+ onBlur: (v) => handleFieldBlur(sf.name, v)
2416
+ }
2417
+ );
2418
+ }
2419
+ return /* @__PURE__ */ React2.createElement(Box2, { key: sf.name, flex: 1 }, sfElement);
2420
+ }));
2421
+ }));
2422
+ }
2259
2423
  if (field.type === "crmPropertyList") {
2260
2424
  return /* @__PURE__ */ React2.createElement(
2261
2425
  CrmPropertyList,
@@ -2800,38 +2964,19 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2800
2964
  }
2801
2965
  return elements;
2802
2966
  };
2803
- const renderLegacyLayout = (fieldSubset) => {
2967
+ const renderSingleColumnLayout = (fieldSubset) => {
2804
2968
  const fieldList = fieldSubset || visibleFields;
2805
- const rows = [];
2806
- let i = 0;
2807
- while (i < fieldList.length) {
2808
- const field = fieldList[i];
2809
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
2810
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
2811
- i += 2;
2812
- } else {
2813
- rows.push({ type: "single", field });
2814
- i++;
2815
- }
2816
- }
2817
2969
  const elements = [];
2818
2970
  const processedDeps = /* @__PURE__ */ new Set();
2819
- for (const row of rows) {
2820
- if (row.type === "pair") {
2821
- elements.push(
2822
- /* @__PURE__ */ React2.createElement(Flex2, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ React2.createElement(Box2, { flex: 1 }, renderField(row.fields[1])))
2823
- );
2824
- } else {
2825
- const field = row.field;
2826
- if (processedDeps.has(field.name)) continue;
2827
- elements.push(
2828
- /* @__PURE__ */ React2.createElement(React2.Fragment, { key: field.name }, renderField(field))
2829
- );
2830
- const dependents = getDependents(field);
2831
- if (dependents.length > 0) {
2832
- for (const dep of dependents) processedDeps.add(dep.name);
2833
- elements.push(renderDependentGroup(field, dependents));
2834
- }
2971
+ for (const field of fieldList) {
2972
+ if (processedDeps.has(field.name)) continue;
2973
+ elements.push(
2974
+ /* @__PURE__ */ React2.createElement(React2.Fragment, { key: field.name }, renderField(field))
2975
+ );
2976
+ const dependents = getDependents(field);
2977
+ if (dependents.length > 0) {
2978
+ for (const dep of dependents) processedDeps.add(dep.name);
2979
+ elements.push(renderDependentGroup(field, dependents));
2835
2980
  }
2836
2981
  }
2837
2982
  return elements;
@@ -2842,10 +2987,20 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2842
2987
  let batch = [];
2843
2988
  const flushBatch = () => {
2844
2989
  if (batch.length === 0) return;
2845
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
2846
- for (const chunk of chunks) {
2990
+ if (maxColumns) {
2991
+ const chunks = Array.from(
2992
+ { length: Math.ceil(batch.length / maxColumns) },
2993
+ (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)
2994
+ );
2995
+ for (const chunk of chunks) {
2996
+ const remainder = maxColumns - chunk.length;
2997
+ elements.push(
2998
+ /* @__PURE__ */ React2.createElement(Flex2, { key: `ag-${chunk[0].name}`, direction: "row", gap }, chunk.map((f) => /* @__PURE__ */ React2.createElement(Box2, { key: f.name, flex: 1 }, renderField(f))), remainder > 0 && /* @__PURE__ */ React2.createElement(Box2, { flex: remainder }))
2999
+ );
3000
+ }
3001
+ } else {
2847
3002
  elements.push(
2848
- /* @__PURE__ */ React2.createElement(AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ React2.createElement(React2.Fragment, { key: f.name }, renderField(f))))
3003
+ /* @__PURE__ */ React2.createElement(AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ React2.createElement(React2.Fragment, { key: f.name }, renderField(f))))
2849
3004
  );
2850
3005
  }
2851
3006
  batch = [];
@@ -2904,7 +3059,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2904
3059
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
2905
3060
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
2906
3061
  if (columns > 1) return renderGridLayout(fieldSubset);
2907
- return renderLegacyLayout(fieldSubset);
3062
+ return renderSingleColumnLayout(fieldSubset);
2908
3063
  };
2909
3064
  const renderSections = () => {
2910
3065
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -2917,7 +3072,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
2917
3072
  for (const sec of sections) {
2918
3073
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
2919
3074
  if (sectionFields.length === 0) continue;
2920
- const accordionContent = /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, renderFieldSubset(sectionFields));
3075
+ const sectionContext = { values: formValues, errors: formErrors };
3076
+ const accordionContent = /* @__PURE__ */ React2.createElement(Flex2, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields), sec.renderAfter && sec.renderAfter(sectionContext));
2921
3077
  const accordion = /* @__PURE__ */ React2.createElement(
2922
3078
  Accordion,
2923
3079
  {
package/form.d.ts CHANGED
@@ -11,6 +11,7 @@ export {
11
11
  FormBuilderLayout,
12
12
  FormBuilderLayoutEntry,
13
13
  FormBuilderSection,
14
+ FormBuilderSectionContext,
14
15
  FormBuilderStep,
15
16
  FormBuilderRef,
16
17
  FieldTypePlugin,
package/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export type {
32
32
  FormBuilderLayout,
33
33
  FormBuilderLayoutEntry,
34
34
  FormBuilderSection,
35
+ FormBuilderSectionContext,
35
36
  FormBuilderStep,
36
37
  FormBuilderRef,
37
38
  FieldTypePlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hs-uix",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Production-ready UI components for HubSpot UI Extensions — DataTable, FormBuilder, and more",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",