@fairfox/polly 0.77.3 → 0.79.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.
Files changed (87) hide show
  1. package/dist/cli/polly.js +46 -3
  2. package/dist/cli/polly.js.map +3 -3
  3. package/dist/src/background/index.js.map +3 -3
  4. package/dist/src/background/message-router.js.map +3 -3
  5. package/dist/src/client/index.js +137 -32
  6. package/dist/src/client/index.js.map +6 -5
  7. package/dist/src/client/wrapper.d.ts +39 -2
  8. package/dist/src/elysia/index.js +22 -3
  9. package/dist/src/elysia/index.js.map +5 -5
  10. package/dist/src/elysia/route-match.d.ts +13 -0
  11. package/dist/src/index.d.ts +1 -1
  12. package/dist/src/index.js +12 -2
  13. package/dist/src/index.js.map +7 -7
  14. package/dist/src/mesh.js +87 -46
  15. package/dist/src/mesh.js.map +12 -11
  16. package/dist/src/peer.js +7 -3
  17. package/dist/src/peer.js.map +6 -6
  18. package/dist/src/polly-ui/Badge.d.ts +5 -0
  19. package/dist/src/polly-ui/Button.d.ts +31 -6
  20. package/dist/src/polly-ui/Dropdown.d.ts +6 -0
  21. package/dist/src/polly-ui/Select.d.ts +11 -1
  22. package/dist/src/polly-ui/TextInput.d.ts +30 -0
  23. package/dist/src/polly-ui/index.css +10 -0
  24. package/dist/src/polly-ui/index.js +81 -32
  25. package/dist/src/polly-ui/index.js.map +10 -10
  26. package/dist/src/polly-ui/styles.css +10 -0
  27. package/dist/src/shared/adapters/index.js.map +3 -3
  28. package/dist/src/shared/lib/context-helpers.js.map +3 -3
  29. package/dist/src/shared/lib/mesh-client.d.ts +38 -0
  30. package/dist/src/shared/lib/mesh-signaling-client.d.ts +6 -5
  31. package/dist/src/shared/lib/mesh-state.d.ts +21 -0
  32. package/dist/src/shared/lib/message-bus.js.map +3 -3
  33. package/dist/src/shared/lib/peer-relay-adapter.d.ts +5 -0
  34. package/dist/src/shared/lib/peer-repo-server.d.ts +15 -0
  35. package/dist/src/shared/lib/resource.js +11 -2
  36. package/dist/src/shared/lib/resource.js.map +6 -6
  37. package/dist/src/shared/lib/state.d.ts +20 -0
  38. package/dist/src/shared/lib/state.js +11 -1
  39. package/dist/src/shared/lib/state.js.map +5 -5
  40. package/dist/src/shared/state/app-state.js +10 -1
  41. package/dist/src/shared/state/app-state.js.map +5 -5
  42. package/dist/tools/init/src/cli.js +23 -2
  43. package/dist/tools/init/src/cli.js.map +4 -4
  44. package/dist/tools/init/templates/pwa/package.json.template +1 -1
  45. package/dist/tools/init/templates/pwa/src/service-worker.ts.template +26 -15
  46. package/dist/tools/init/templates/pwa/src/shared-worker.ts.template +13 -3
  47. package/dist/tools/init/templates/pwa/tsconfig.json.template +2 -2
  48. package/dist/tools/init/templates/pwa/tsconfig.worker.json.template +17 -0
  49. package/dist/tools/test/src/browser/index.js +5 -2
  50. package/dist/tools/test/src/browser/index.js.map +3 -3
  51. package/dist/tools/test/src/contrast/index.js +20 -15
  52. package/dist/tools/test/src/contrast/index.js.map +3 -3
  53. package/dist/tools/test/src/e2e-cli/index.d.ts +10 -0
  54. package/dist/tools/test/src/e2e-cli/run-cli.d.ts +25 -0
  55. package/dist/tools/test/src/e2e-cli/with-temp-dir.d.ts +15 -0
  56. package/dist/tools/test/src/e2e-mesh/index.js +12 -7
  57. package/dist/tools/test/src/e2e-mesh/index.js.map +4 -4
  58. package/dist/tools/test/src/e2e-mesh/launch-peer.d.ts +7 -1
  59. package/dist/tools/test/src/e2e-relay/index.d.ts +12 -0
  60. package/dist/tools/test/src/e2e-relay/wait-for-relay-convergence.d.ts +27 -0
  61. package/dist/tools/test/src/e2e-relay/with-repo-server.d.ts +24 -0
  62. package/dist/tools/test/src/e2e-shared/assert.d.ts +18 -0
  63. package/dist/tools/test/src/e2e-shared/contract.d.ts +40 -0
  64. package/dist/tools/test/src/e2e-shared/index.d.ts +2 -0
  65. package/dist/tools/test/src/tiers/args.d.ts +23 -0
  66. package/dist/tools/test/src/tiers/cli.d.ts +2 -0
  67. package/dist/tools/test/src/tiers/cli.js +490 -0
  68. package/dist/tools/test/src/tiers/cli.js.map +16 -0
  69. package/dist/tools/test/src/tiers/detect.d.ts +12 -0
  70. package/dist/tools/test/src/tiers/discover.d.ts +2 -0
  71. package/dist/tools/test/src/tiers/engine.d.ts +3 -0
  72. package/dist/tools/test/src/tiers/index.d.ts +14 -0
  73. package/dist/tools/test/src/tiers/protocol.d.ts +10 -0
  74. package/dist/tools/test/src/tiers/reporter.d.ts +12 -0
  75. package/dist/tools/test/src/tiers/types.d.ts +94 -0
  76. package/dist/tools/test/src/tiers/worker.d.ts +2 -0
  77. package/dist/tools/test/src/tiers/worker.js +60 -0
  78. package/dist/tools/test/src/tiers/worker.js.map +12 -0
  79. package/dist/tools/verify/src/cli.js +322 -27
  80. package/dist/tools/verify/src/cli.js.map +13 -10
  81. package/dist/tools/verify/src/config.d.ts +10 -0
  82. package/dist/tools/verify/src/config.js.map +2 -2
  83. package/dist/tools/verify/src/stryker/index.js +20 -11
  84. package/dist/tools/verify/src/stryker/index.js.map +3 -3
  85. package/dist/tools/visualize/src/cli.js +8 -5
  86. package/dist/tools/visualize/src/cli.js.map +4 -4
  87. package/package.json +16 -6
