coles-solid-library 0.3.7 → 0.3.8

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,23 +1599,52 @@ 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());
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 => {
1623
+ if (props.checked === undefined) {
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
+ }
1632
+ }
1633
+ field?.setTextInside?.(next);
1634
+ props.onChange?.(next);
1635
+ };
1594
1636
  const handleChange = e => {
1595
1637
  const target = e.currentTarget;
1596
1638
  const newChecked = target.checked;
1597
- if (props.checked === undefined) {
1598
- setInternalChecked(newChecked);
1599
- props.onChange?.(newChecked);
1600
- } else {
1601
- // Controlled: prevent native state flip from persisting
1639
+ if (props.checked !== undefined) {
1640
+ // controlled: revert DOM change and just emit
1602
1641
  e.preventDefault();
1603
- // Re-sync DOM to prop (in case browser already toggled visually before preventDefault took effect)
1604
1642
  queueMicrotask(() => {
1605
1643
  target.checked = !!props.checked;
1606
1644
  });
1607
1645
  props.onChange?.(newChecked);
1646
+ } else {
1647
+ commitValue(newChecked);
1608
1648
  }
1609
1649
  };
1610
1650
  const handleClick = e => {
@@ -2006,6 +2046,9 @@ function Select(props) {
2006
2046
  }
2007
2047
  // store orientation for class assignment
2008
2048
  setDropTop(placeAbove);
2049
+ // Compute available vertical space for dropdown and clamp to a sensible minimum
2050
+ const availableSpace = (placeAbove ? spaceAbove : spaceBelow) - VIEWPORT_MARGIN;
2051
+ const maxHeight = Math.max(160, availableSpace); // ensure at least 160px so a few options are visible
2009
2052
  let newY;
2010
2053
  if (!placeAbove) {
2011
2054
  newY = baseRect.bottom + window.scrollY; // default below
@@ -2027,6 +2070,7 @@ function Select(props) {
2027
2070
  dropdown.style.left = `${newX}px`;
2028
2071
  dropdown.style.top = `${newY}px`;
2029
2072
  dropdown.style.width = `${baseRect.width}px`;
2073
+ dropdown.style.maxHeight = `${maxHeight}px`;
2030
2074
  };
2031
2075
  // Update width of select to match option text width
2032
2076
  createEffect(() => {
@@ -2178,7 +2222,6 @@ function Select(props) {
2178
2222
  get children() {
2179
2223
  var _el$12 = _tmpl$4$1();
2180
2224
  use(setDropdownRef, _el$12);
2181
- _el$12.style.setProperty("max-height", "calc(100vh - 8px)");
2182
2225
  insert(_el$12, () => props.children);
2183
2226
  createRenderEffect(_p$ => {
2184
2227
  var _v$ = `${styles$7['solid_select__dropdown']} ${dropTop() ? styles$7.dropTop : styles$7.dropBottom} ${open() ? styles$7.open : ''} ${props.dropdownClass || ""}`,
@@ -2345,76 +2388,101 @@ var _tmpl$$e = /*#__PURE__*/template(`<textarea>`);
2345
2388
  const TextArea = props => {
2346
2389
  let myElement;
2347
2390
  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
- }
2391
+ const fieldCtx = useFormProvider();
2392
+ const formCtx = useFormContext();
2393
+ const formName = fieldCtx?.formName;
2394
+ // Internal state only used when not provided externally and not in form context.
2395
+ const [internal, setInternal] = createSignal(customProps.text ? customProps.text() : "");
2396
+ // Determine current value priority: FormGroup -> external accessor -> internal state
2397
+ const areaValue = createMemo(() => {
2398
+ if (formName && formCtx?.data && !isNullish(formCtx.data[formName])) {
2399
+ return formCtx.data[formName];
2400
+ }
2401
+ if (customProps.text) return customProps.text();
2402
+ return internal();
2403
+ });
2404
+ function resizeToContent() {
2405
+ if (!myElement) return;
2406
+ myElement.style.height = 'auto';
2407
+ const minHeight = customProps.minSize?.height ?? 100;
2408
+ const currentHeight = Math.max(minHeight, myElement.scrollHeight);
2409
+ myElement.style.height = `${currentHeight}px`;
2410
+ myElement.style.overflowY = 'hidden';
2360
2411
  }
2361
- // sets the field type on mount and sets the height of the textarea
2412
+ // Set field type & initial floating state
2362
2413
  onMount(() => {
2363
- OnInput();
2364
- context?.setFieldType("textarea");
2414
+ resizeToContent();
2415
+ fieldCtx?.setFieldType?.('textarea');
2416
+ if (formName && formCtx?.data) {
2417
+ const v = formCtx.data[formName];
2418
+ if (!isNullish(v) && String(v).trim() !== '') {
2419
+ fieldCtx?.setValue?.(v);
2420
+ fieldCtx?.setTextInside?.(true);
2421
+ } else {
2422
+ fieldCtx?.setTextInside?.(false);
2423
+ }
2424
+ } else if (customProps.text) {
2425
+ const v = customProps.text();
2426
+ fieldCtx?.setTextInside?.(!(v === undefined || v === null || v.trim() === ''));
2427
+ }
2365
2428
  });
2366
- // updates height of textarea when text is changed
2367
- createRenderEffect(() => {
2368
- OnInput();
2369
- customProps.text();
2429
+ // React to programmatic FormGroup.set changes
2430
+ createEffect(() => {
2431
+ if (formName && formCtx?.data) {
2432
+ const v = formCtx.data[formName];
2433
+ // keep internal state aligned if unmanaged
2434
+ if (!customProps.text && internal() !== v) setInternal(String(v ?? ''));
2435
+ if (fieldCtx) {
2436
+ const has = !(v === undefined || v === null || typeof v === 'string' && v.trim() === '');
2437
+ fieldCtx.setTextInside(has);
2438
+ fieldCtx.setValue?.(v);
2439
+ }
2440
+ }
2370
2441
  });
2442
+ // Resize whenever value changes (user or programmatic)
2371
2443
  createEffect(() => {
2372
- if (Object.keys(props).includes("required") || props?.required === true) {
2373
- context?.setName(old => `${old} *`);
2444
+ areaValue();
2445
+ queueMicrotask(resizeToContent);
2446
+ });
2447
+ const handleInput = e => {
2448
+ const newVal = e.currentTarget.value;
2449
+ // Update whichever source is active
2450
+ if (formName && formCtx?.formGroup) {
2451
+ formCtx.formGroup.set(formName, newVal);
2452
+ formCtx.setData?.(formName, newVal);
2453
+ } else if (customProps.setText) {
2454
+ customProps.setText(newVal);
2374
2455
  } else {
2375
- context?.setName(old => old);
2456
+ setInternal(newVal);
2376
2457
  }
2377
- });
2458
+ if (fieldCtx) {
2459
+ fieldCtx.setValue?.(newVal);
2460
+ fieldCtx.setTextInside?.(newVal.trim().length > 0);
2461
+ }
2462
+ resizeToContent();
2463
+ };
2378
2464
  return (() => {
2379
2465
  var _el$ = _tmpl$$e();
2380
2466
  use(el => {
2381
2467
  myElement = el;
2382
- OnInput();
2468
+ resizeToContent();
2383
2469
  }, _el$);
2384
2470
  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;
2471
+ get value() {
2472
+ return areaValue();
2397
2473
  },
2398
2474
  get ["class"]() {
2399
- return `${style$4.areaStyle} ${customProps.class ?? ""} ${customProps.transparent ? customProps.transparent : ""}`;
2475
+ return `${style$4.areaStyle} ${customProps.class ?? ''} ${customProps.transparent ? customProps.transparent : ''}`;
2400
2476
  },
2401
- get value() {
2402
- return customProps.text();
2403
- },
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
- }
2477
+ get placeholder() {
2478
+ return fieldCtx?.getTextInside?.() && !fieldCtx?.getFocused?.() ? '' : props.placeholder;
2414
2479
  },
2415
2480
  get title() {
2416
2481
  return customProps.tooltip;
2417
- }
2482
+ },
2483
+ "onInput": handleInput,
2484
+ "onFocus": () => fieldCtx?.setFocused?.(true),
2485
+ "onBlur": () => fieldCtx?.setFocused?.(false)
2418
2486
  }), false, false);
2419
2487
  return _el$;
2420
2488
  })();
@@ -3834,6 +3902,13 @@ class FormGroup {
3834
3902
  this.validators = newValidators;
3835
3903
  this.errors = createSignal(newErrors);
3836
3904
  }
3905
+ /**
3906
+ * INTERNAL: returns the reactive internal store reference (DO NOT MUTATE outside FormGroup).
3907
+ * Used for bridging into FormContext without triggering cloning loops.
3908
+ */
3909
+ _unsafeRaw() {
3910
+ return this.internalDataSignal[0];
3911
+ }
3837
3912
  get(key) {
3838
3913
  if (!key) {
3839
3914
  // 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.8",
4
4
  "description": "A SolidJS mostly UI library",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",