coles-solid-library 0.3.7 → 0.3.9

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.
@@ -14,6 +14,11 @@ export declare class FormGroup<T extends object> {
14
14
  private meta;
15
15
  private keys;
16
16
  constructor(data: FormGroupData<T>);
17
+ /**
18
+ * INTERNAL: returns the reactive internal store reference (DO NOT MUTATE outside FormGroup).
19
+ * Used for bridging into FormContext without triggering cloning loops.
20
+ */
21
+ _unsafeRaw(): T;
17
22
  /**
18
23
  * Gets the current form data or the value of a specific control.
19
24
  *
@@ -1,7 +1,9 @@
1
1
  import { Accessor, Component, JSX, Setter } from "solid-js";
2
2
  interface Props extends JSX.TextareaHTMLAttributes<HTMLTextAreaElement> {
3
- text: Accessor<string>;
4
- setText: Setter<string>;
3
+ /** Optional external accessor (legacy). If omitted and inside a FormField+Form, form data is used. */
4
+ text?: Accessor<string>;
5
+ /** Optional external setter (legacy). */
6
+ setText?: Setter<string>;
5
7
  class?: string;
6
8
  tooltip?: string;
7
9
  transparent?: boolean;
package/dist/index.esm.js CHANGED
@@ -1441,6 +1441,17 @@ const FormInner = props => {
1441
1441
  const Provider = props => {
1442
1442
  const defaultData = props.value ?? {};
1443
1443
  const [data, setData] = createStore(defaultData);
1444
+ // Bridge: keep FormContext store in sync with underlying FormGroup reactive store
1445
+ // This allows programmatic calls to formGroup.set(...) to propagate into bound fields.
1446
+ createEffect(() => {
1447
+ const raw = props.formGroup._unsafeRaw(); // reactive read of underlying store
1448
+ for (const k in raw) {
1449
+ const nextVal = raw[k];
1450
+ if (data[k] !== nextVal) {
1451
+ setData(k, nextVal);
1452
+ }
1453
+ }
1454
+ });
1444
1455
  return createComponent(FormContext.Provider, {
1445
1456
  get value() {
1446
1457
  return {
@@ -1588,40 +1599,62 @@ styleInject(css_248z$d);
1588
1599
  var _tmpl$$h = /*#__PURE__*/template(`<label><input type=checkbox><span>`),
1589
1600
  _tmpl$2$9 = /*#__PURE__*/template(`<span>`);
1590
1601
  function Checkbox(props) {
1591
- // Handle controlled vs. uncontrolled state
1602
+ const field = useFormProvider();
1603
+ const formCtx = useFormContext();
1604
+ const formName = field?.formName;
1605
+ // Internal state for uncontrolled usage outside form context
1592
1606
  const [internalChecked, setInternalChecked] = createSignal(props.defaultChecked ?? false);
1593
- const checkedState = createMemo(() => props.checked !== undefined ? props.checked : internalChecked());
1594
- const handleChange = e => {
1595
- const target = e.currentTarget;
1596
- const newChecked = target.checked;
1607
+ // Derive current checked state with priority: controlled prop -> form context -> field local value -> internal state
1608
+ const checkedState = createMemo(() => {
1609
+ if (props.checked !== undefined) return !!props.checked;
1610
+ if (formName && formCtx?.data) return !!formCtx.data[formName];
1611
+ if (field?.getValue) return !!field.getValue();
1612
+ return internalChecked();
1613
+ });
1614
+ // Keep field/form floating states in sync if programmatic changes occur
1615
+ createEffect(() => {
1616
+ const c = checkedState();
1617
+ if (formName && formCtx?.data) {
1618
+ field?.setTextInside?.(c);
1619
+ field?.setValue?.(c);
1620
+ }
1621
+ });
1622
+ const commitValue = next => {
1597
1623
  if (props.checked === undefined) {
1598
- setInternalChecked(newChecked);
1599
- props.onChange?.(newChecked);
1600
- } else {
1601
- // Controlled: prevent native state flip from persisting
1602
- e.preventDefault();
1603
- // Re-sync DOM to prop (in case browser already toggled visually before preventDefault took effect)
1604
- queueMicrotask(() => {
1605
- target.checked = !!props.checked;
1606
- });
1607
- props.onChange?.(newChecked);
1624
+ if (formName && formCtx?.formGroup) {
1625
+ formCtx.formGroup.set(formName, next);
1626
+ formCtx.setData?.(formName, next);
1627
+ } else if (field?.setValue) {
1628
+ field.setValue(next);
1629
+ } else {
1630
+ setInternalChecked(next);
1631
+ }
1608
1632
  }
1633
+ field?.setTextInside?.(next);
1634
+ props.onChange?.(next);
1609
1635
  };
1610
- const handleClick = e => {
1636
+ const handleChange = e => {
1637
+ const target = e.currentTarget;
1638
+ const newChecked = target.checked;
1611
1639
  if (props.checked !== undefined) {
1612
- e.preventDefault();
1613
- const target = e.currentTarget;
1640
+ // controlled: emit change; parent should update props.checked.
1641
+ // After microtask (allow parent reactive update) sync DOM to current checkedState
1642
+ props.onChange?.(newChecked);
1614
1643
  queueMicrotask(() => {
1615
- target.checked = !!props.checked;
1644
+ // checkedState() reflects latest props / form context
1645
+ const desired = checkedState();
1646
+ if (target.checked !== desired) target.checked = desired;
1616
1647
  });
1648
+ return;
1617
1649
  }
1650
+ // uncontrolled / form-managed
1651
+ commitValue(newChecked);
1618
1652
  };
1619
1653
  return (() => {
1620
1654
  var _el$ = _tmpl$$h(),
1621
1655
  _el$2 = _el$.firstChild,
1622
1656
  _el$3 = _el$2.nextSibling;
1623
1657
  _el$2.addEventListener("change", handleChange);
1624
- _el$2.$$click = handleClick;
1625
1658
  insert(_el$, (() => {
1626
1659
  var _c$ = createMemo(() => !!props.label);
1627
1660
  return () => _c$() && (() => {
@@ -1660,7 +1693,6 @@ function Checkbox(props) {
1660
1693
  return _el$;
1661
1694
  })();
1662
1695
  }
1663
- delegateEvents(["click"]);
1664
1696
 
1665
1697
  var css_248z$c = ".selectStyles-module_solid_select__placeholder__VO5-- {\n opacity: var(--text-emphasis-medium, 87%);\n transition: all 0.7s ease;\n}\n\n.selectStyles-module_solid_select__transparent__nOpvm {\n background-color: transparent;\n}\n\n.selectStyles-module_solid_select__3plh1 {\n display: inline-block;\n position: relative;\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n text-align: left;\n border-radius: 8px;\n width: 100%;\n margin: 0px 6px;\n}\n\n.selectStyles-module_solid_select__control__Wmmpg {\n display: flex;\n align-items: center;\n width: 100%;\n max-height: 48px;\n border-radius: 8px;\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n cursor: pointer;\n transition: border 0.7s ease, padding 0.7s ease;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.selectStyles-module_solid_select__control__Wmmpg:first-child {\n flex-grow: 1;\n}\n.selectStyles-module_solid_select__control__Wmmpg:last-child {\n padding: 0px 6px;\n margin: 0px;\n}\n\n.selectStyles-module_solid_select__control_no_form__Cq9Sc {\n display: flex;\n align-items: center;\n width: 100%;\n height: 48px;\n border-radius: 8px;\n width: 200px;\n margin-top: 2px;\n cursor: pointer;\n}\n.selectStyles-module_solid_select__control_no_form__Cq9Sc:first-child {\n flex-grow: 1;\n}\n.selectStyles-module_solid_select__control_no_form__Cq9Sc:last-child {\n padding: 0px 6px;\n margin: 0px;\n}\n\n.selectStyles-module_solid_select__arrow__OPCZo {\n font-size: 0.7em;\n}\n\n.selectStyles-module_solid_select__value__rNtqF {\n flex-grow: 1;\n transition: all 0.3s ease;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: block;\n}\n\n.selectStyles-module_multiselect-value__UUhhP {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n.selectStyles-module_solid_select__dropdown__UCt-N {\n position: absolute;\n background-color: var(--surface-color, #EEEEEE);\n top: 100%;\n left: 0;\n z-index: 999999999999999;\n max-height: 200px;\n border-radius: 8px;\n overflow-x: hidden;\n overflow-y: auto;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);\n transform-origin: top;\n transform: scaleY(0);\n transition: transform 0.3s ease-out, opacity 0.3s ease-out;\n opacity: 0;\n}\n\n.selectStyles-module_dropTop__IVz9p {\n transform-origin: bottom;\n}\n\n.selectStyles-module_dropBottom__cYJF0 {\n transform-origin: top;\n}\n\n.selectStyles-module_open__f8zLA {\n transform: scaleY(1);\n opacity: 1;\n}\n\n.selectStyles-module_solid_select__option__47WMY {\n padding: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n min-height: 32px;\n background-color: var(--container-color, #ffffff);\n color: var(--on-container-color, #000);\n border-radius: 8px;\n}\n.selectStyles-module_solid_select__option__47WMY:hover {\n border: 2px inset var(--primary-color-varient, #43A047);\n}\n.selectStyles-module_solid_select__option__47WMY:has(.selectStyles-module_selected__1-CKm) {\n font-weight: bold;\n}\n\n.selectStyles-module_checkmark__UWcbd {\n display: inline-block;\n width: 1.2em;\n margin-right: 4px;\n text-align: center;\n}\n\n.selectStyles-module_option-label__bBFSW {\n width: -moz-max-content;\n width: max-content;\n height: -moz-max-content;\n height: max-content;\n}";
1666
1698
  var styles$7 = {"solid_select__placeholder":"selectStyles-module_solid_select__placeholder__VO5--","solid_select__transparent":"selectStyles-module_solid_select__transparent__nOpvm","solid_select":"selectStyles-module_solid_select__3plh1","solid_select__control":"selectStyles-module_solid_select__control__Wmmpg","solid_select__control_no_form":"selectStyles-module_solid_select__control_no_form__Cq9Sc","solid_select__arrow":"selectStyles-module_solid_select__arrow__OPCZo","solid_select__value":"selectStyles-module_solid_select__value__rNtqF","multiselect-value":"selectStyles-module_multiselect-value__UUhhP","solid_select__dropdown":"selectStyles-module_solid_select__dropdown__UCt-N","dropTop":"selectStyles-module_dropTop__IVz9p","dropBottom":"selectStyles-module_dropBottom__cYJF0","open":"selectStyles-module_open__f8zLA","solid_select__option":"selectStyles-module_solid_select__option__47WMY","selected":"selectStyles-module_selected__1-CKm","checkmark":"selectStyles-module_checkmark__UWcbd","option-label":"selectStyles-module_option-label__bBFSW"};
@@ -2006,6 +2038,9 @@ function Select(props) {
2006
2038
  }
2007
2039
  // store orientation for class assignment
2008
2040
  setDropTop(placeAbove);
2041
+ // Compute available vertical space for dropdown and clamp to a sensible minimum
2042
+ const availableSpace = (placeAbove ? spaceAbove : spaceBelow) - VIEWPORT_MARGIN;
2043
+ const maxHeight = Math.max(160, availableSpace); // ensure at least 160px so a few options are visible
2009
2044
  let newY;
2010
2045
  if (!placeAbove) {
2011
2046
  newY = baseRect.bottom + window.scrollY; // default below
@@ -2027,6 +2062,7 @@ function Select(props) {
2027
2062
  dropdown.style.left = `${newX}px`;
2028
2063
  dropdown.style.top = `${newY}px`;
2029
2064
  dropdown.style.width = `${baseRect.width}px`;
2065
+ dropdown.style.maxHeight = `${maxHeight}px`;
2030
2066
  };
2031
2067
  // Update width of select to match option text width
2032
2068
  createEffect(() => {
@@ -2178,7 +2214,6 @@ function Select(props) {
2178
2214
  get children() {
2179
2215
  var _el$12 = _tmpl$4$1();
2180
2216
  use(setDropdownRef, _el$12);
2181
- _el$12.style.setProperty("max-height", "calc(100vh - 8px)");
2182
2217
  insert(_el$12, () => props.children);
2183
2218
  createRenderEffect(_p$ => {
2184
2219
  var _v$ = `${styles$7['solid_select__dropdown']} ${dropTop() ? styles$7.dropTop : styles$7.dropBottom} ${open() ? styles$7.open : ''} ${props.dropdownClass || ""}`,
@@ -2345,76 +2380,101 @@ var _tmpl$$e = /*#__PURE__*/template(`<textarea>`);
2345
2380
  const TextArea = props => {
2346
2381
  let myElement;
2347
2382
  const [customProps, normalProps] = splitProps(props, ["minSize", "text", "setText", "class", "tooltip", "transparent"]);
2348
- const context = useFormProvider();
2349
- /**
2350
- * Function to set the height of the textarea based on the content
2351
- */
2352
- function OnInput() {
2353
- if (myElement) {
2354
- myElement.style.height = 'auto';
2355
- const minHeight = customProps.minSize?.height ?? 100;
2356
- const currentHeight = myElement.scrollHeight < minHeight ? minHeight : myElement.scrollHeight;
2357
- myElement.style.height = `${currentHeight}px`;
2358
- myElement.setAttribute("style", "height:" + currentHeight + "px;overflow-y:hidden;");
2359
- }
2383
+ const fieldCtx = useFormProvider();
2384
+ const formCtx = useFormContext();
2385
+ const formName = fieldCtx?.formName;
2386
+ // Internal state only used when not provided externally and not in form context.
2387
+ const [internal, setInternal] = createSignal(customProps.text ? customProps.text() : "");
2388
+ // Determine current value priority: FormGroup -> external accessor -> internal state
2389
+ const areaValue = createMemo(() => {
2390
+ if (formName && formCtx?.data && !isNullish(formCtx.data[formName])) {
2391
+ return formCtx.data[formName];
2392
+ }
2393
+ if (customProps.text) return customProps.text();
2394
+ return internal();
2395
+ });
2396
+ function resizeToContent() {
2397
+ if (!myElement) return;
2398
+ myElement.style.height = 'auto';
2399
+ const minHeight = customProps.minSize?.height ?? 100;
2400
+ const currentHeight = Math.max(minHeight, myElement.scrollHeight);
2401
+ myElement.style.height = `${currentHeight}px`;
2402
+ myElement.style.overflowY = 'hidden';
2360
2403
  }
2361
- // sets the field type on mount and sets the height of the textarea
2404
+ // Set field type & initial floating state
2362
2405
  onMount(() => {
2363
- OnInput();
2364
- context?.setFieldType("textarea");
2406
+ resizeToContent();
2407
+ fieldCtx?.setFieldType?.('textarea');
2408
+ if (formName && formCtx?.data) {
2409
+ const v = formCtx.data[formName];
2410
+ if (!isNullish(v) && String(v).trim() !== '') {
2411
+ fieldCtx?.setValue?.(v);
2412
+ fieldCtx?.setTextInside?.(true);
2413
+ } else {
2414
+ fieldCtx?.setTextInside?.(false);
2415
+ }
2416
+ } else if (customProps.text) {
2417
+ const v = customProps.text();
2418
+ fieldCtx?.setTextInside?.(!(v === undefined || v === null || v.trim() === ''));
2419
+ }
2365
2420
  });
2366
- // updates height of textarea when text is changed
2367
- createRenderEffect(() => {
2368
- OnInput();
2369
- customProps.text();
2421
+ // React to programmatic FormGroup.set changes
2422
+ createEffect(() => {
2423
+ if (formName && formCtx?.data) {
2424
+ const v = formCtx.data[formName];
2425
+ // keep internal state aligned if unmanaged
2426
+ if (!customProps.text && internal() !== v) setInternal(String(v ?? ''));
2427
+ if (fieldCtx) {
2428
+ const has = !(v === undefined || v === null || typeof v === 'string' && v.trim() === '');
2429
+ fieldCtx.setTextInside(has);
2430
+ fieldCtx.setValue?.(v);
2431
+ }
2432
+ }
2370
2433
  });
2434
+ // Resize whenever value changes (user or programmatic)
2371
2435
  createEffect(() => {
2372
- if (Object.keys(props).includes("required") || props?.required === true) {
2373
- context?.setName(old => `${old} *`);
2436
+ areaValue();
2437
+ queueMicrotask(resizeToContent);
2438
+ });
2439
+ const handleInput = e => {
2440
+ const newVal = e.currentTarget.value;
2441
+ // Update whichever source is active
2442
+ if (formName && formCtx?.formGroup) {
2443
+ formCtx.formGroup.set(formName, newVal);
2444
+ formCtx.setData?.(formName, newVal);
2445
+ } else if (customProps.setText) {
2446
+ customProps.setText(newVal);
2374
2447
  } else {
2375
- context?.setName(old => old);
2448
+ setInternal(newVal);
2376
2449
  }
2377
- });
2450
+ if (fieldCtx) {
2451
+ fieldCtx.setValue?.(newVal);
2452
+ fieldCtx.setTextInside?.(newVal.trim().length > 0);
2453
+ }
2454
+ resizeToContent();
2455
+ };
2378
2456
  return (() => {
2379
2457
  var _el$ = _tmpl$$e();
2380
2458
  use(el => {
2381
2459
  myElement = el;
2382
- OnInput();
2460
+ resizeToContent();
2383
2461
  }, _el$);
2384
2462
  spread(_el$, mergeProps(normalProps, {
2385
- "onFocus": e => {
2386
- if (!isNullish(context?.getName)) {
2387
- context?.setFocused(true);
2388
- }
2389
- },
2390
- "onBlur": e => {
2391
- if (context?.setFocused) {
2392
- context?.setFocused(false);
2393
- }
2394
- },
2395
- get placeholder() {
2396
- return !!context?.getName && context?.getTextInside() && !context?.getFocused() ? "" : props.placeholder;
2463
+ get value() {
2464
+ return areaValue();
2397
2465
  },
2398
2466
  get ["class"]() {
2399
- return `${style$4.areaStyle} ${customProps.class ?? ""} ${customProps.transparent ? customProps.transparent : ""}`;
2400
- },
2401
- get value() {
2402
- return customProps.text();
2467
+ return `${style$4.areaStyle} ${customProps.class ?? ''} ${customProps.transparent ? customProps.transparent : ''}`;
2403
2468
  },
2404
- "onInput": e => {
2405
- customProps.setText(e.currentTarget.value);
2406
- OnInput();
2407
- if (!!context?.getName && !!e.currentTarget.value.trim()) {
2408
- context?.setValue(e.currentTarget.value);
2409
- context?.setTextInside(true);
2410
- } else if (!!context?.getName && !e.currentTarget.value.trim()) {
2411
- context?.setValue("");
2412
- context?.setTextInside(false);
2413
- }
2469
+ get placeholder() {
2470
+ return fieldCtx?.getTextInside?.() && !fieldCtx?.getFocused?.() ? '' : props.placeholder;
2414
2471
  },
2415
2472
  get title() {
2416
2473
  return customProps.tooltip;
2417
- }
2474
+ },
2475
+ "onInput": handleInput,
2476
+ "onFocus": () => fieldCtx?.setFocused?.(true),
2477
+ "onBlur": () => fieldCtx?.setFocused?.(false)
2418
2478
  }), false, false);
2419
2479
  return _el$;
2420
2480
  })();
@@ -3834,6 +3894,13 @@ class FormGroup {
3834
3894
  this.validators = newValidators;
3835
3895
  this.errors = createSignal(newErrors);
3836
3896
  }
3897
+ /**
3898
+ * INTERNAL: returns the reactive internal store reference (DO NOT MUTATE outside FormGroup).
3899
+ * Used for bridging into FormContext without triggering cloning loops.
3900
+ */
3901
+ _unsafeRaw() {
3902
+ return this.internalDataSignal[0];
3903
+ }
3837
3904
  get(key) {
3838
3905
  if (!key) {
3839
3906
  // Custom clone that preserves FormArray instances (structuredClone fails on functions inside)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coles-solid-library",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "A SolidJS mostly UI library",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",