@@ -5,6 +5,9 @@
5
5
  * from the `--polly-status-*` token family. The default variant uses
6
6
  * surface-sunken + muted text for a neutral pill. Consumers style
7
7
  * placement via className; the primitive owns size, shape, and colour.
8
+ *
9
+ * A badge is often an abbreviated or iconic status ("3", "●"); pass
10
+ * `title` to attach an explanatory hover tooltip ("3 peers connected").
8
11
  */
9
12
  import type { ComponentChildren, JSX } from "preact";
10
13
  export type BadgeVariant = "default" | "info" | "success" | "warning" | "danger";
@@ -13,5 +16,7 @@ export type BadgeProps = {
13
16
  variant?: BadgeVariant;
14
17
  className?: string;
15
18
  id?: string;
19
+ /** Native hover tooltip — explains an abbreviated or iconic badge. */
20
+ title?: string;
16
21
  };
17
22
  export declare function Badge(props: BadgeProps): JSX.Element;
@@ -7,9 +7,18 @@
7
7
  * size picks the padding + font scale (small/normal/large). Icon +
8
8
  * label are arranged with a nested inline <Layout>.
9
9
  *
10
+ * A text `label` is the accessible name. An icon-only button (icon, no
11
+ * label) has none, so the type REQUIRES `aria-label` there — an unnamed
12
+ * icon button won't compile. No build-time lint needed; the call site
13
+ * itself is the gate.
14
+ *
10
15
  * Action wiring is declared via data-* attributes consumed by the
11
16
  * global event delegator in @fairfox/polly/actions — Button does not
12
17
  * accept an onClick prop.
