@page-speed/forms 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # OpenSite Page Speed Forms
1
+ <img width="1200" height="330" alt="page-speed-forms-npm-module" src="https://github.com/user-attachments/assets/4dd21311-9de6-4c42-be75-bbc8fe5a0192" />
2
+
3
+ # @page-speed/forms
2
4
 
3
5
  Type-safe form state management and validation for React applications.
4
6
 
@@ -6,7 +8,7 @@ Type-safe form state management and validation for React applications.
6
8
 
7
9
  OpenSite Page Speed Forms is a high-performance library designed to streamline form state management, validation, and submission handling in React applications. This library is part of OpenSite AI's open-source ecosystem, built for performance and open collaboration. By emphasizing type safety and modularity, it aligns with OpenSite's goal to create scalable, open, and developer-friendly performance tooling.
8
10
 
9
- Learn more at [opensite.ai](https://opensite.ai).
11
+ Learn more at [OpenSite.ai Developers](https://opensite.ai/developers).
10
12
 
11
13
  ## Key Features
12
14
 
@@ -91,4 +93,4 @@ Licensed under the BSD 3-Clause License. See the [LICENSE](./LICENSE) file for d
91
93
 
92
94
  - [Domain Extractor](https://github.com/opensite-ai/domain_extractor)
93
95
  - [Page Speed Hooks](https://github.com/opensite-ai/page-speed-hooks)
94
- - Visit [opensite.ai](https://opensite.ai) for more tools and information.
96
+ - Visit [opensite.ai](https://opensite.ai) for more tools and information.
package/dist/inputs.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React5 = require('react');
3
+ var React6 = require('react');
4
4
 
5
5
  function _interopNamespace(e) {
6
6
  if (e && e.__esModule) return e;
@@ -20,7 +20,7 @@ function _interopNamespace(e) {
20
20
  return Object.freeze(n);
21
21
  }
22
22
 
23
- var React5__namespace = /*#__PURE__*/_interopNamespace(React5);
23
+ var React6__namespace = /*#__PURE__*/_interopNamespace(React6);
24
24
 
25
25
  function TextInput({
26
26
  name,
@@ -44,7 +44,7 @@ function TextInput({
44
44
  const baseClassName = "text-input";
45
45
  const errorClassName = error ? "text-input--error" : "";
46
46
  const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
47
- return /* @__PURE__ */ React5__namespace.createElement(
47
+ return /* @__PURE__ */ React6__namespace.createElement(
48
48
  "input",
49
49
  {
50
50
  type,
@@ -90,7 +90,7 @@ function TextArea({
90
90
  const baseClassName = "textarea";
91
91
  const errorClassName = error ? "textarea--error" : "";
92
92
  const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
93
- return /* @__PURE__ */ React5__namespace.createElement(
93
+ return /* @__PURE__ */ React6__namespace.createElement(
94
94
  "textarea",
95
95
  {
96
96
  name,
@@ -127,8 +127,8 @@ function Checkbox({
127
127
  label,
128
128
  ...props
129
129
  }) {
130
- const inputRef = React5__namespace.useRef(null);
131
- React5__namespace.useEffect(() => {
130
+ const inputRef = React6__namespace.useRef(null);
131
+ React6__namespace.useEffect(() => {
132
132
  if (inputRef.current) {
133
133
  inputRef.current.indeterminate = indeterminate;
134
134
  }
@@ -142,7 +142,7 @@ function Checkbox({
142
142
  const baseClassName = "checkbox";
143
143
  const errorClassName = error ? "checkbox--error" : "";
144
144
  const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
145
- const checkbox = /* @__PURE__ */ React5__namespace.createElement(
145
+ const checkbox = /* @__PURE__ */ React6__namespace.createElement(
146
146
  "input",
147
147
  {
148
148
  ref: inputRef,
@@ -161,11 +161,134 @@ function Checkbox({
161
161
  }
162
162
  );
163
163
  if (label) {
164
- return /* @__PURE__ */ React5__namespace.createElement("label", { className: "checkbox-label" }, checkbox, /* @__PURE__ */ React5__namespace.createElement("span", { className: "checkbox-label-text" }, label));
164
+ return /* @__PURE__ */ React6__namespace.createElement("label", { className: "checkbox-label" }, checkbox, /* @__PURE__ */ React6__namespace.createElement("span", { className: "checkbox-label-text" }, label));
165
165
  }
166
166
  return checkbox;
167
167
  }
168
168
  Checkbox.displayName = "Checkbox";
169
+ function CheckboxGroup({
170
+ name,
171
+ value = [],
172
+ onChange,
173
+ onBlur,
174
+ disabled = false,
175
+ required = false,
176
+ error = false,
177
+ className = "",
178
+ layout = "stacked",
179
+ label,
180
+ description,
181
+ options,
182
+ showSelectAll = false,
183
+ selectAllLabel = "Select all",
184
+ minSelections,
185
+ maxSelections,
186
+ renderOption,
187
+ gridColumns = 2,
188
+ ...props
189
+ }) {
190
+ const enabledOptions = options.filter((opt) => !opt.disabled);
191
+ const enabledValues = enabledOptions.map((opt) => opt.value);
192
+ const selectedEnabledCount = value.filter(
193
+ (v) => enabledValues.includes(v)
194
+ ).length;
195
+ const allSelected = selectedEnabledCount === enabledOptions.length;
196
+ const someSelected = selectedEnabledCount > 0 && !allSelected;
197
+ const handleChange = (optionValue, checked) => {
198
+ const newValues = checked ? [...value, optionValue] : value.filter((v) => v !== optionValue);
199
+ if (maxSelections && checked && newValues.length > maxSelections) {
200
+ return;
201
+ }
202
+ onChange(newValues);
203
+ };
204
+ const handleSelectAll = (checked) => {
205
+ if (checked) {
206
+ const allValues = enabledOptions.map((opt) => opt.value);
207
+ onChange(allValues);
208
+ } else {
209
+ onChange([]);
210
+ }
211
+ };
212
+ const handleBlur = () => {
213
+ onBlur?.();
214
+ };
215
+ const baseClassName = "checkbox-group";
216
+ const errorClassName = error ? "checkbox-group--error" : "";
217
+ const layoutClassName = `checkbox-group--${layout}`;
218
+ const combinedClassName = `${baseClassName} ${errorClassName} ${layoutClassName} ${className}`.trim();
219
+ const maxReached = Boolean(maxSelections && value.length >= maxSelections);
220
+ return /* @__PURE__ */ React6__namespace.createElement(
221
+ "div",
222
+ {
223
+ className: combinedClassName,
224
+ role: "group",
225
+ "aria-invalid": error || props["aria-invalid"],
226
+ "aria-describedby": props["aria-describedby"],
227
+ "aria-required": required || props["aria-required"],
228
+ "aria-label": typeof label === "string" ? label : props["aria-label"],
229
+ style: layout === "grid" ? {
230
+ gridTemplateColumns: `repeat(${gridColumns}, 1fr)`
231
+ } : void 0
232
+ },
233
+ label && /* @__PURE__ */ React6__namespace.createElement("div", { className: "checkbox-group-label" }, label),
234
+ description && /* @__PURE__ */ React6__namespace.createElement("div", { className: "checkbox-group-description" }, description),
235
+ /* @__PURE__ */ React6__namespace.createElement("div", { className: "checkbox-options" }, showSelectAll && enabledOptions.length > 0 && /* @__PURE__ */ React6__namespace.createElement("label", { className: "checkbox-option checkbox-option--select-all" }, /* @__PURE__ */ React6__namespace.createElement(
236
+ "input",
237
+ {
238
+ type: "checkbox",
239
+ checked: allSelected,
240
+ ref: (input) => {
241
+ if (input) {
242
+ input.indeterminate = someSelected;
243
+ }
244
+ },
245
+ onChange: (e) => handleSelectAll(e.target.checked),
246
+ onBlur: handleBlur,
247
+ disabled,
248
+ className: "checkbox-input",
249
+ "aria-label": selectAllLabel
250
+ }
251
+ ), /* @__PURE__ */ React6__namespace.createElement("div", { className: "checkbox-content" }, /* @__PURE__ */ React6__namespace.createElement("span", { className: "checkbox-label" }, selectAllLabel))), options.map((option) => {
252
+ const isChecked = value.includes(option.value);
253
+ const isDisabled = disabled || option.disabled || maxReached && !isChecked;
254
+ const checkboxId = `${name}-${option.value}`;
255
+ return /* @__PURE__ */ React6__namespace.createElement(
256
+ "label",
257
+ {
258
+ key: option.value,
259
+ className: `checkbox-option ${isDisabled ? "checkbox-option--disabled" : ""}`,
260
+ htmlFor: checkboxId
261
+ },
262
+ /* @__PURE__ */ React6__namespace.createElement(
263
+ "input",
264
+ {
265
+ type: "checkbox",
266
+ id: checkboxId,
267
+ name,
268
+ value: option.value,
269
+ checked: isChecked,
270
+ onChange: (e) => handleChange(option.value, e.target.checked),
271
+ onBlur: handleBlur,
272
+ disabled: isDisabled,
273
+ required: required && minSelections ? value.length < minSelections : false,
274
+ className: "checkbox-input",
275
+ "aria-describedby": option.description ? `${checkboxId}-description` : props["aria-describedby"]
276
+ }
277
+ ),
278
+ /* @__PURE__ */ React6__namespace.createElement("div", { className: "checkbox-content" }, renderOption ? renderOption(option) : /* @__PURE__ */ React6__namespace.createElement(React6__namespace.Fragment, null, /* @__PURE__ */ React6__namespace.createElement("span", { className: "checkbox-label" }, option.label), option.description && /* @__PURE__ */ React6__namespace.createElement(
279
+ "span",
280
+ {
281
+ className: "checkbox-description",
282
+ id: `${checkboxId}-description`
283
+ },
284
+ option.description
285
+ )))
286
+ );
287
+ })),
288
+ (minSelections || maxSelections) && /* @__PURE__ */ React6__namespace.createElement("div", { className: "checkbox-group-feedback", "aria-live": "polite" }, minSelections && value.length < minSelections && /* @__PURE__ */ React6__namespace.createElement("span", { className: "checkbox-group-feedback-min" }, "Select at least ", minSelections, " option", minSelections !== 1 ? "s" : ""), maxSelections && /* @__PURE__ */ React6__namespace.createElement("span", { className: "checkbox-group-feedback-max" }, value.length, "/", maxSelections, " selected"))
289
+ );
290
+ }
291
+ CheckboxGroup.displayName = "CheckboxGroup";
169
292
  function Radio({
170
293
  name,
171
294
  value,
@@ -215,7 +338,7 @@ function Radio({
215
338
  const errorClassName = error ? "radio-group--error" : "";
216
339
  const layoutClassName = `radio-group--${layout}`;
217
340
  const combinedClassName = `${baseClassName} ${errorClassName} ${layoutClassName} ${className}`.trim();
218
- return /* @__PURE__ */ React5__namespace.createElement(
341
+ return /* @__PURE__ */ React6__namespace.createElement(
219
342
  "div",
220
343
  {
221
344
  className: combinedClassName,
@@ -225,19 +348,19 @@ function Radio({
225
348
  "aria-required": required || props["aria-required"],
226
349
  "aria-label": typeof label === "string" ? label : props["aria-label"]
227
350
  },
228
- label && /* @__PURE__ */ React5__namespace.createElement("div", { className: "radio-group-label" }, label),
229
- /* @__PURE__ */ React5__namespace.createElement("div", { className: "radio-options" }, options.map((option, index) => {
351
+ label && /* @__PURE__ */ React6__namespace.createElement("div", { className: "radio-group-label" }, label),
352
+ /* @__PURE__ */ React6__namespace.createElement("div", { className: "radio-options" }, options.map((option, index) => {
230
353
  const isChecked = value === option.value;
231
354
  const isDisabled = disabled || option.disabled;
232
355
  const radioId = `${name}-${option.value}`;
233
- return /* @__PURE__ */ React5__namespace.createElement(
356
+ return /* @__PURE__ */ React6__namespace.createElement(
234
357
  "label",
235
358
  {
236
359
  key: option.value,
237
360
  className: `radio-option ${isDisabled ? "radio-option--disabled" : ""}`,
238
361
  htmlFor: radioId
239
362
  },
240
- /* @__PURE__ */ React5__namespace.createElement(
363
+ /* @__PURE__ */ React6__namespace.createElement(
241
364
  "input",
242
365
  {
243
366
  type: "radio",
@@ -254,7 +377,7 @@ function Radio({
254
377
  "aria-describedby": option.description ? `${radioId}-description` : props["aria-describedby"]
255
378
  }
256
379
  ),
257
- /* @__PURE__ */ React5__namespace.createElement("div", { className: "radio-content" }, /* @__PURE__ */ React5__namespace.createElement("span", { className: "radio-label" }, option.label), option.description && /* @__PURE__ */ React5__namespace.createElement(
380
+ /* @__PURE__ */ React6__namespace.createElement("div", { className: "radio-content" }, /* @__PURE__ */ React6__namespace.createElement("span", { className: "radio-label" }, option.label), option.description && /* @__PURE__ */ React6__namespace.createElement(
258
381
  "span",
259
382
  {
260
383
  className: "radio-description",
@@ -286,19 +409,19 @@ function Select({
286
409
  renderOption,
287
410
  ...props
288
411
  }) {
289
- const [isOpen, setIsOpen] = React5__namespace.useState(false);
290
- const [searchQuery, setSearchQuery] = React5__namespace.useState("");
291
- const [focusedIndex, setFocusedIndex] = React5__namespace.useState(-1);
292
- const selectRef = React5__namespace.useRef(null);
293
- const searchInputRef = React5__namespace.useRef(null);
412
+ const [isOpen, setIsOpen] = React6__namespace.useState(false);
413
+ const [searchQuery, setSearchQuery] = React6__namespace.useState("");
414
+ const [focusedIndex, setFocusedIndex] = React6__namespace.useState(-1);
415
+ const selectRef = React6__namespace.useRef(null);
416
+ const searchInputRef = React6__namespace.useRef(null);
294
417
  const dropdownId = `${name}-dropdown`;
295
- const allOptions = React5__namespace.useMemo(() => {
418
+ const allOptions = React6__namespace.useMemo(() => {
296
419
  if (optionGroups.length > 0) {
297
420
  return optionGroups.flatMap((group) => group.options);
298
421
  }
299
422
  return options;
300
423
  }, [options, optionGroups]);
301
- const filteredOptions = React5__namespace.useMemo(() => {
424
+ const filteredOptions = React6__namespace.useMemo(() => {
302
425
  if (!searchQuery.trim()) {
303
426
  return allOptions;
304
427
  }
@@ -308,7 +431,7 @@ function Select({
308
431
  return label.toLowerCase().includes(query);
309
432
  });
310
433
  }, [allOptions, searchQuery]);
311
- const selectedOption = React5__namespace.useMemo(() => {
434
+ const selectedOption = React6__namespace.useMemo(() => {
312
435
  return allOptions.find((opt) => opt.value === value);
313
436
  }, [allOptions, value]);
314
437
  const handleSelect = (optionValue) => {
@@ -412,7 +535,7 @@ function Select({
412
535
  const handleBlur = () => {
413
536
  onBlur?.();
414
537
  };
415
- React5__namespace.useEffect(() => {
538
+ React6__namespace.useEffect(() => {
416
539
  const handleClickOutside = (event) => {
417
540
  if (selectRef.current && !selectRef.current.contains(event.target)) {
418
541
  setIsOpen(false);
@@ -433,7 +556,7 @@ function Select({
433
556
  const disabledClassName = disabled ? "select--disabled" : "";
434
557
  const openClassName = isOpen ? "select--open" : "";
435
558
  const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${openClassName} ${className}`.trim();
436
- return /* @__PURE__ */ React5__namespace.createElement(
559
+ return /* @__PURE__ */ React6__namespace.createElement(
437
560
  "div",
438
561
  {
439
562
  ref: selectRef,
@@ -441,7 +564,7 @@ function Select({
441
564
  onKeyDown: handleKeyDown,
442
565
  onBlur: handleBlur
443
566
  },
444
- /* @__PURE__ */ React5__namespace.createElement(
567
+ /* @__PURE__ */ React6__namespace.createElement(
445
568
  "select",
446
569
  {
447
570
  name,
@@ -454,10 +577,10 @@ function Select({
454
577
  tabIndex: -1,
455
578
  style: { display: "none" }
456
579
  },
457
- /* @__PURE__ */ React5__namespace.createElement("option", { value: "" }, "Select..."),
458
- allOptions.map((option) => /* @__PURE__ */ React5__namespace.createElement("option", { key: option.value, value: option.value }, typeof option.label === "string" ? option.label : option.value))
580
+ /* @__PURE__ */ React6__namespace.createElement("option", { value: "" }, "Select..."),
581
+ allOptions.map((option) => /* @__PURE__ */ React6__namespace.createElement("option", { key: option.value, value: option.value }, typeof option.label === "string" ? option.label : option.value))
459
582
  ),
460
- /* @__PURE__ */ React5__namespace.createElement(
583
+ /* @__PURE__ */ React6__namespace.createElement(
461
584
  "div",
462
585
  {
463
586
  className: "select-trigger",
@@ -471,8 +594,8 @@ function Select({
471
594
  "aria-disabled": disabled,
472
595
  tabIndex: disabled ? -1 : 0
473
596
  },
474
- /* @__PURE__ */ React5__namespace.createElement("span", { className: "select-value" }, selectedOption ? renderOption ? renderOption(selectedOption) : selectedOption.label : /* @__PURE__ */ React5__namespace.createElement("span", { className: "select-placeholder" }, placeholder)),
475
- /* @__PURE__ */ React5__namespace.createElement("div", { className: "select-icons" }, loading && /* @__PURE__ */ React5__namespace.createElement("span", { className: "select-loading" }, "\u23F3"), clearable && value && !disabled && !loading && /* @__PURE__ */ React5__namespace.createElement(
597
+ /* @__PURE__ */ React6__namespace.createElement("span", { className: "select-value" }, selectedOption ? renderOption ? renderOption(selectedOption) : selectedOption.label : /* @__PURE__ */ React6__namespace.createElement("span", { className: "select-placeholder" }, placeholder)),
598
+ /* @__PURE__ */ React6__namespace.createElement("div", { className: "select-icons" }, loading && /* @__PURE__ */ React6__namespace.createElement("span", { className: "select-loading" }, "\u23F3"), clearable && value && !disabled && !loading && /* @__PURE__ */ React6__namespace.createElement(
476
599
  "button",
477
600
  {
478
601
  type: "button",
@@ -482,9 +605,9 @@ function Select({
482
605
  tabIndex: -1
483
606
  },
484
607
  "\u2715"
485
- ), /* @__PURE__ */ React5__namespace.createElement("span", { className: "select-arrow", "aria-hidden": "true" }, isOpen ? "\u25B2" : "\u25BC"))
608
+ ), /* @__PURE__ */ React6__namespace.createElement("span", { className: "select-arrow", "aria-hidden": "true" }, isOpen ? "\u25B2" : "\u25BC"))
486
609
  ),
487
- isOpen && /* @__PURE__ */ React5__namespace.createElement("div", { id: dropdownId, className: "select-dropdown", role: "listbox" }, searchable && /* @__PURE__ */ React5__namespace.createElement("div", { className: "select-search" }, /* @__PURE__ */ React5__namespace.createElement(
610
+ isOpen && /* @__PURE__ */ React6__namespace.createElement("div", { id: dropdownId, className: "select-dropdown", role: "listbox" }, searchable && /* @__PURE__ */ React6__namespace.createElement("div", { className: "select-search" }, /* @__PURE__ */ React6__namespace.createElement(
488
611
  "input",
489
612
  {
490
613
  ref: searchInputRef,
@@ -496,19 +619,19 @@ function Select({
496
619
  onClick: (e) => e.stopPropagation(),
497
620
  "aria-label": "Search options"
498
621
  }
499
- )), /* @__PURE__ */ React5__namespace.createElement("div", { className: "select-options" }, filteredOptions.length === 0 ? /* @__PURE__ */ React5__namespace.createElement("div", { className: "select-no-options" }, "No options found") : optionGroups.length > 0 ? (
622
+ )), /* @__PURE__ */ React6__namespace.createElement("div", { className: "select-options" }, filteredOptions.length === 0 ? /* @__PURE__ */ React6__namespace.createElement("div", { className: "select-no-options" }, "No options found") : optionGroups.length > 0 ? (
500
623
  // Render grouped options
501
624
  optionGroups.map((group, groupIndex) => {
502
625
  const groupOptions = group.options.filter(
503
626
  (opt) => filteredOptions.includes(opt)
504
627
  );
505
628
  if (groupOptions.length === 0) return null;
506
- return /* @__PURE__ */ React5__namespace.createElement("div", { key: groupIndex, className: "select-optgroup" }, /* @__PURE__ */ React5__namespace.createElement("div", { className: "select-optgroup-label" }, group.label), groupOptions.map((option) => {
629
+ return /* @__PURE__ */ React6__namespace.createElement("div", { key: groupIndex, className: "select-optgroup" }, /* @__PURE__ */ React6__namespace.createElement("div", { className: "select-optgroup-label" }, group.label), groupOptions.map((option) => {
507
630
  const globalIndex = filteredOptions.indexOf(option);
508
631
  const isSelected = value === option.value;
509
632
  const isFocused = globalIndex === focusedIndex;
510
633
  const isDisabled = option.disabled;
511
- return /* @__PURE__ */ React5__namespace.createElement(
634
+ return /* @__PURE__ */ React6__namespace.createElement(
512
635
  "div",
513
636
  {
514
637
  key: option.value,
@@ -528,7 +651,7 @@ function Select({
528
651
  const isSelected = value === option.value;
529
652
  const isFocused = index === focusedIndex;
530
653
  const isDisabled = option.disabled;
531
- return /* @__PURE__ */ React5__namespace.createElement(
654
+ return /* @__PURE__ */ React6__namespace.createElement(
532
655
  "div",
533
656
  {
534
657
  key: option.value,
@@ -547,6 +670,7 @@ function Select({
547
670
  Select.displayName = "Select";
548
671
 
549
672
  exports.Checkbox = Checkbox;
673
+ exports.CheckboxGroup = CheckboxGroup;
550
674
  exports.Radio = Radio;
551
675
  exports.Select = Select;
552
676
  exports.TextArea = TextArea;