18
+ *
19
+ * Every button surfaces a native hover tooltip: an explicit `title`
20
+ * wins, else the visible text label, else the `aria-label` (so an
21
+ * icon-only button still gets one). Pass `title=""` to suppress it.
13
22
  */
14
23
  import type { ComponentChildren, JSX, VNode } from "preact";
15
24
  export type ButtonTier = "primary" | "secondary" | "tertiary";
@@ -29,10 +38,7 @@ type BaseButtonProps = {
29
38
  bounded?: boolean;
30
39
  className?: string;
31
40
  title?: string;
32
- icon?: VNode;
33
- label: ComponentChildren;
34
41
  "data-action"?: string;
35
- "aria-label"?: string;
36
42
  /** Additional action-payload attributes the event delegator parses
37
43
  * into `ctx.data`. `data-action-tid="t-17"` becomes `{ tid: "t-17" }`,
38
44
  * `data-action-item-id="…"` becomes `{ itemId: "…" }`, and so on.
@@ -40,14 +46,33 @@ type BaseButtonProps = {
40
46
  * consumer cares about type-checks without a Button-side enumeration. */
41
47
  [actionDataAttr: `data-action-${string}`]: string | undefined;
42
48
  };
43
- type ButtonAsButton = BaseButtonProps & {
49
+ /**
50
+ * Content/accessibility dimension. A visible text `label` is the
51
+ * accessible name, so it makes `aria-label` optional. An icon-only
52
+ * button has no text to name it, so `aria-label` is REQUIRED — you
53
+ * cannot construct an unnamed icon button. (Button's hover title then
54
+ * falls back to that aria-label, so the same prop gives both the
55
+ * accessible name and the tooltip.)
56
+ */
57
+ type LabelledContent = {
58
+ label: ComponentChildren;
59
+ icon?: VNode;
60
+ "aria-label"?: string;
61
+ };
62
+ type IconOnlyContent = {
63
+ icon: VNode;
64
+ label?: never;
65
+ "aria-label": string;
66
+ };
67
+ type ButtonContent = LabelledContent | IconOnlyContent;
68
+ type ButtonElement = {
44
69
  href?: never;
45
70
  target?: never;
46
71
  rel?: never;
47
72
  download?: never;
48
73
  type?: "button" | "submit" | "reset";
49
74
  };
50
- type ButtonAsLink = BaseButtonProps & {
75
+ type LinkElement = {
51
76
  href: string;
52
77
  target?: string;
53
78
  rel?: string;
@@ -56,6 +81,6 @@ type ButtonAsLink = BaseButtonProps & {
56
81
  download?: string;
57
82
  type?: never;
58
83
  };
59
- export type ButtonProps = ButtonAsButton | ButtonAsLink;
84
+ export type ButtonProps = BaseButtonProps & ButtonContent & (ButtonElement | LinkElement);
60
85
  export declare function Button(props: ButtonProps): JSX.Element;
61
86
  export {};
@@ -35,6 +35,12 @@ export type DropdownProps = {
35
35
  triggerClassName?: string;
36
36
  /** Disables the trigger <button>. */
37
37
  triggerDisabled?: boolean;
38
+ /**
39
+ * Native hover tooltip on the trigger <button>. Select/ActionSelect
40
+ * pass the current selection so a truncated trigger still reveals its
41
+ * full value on hover.
42
+ */
43
+ triggerTitle?: string;
38
44
  id?: string;
39
45
  };
40
46
  export declare function Dropdown(props: DropdownProps): JSX.Element;
@@ -5,7 +5,9 @@
5
5
  * row for multi-select. The `selected` state is a Signal<Set<T>>;
6
6
  * single-select replaces the set, multi-select toggles membership.
7
7
  * Multi-select mode also shows Select All / Clear action buttons at
8
- * the top of the menu.
8
+ * the top of the menu. Single-select mode can opt into `clearable`,
9
+ * which prepends a placeholder-labelled option that returns the
10
+ * selection to empty — for "Any …" filter semantics.
9
11
  */
10
12
  import { type Signal } from "@preact/signals";
11
13
  import type { JSX } from "preact";
@@ -19,6 +21,14 @@ export type SelectProps<T = string> = {
19
21
  label?: string;
20
22
  placeholder?: string;
21
23
  multiSelect?: boolean;
24
+ /**
25
+ * Single-select only: prepend an option labelled with the placeholder
26
+ * that returns the selection to empty. Without it, picking an option
27
+ * is a trap — nothing in the list leads back to "any". Use it for
28
+ * optional filters ("Any stage"), not required form fields
29
+ * ("Select a team"). Ignored in multi-select, which has Clear.
30
+ */
31
+ clearable?: boolean;
22
32
  disabled?: boolean;
23
33
  /** Apply a comfortable minimum width to the trigger. Default: sizes to content. */
24
34
  wide?: boolean;
@@ -7,17 +7,47 @@
7
7
  * wired by the shared input-base, the stable `[data-polly-input]` hook
8
8
  * is applied for styling, and `invalid` flips `aria-invalid` and
9
9
  * `data-state="invalid"` for consumer selectors.
10
+ *
11
+ * `inputType` sets the native single-line input type — "text" (default),
12
+ * "email", "number", "date", and friends — so the browser supplies the
13
+ * right keyboard and validation. `min`/`max`/`step` apply to the
14
+ * single-line variant (e.g. numeric or date bounds) and keep filter
15
+ * fields from being unvalidated free text.
16
+ *
17
+ * `error`, when supplied, renders a message beneath the field, wraps the
18
+ * control in a labelled group, and auto-wires `aria-describedby` plus the
19
+ * invalid state — no manual id juggling. Without `error` the primitive
20
+ * stays a bare native element, exactly as before, so consumers that own
21
+ * their own surrounding structure are unaffected.
10
22
  */
11
23
  import type { Signal } from "@preact/signals";
12
24
  import type { JSX } from "preact";
13
25
  type Variant = "single" | "multi";
26
+ /** Native single-line input types polly's filter/form fields use. */
27
+ export type TextInputType = "text" | "email" | "url" | "tel" | "search" | "password" | "number" | "date" | "datetime-local" | "time" | "month" | "week";
14
28
  export type TextInputProps = {
15
29
  name: string;
16
30
  /** Signal<string> for controlled, string for uncontrolled default. */
17
31
  value?: Signal<string> | string;
18
32
  variant?: Variant;
33
+ /** Native input type for the single-line variant. Default: "text". */
34
+ inputType?: TextInputType;
35
+ /** Lower bound for the single-line variant (number/date types). */
36
+ min?: number | string;
37
+ /** Upper bound for the single-line variant (number/date types). */
38
+ max?: number | string;
39
+ /** Step granularity for the single-line variant (number/date types). */
40
+ step?: number | string;
19
41
  placeholder?: string;
42
+ /** Native hover tooltip — a hint beyond the placeholder/label. */
43
+ title?: string;
20
44
  invalid?: boolean;
45
+ /**
46
+ * Validation message rendered beneath the field. When present the
47
+ * primitive wraps the control, links the message via aria-describedby,
48
+ * and marks the field invalid. Omit it to keep a bare native element.
49
+ */
50
+ error?: string;
21
51
  required?: boolean;
22
52
  disabled?: boolean;
23
53
  readOnly?: boolean;
@@ -1033,6 +1033,16 @@
1033
1033
  resize: vertical;
1034
1034
  max-block-size: 40em;
1035
1035
  }
1036
+
1037
+ .field_ez4_Vg {
1038
+ display: grid;
1039
+ gap: var(--polly-space-xs);
1040
+ }
1041
+
1042
+ .error_ez4_Vg {
1043
+ font-size: var(--polly-text-sm);
1044
+ color: var(--polly-danger);
1045
+ }
1036
1046
  }
1037
1047
 
1038
1048
  /* src/polly-ui/Toast.module.css */
@@ -407,6 +407,7 @@ function Dropdown(props) {
407
407
  className,
408
408
  triggerClassName,
409
409
  triggerDisabled = false,
410
+ triggerTitle,
410
411
  id
411
412
  } = props;
412
413
  const menuRef = useRef2(null);
@@ -469,7 +470,7 @@ function Dropdown(props) {
469
470
  menu.style.top = `${top}px`;
470
471
  };
471
472
  const onBeforeToggle = (e) => {
472
- if (e.newState === "open") {
473
+ if ("newState" in e && e.newState === "open") {
473
474
  positionMenu();
474
475
  }
475
476
  };
@@ -525,6 +526,7 @@ function Dropdown(props) {
525
526
  type: "button",
526
527
  class: triggerClassName ?? Dropdown_module_default["trigger"] ?? "",
527
528
  disabled: triggerDisabled,
529
+ title: triggerTitle,
528
530
  "data-open": isOpen.value ? "true" : "false",
529
531
  children: trigger
530
532
  }, undefined, false, undefined, this),
@@ -532,6 +534,7 @@ function Dropdown(props) {
532
534
  ref: menuRef,
533
535
  id: popoverId,
534
536
  role: "listbox",
537
+ "aria-multiselectable": multiSelect ? "true" : undefined,
535
538
  class: Dropdown_module_default["menu"] ?? "",
536
539
  "data-align": align,
537
540
  popover: "auto",
@@ -621,6 +624,7 @@ function ActionSelect(props) {
621
624
  disabled ? /* @__PURE__ */ jsxDEV4("span", {
622
625
  class: triggerClass,
623
626
  "aria-disabled": "true",
627
+ title: displayText,
624
628
  children: /* @__PURE__ */ jsxDEV4("span", {
625
629
  class: Select_module_default["triggerLabel"],
626
630
  children: displayText
@@ -628,6 +632,7 @@ function ActionSelect(props) {
628
632
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Dropdown, {
629
633
  isOpen,
630
634
  triggerClassName: triggerClass,
635
+ triggerTitle: displayText,
631
636
  trigger: /* @__PURE__ */ jsxDEV4(Fragment, {
632
637
  children: [
633
638
  /* @__PURE__ */ jsxDEV4("span", {
@@ -681,7 +686,7 @@ function variantClass(variant) {
681
686
  return;
682
687
  }
683
688
  function Badge(props) {
684
- const { children, variant = "default", className, id } = props;
689
+ const { children, variant = "default", className, id, title } = props;
685
690
  const parts = [Badge_module_default["badge"]];
686
691
  const v = variantClass(variant);
687
692
  if (v)
@@ -690,6 +695,7 @@ function Badge(props) {
690
695
  parts.push(className);
691
696
  return /* @__PURE__ */ jsxDEV5("span", {
692
697
  id,
698
+ title,
693
699
  class: parts.join(" "),
694
700
  "data-polly-ui": true,
695
701
  "data-polly-badge": variant,
@@ -781,13 +787,14 @@ function Button(props) {
781
787
  alignItems: "center",
782
788
  children: [
783
789
  icon,
784
- /* @__PURE__ */ jsxDEV6("span", {
790
+ label !== undefined && /* @__PURE__ */ jsxDEV6("span", {
785
791
  children: label
786
792
  }, undefined, false, undefined, this)
787
793
  ]
788
794
  }, undefined, true, undefined, this) : label;
789
795
  const dataAction = props["data-action"];
790
796
  const ariaLabel = props["aria-label"];
797
+ const resolvedTitle = title ?? (typeof label === "string" && label.length > 0 ? label : typeof ariaLabel === "string" ? ariaLabel : undefined);
791
798
  const actionDataAttrs = {};
792
799
  for (const key of Object.keys(props)) {
793
800
  if (key.startsWith("data-action-")) {
@@ -801,7 +808,7 @@ function Button(props) {
801
808
  return /* @__PURE__ */ jsxDEV6("a", {
802
809
  id,
803
810
  class: buttonClass,
804
- title,
811
+ title: resolvedTitle,
805
812
  href: disabled ? undefined : props.href,
806
813
  target: "target" in props ? props.target : undefined,
807
814
  rel: "rel" in props ? props.rel : undefined,
@@ -819,7 +826,7 @@ function Button(props) {
819
826
  return /* @__PURE__ */ jsxDEV6("button", {
820
827
  id,
821
828
  class: buttonClass,
822
- title,
829
+ title: resolvedTitle,
823
830
  type: resolvedType,
824
831
  disabled,
825
832
  "aria-label": ariaLabel,
@@ -1802,6 +1809,7 @@ function Select(props) {
1802
1809
  label,
1803
1810
  placeholder = "Select…",
1804
1811
  multiSelect = false,
1812
+ clearable = false,
1805
1813
  disabled = false,
1806
1814
  wide = false,
1807
1815
  className,
@@ -1832,6 +1840,10 @@ function Select(props) {
1832
1840
  const handleClear = () => {
1833
1841
  selected.value = new Set;
1834
1842
  };
1843
+ const handleClearOption = () => {
1844
+ selected.value = new Set;
1845
+ isOpen2.value = false;
1846
+ };
1835
1847
  const triggerParts = [Select_module_default["trigger"] ?? ""];
1836
1848
  if (isEmpty.value)
1837
1849
  triggerParts.push(Select_module_default["placeholder"] ?? "");
@@ -1868,6 +1880,7 @@ function Select(props) {
1868
1880
  trigger: triggerContent,
1869
1881
  triggerClassName: triggerClass,
1870
1882
  triggerDisabled: disabled,
1883
+ triggerTitle: displayText.value,
1871
1884
  multiSelect,
1872
1885
  children: [
1873
1886
  multiSelect && /* @__PURE__ */ jsxDEV17("div", {
@@ -1891,11 +1904,24 @@ function Select(props) {
1891
1904
  ]
1892
1905
  }, undefined, true, undefined, this)
1893
1906
  }, undefined, false, undefined, this),
1907
+ !multiSelect && clearable && /* @__PURE__ */ jsxDEV17("button", {
1908
+ type: "button",
1909
+ role: "option",
1910
+ "aria-selected": isEmpty.value ? "true" : "false",
1911
+ class: isEmpty.value ? `${Select_module_default["option"]} ${Select_module_default["optionSelected"]}` : Select_module_default["option"],
1912
+ "data-polly-select-clear": true,
1913
+ onClick: handleClearOption,
1914
+ children: /* @__PURE__ */ jsxDEV17("span", {
1915
+ children: placeholder
1916
+ }, undefined, false, undefined, this)
1917
+ }, undefined, false, undefined, this),
1894
1918
  options.map((opt) => {
1895
1919
  const isSelected = selected.value.has(opt.value);
1896
1920
  const optClass = isSelected ? `${Select_module_default["option"]} ${Select_module_default["optionSelected"]}` : Select_module_default["option"];
1897
1921
  return /* @__PURE__ */ jsxDEV17("button", {
1898
1922
  type: "button",
1923
+ role: "option",
1924
+ "aria-selected": isSelected ? "true" : "false",
1899
1925
  class: optClass,
1900
1926
  onClick: () => handleOptionClick(opt.value),
1901
1927
  children: [
@@ -2070,6 +2096,9 @@ function Text(props) {
2070
2096
  "data-polly-text": tone
2071
2097
  }, children);
2072
2098
  }
2099
+ // src/polly-ui/TextInput.tsx
2100
+ import { useId as useId2 } from "preact/hooks";
2101
+
2073
2102
  // src/polly-ui/internal/input-base.ts
2074
2103
  function buildInputA11y(props) {
2075
2104
  const attrs = {
@@ -2091,7 +2120,9 @@ function buildInputA11y(props) {
2091
2120
 
2092
2121
  // src/polly-ui/TextInput.module.css
2093
2122
  var TextInput_module_default = {
2094
- input: "input_ez4_Vg"
2123
+ input: "input_ez4_Vg",
2124
+ field: "field_ez4_Vg",
2125
+ error: "error_ez4_Vg"
2095
2126
  };
2096
2127
 
2097
2128
  // src/polly-ui/TextInput.tsx
@@ -2101,12 +2132,15 @@ function isSignal2(v) {
2101
2132
  }
2102
2133
  function TextInput(props) {
2103
2134
  const variant = props.variant ?? "single";
2135
+ const errorId = useId2();
2136
+ const hasError = props.error !== undefined && props.error !== "";
2137
+ const describedBy = [props.describedBy, hasError ? errorId : undefined].filter(Boolean).join(" ") || undefined;
2104
2138
  const a11y = buildInputA11y({
2105
2139
  name: props.name,
2106
2140
  id: props.id,
2107
2141
  required: props.required,
2108
- invalid: props.invalid,
2109
- describedBy: props.describedBy,
2142
+ invalid: props.invalid || hasError,
2143
+ describedBy,
2110
2144
  placeholder: props.placeholder,
2111
2145
  disabled: props.disabled,
2112
2146
  readOnly: props.readOnly
@@ -2115,36 +2149,51 @@ function TextInput(props) {
2115
2149
  const stringValue = controlled ? props.value.value : undefined;
2116
2150
  const defaultValue = controlled ? undefined : props.value ?? "";
2117
2151
  const className = props.className ? `${TextInput_module_default["input"]} ${props.className}` : TextInput_module_default["input"];
2118
- if (variant === "multi") {
2119
- return /* @__PURE__ */ jsxDEV20("textarea", {
2120
- ...a11y,
2121
- class: className,
2122
- "data-polly-input-variant": "multi",
2123
- rows: props.rows,
2124
- autoFocus: props.autoFocus,
2125
- value: stringValue,
2126
- defaultValue,
2127
- onInput: (e) => {
2128
- if (controlled) {
2129
- props.value.value = e.currentTarget.value;
2130
- }
2131
- }
2132
- }, undefined, false, undefined, this);
2133
- }
2134
- return /* @__PURE__ */ jsxDEV20("input", {
2152
+ const onInput = (e) => {
2153
+ if (controlled) {
2154
+ props.value.value = e.currentTarget.value;
2155
+ }
2156
+ };
2157
+ const control = variant === "multi" ? /* @__PURE__ */ jsxDEV20("textarea", {
2158
+ ...a11y,
2159
+ class: className,
2160
+ title: props.title,
2161
+ "data-polly-input-variant": "multi",
2162
+ rows: props.rows,
2163
+ autoFocus: props.autoFocus,
2164
+ value: stringValue,
2165
+ defaultValue,
2166
+ onInput
2167
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20("input", {
2135
2168
  ...a11y,
2136
- type: "text",
2169
+ type: props.inputType ?? "text",
2137
2170
  class: className,
2171
+ title: props.title,
2138
2172
  "data-polly-input-variant": "single",
2173
+ min: props.min,
2174
+ max: props.max,
2175
+ step: props.step,
2139
2176
  autoFocus: props.autoFocus,
2140
2177
  value: stringValue,
2141
2178
  defaultValue,
2142
- onInput: (e) => {
2143
- if (controlled) {
2144
- props.value.value = e.currentTarget.value;
2145
- }
2146
- }
2179
+ onInput
2147
2180
  }, undefined, false, undefined, this);
2181
+ if (!hasError)
2182
+ return control;
2183
+ return /* @__PURE__ */ jsxDEV20("div", {
2184
+ class: TextInput_module_default["field"],
2185
+ "data-polly-ui": true,
2186
+ "data-polly-field": true,
2187
+ children: [
2188
+ control,
2189
+ /* @__PURE__ */ jsxDEV20("span", {
2190
+ id: errorId,
2191
+ role: "alert",
2192
+ class: TextInput_module_default["error"],
2193
+ children: props.error
2194
+ }, undefined, false, undefined, this)
2195
+ ]
2196
+ }, undefined, true, undefined, this);
2148
2197
  }
2149
2198
  // src/polly-ui/Toast.tsx
2150
2199
  import { createPortal as createPortal2 } from "preact/compat";
@@ -2335,4 +2384,4 @@ export {
2335
2384
  ActionForm
2336
2385
  };
2337
2386
 
2338
- //# debugId=E9957371E8115C1E64756E2164756E21
2387
+ //# debugId=0625A7B5727F691364756E2164756E21