@public-ui/mcp 3.1.2-rc.1 → 3.1.2-rc.2
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/package.json +12 -11
- package/shared/sample-index.json +13 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-ui/mcp",
|
|
3
|
-
"version": "3.1.2-rc.
|
|
3
|
+
"version": "3.1.2-rc.2",
|
|
4
4
|
"license": "EUPL-1.2",
|
|
5
5
|
"homepage": "https://public-ui.github.io",
|
|
6
6
|
"repository": {
|
|
@@ -42,23 +42,24 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/sdk": "1.
|
|
45
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
46
46
|
"express": "5.2.1",
|
|
47
47
|
"fuse.js": "7.1.0",
|
|
48
48
|
"zod": "3.25.76",
|
|
49
|
-
"@public-ui/components": "3.1.2-rc.
|
|
49
|
+
"@public-ui/components": "3.1.2-rc.2"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/eslintrc": "3.3.3",
|
|
53
|
+
"@eslint/js": "9.39.2",
|
|
52
54
|
"@modelcontextprotocol/inspector": "0.19.0",
|
|
53
55
|
"@types/express": "5.0.6",
|
|
54
|
-
"@types/node": "24.10.
|
|
55
|
-
"@typescript-eslint/eslint-plugin": "
|
|
56
|
-
"@typescript-eslint/parser": "
|
|
57
|
-
"eslint": "
|
|
58
|
-
"eslint-config-prettier": "
|
|
59
|
-
"eslint-plugin-html": "8.1.4",
|
|
56
|
+
"@types/node": "24.10.10",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "8.54.0",
|
|
58
|
+
"@typescript-eslint/parser": "8.54.0",
|
|
59
|
+
"eslint": "9.39.2",
|
|
60
|
+
"eslint-config-prettier": "10.1.8",
|
|
60
61
|
"eslint-plugin-json": "3.1.0",
|
|
61
|
-
"knip": "5.
|
|
62
|
+
"knip": "5.83.0",
|
|
62
63
|
"nodemon": "3.1.11",
|
|
63
64
|
"prettier": "3.8.1",
|
|
64
65
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"dev": "nodemon",
|
|
81
82
|
"format": "prettier -c src test public",
|
|
82
83
|
"lint": "pnpm lint:eslint && pnpm lint:tsc",
|
|
83
|
-
"lint:eslint": "eslint src test
|
|
84
|
+
"lint:eslint": "eslint src test",
|
|
84
85
|
"lint:tsc": "tsc --noemit",
|
|
85
86
|
"preinspect": "pnpm generate-index",
|
|
86
87
|
"inspect": "mcp-inspector tsx src/cli.ts",
|
package/shared/sample-index.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"metadata": {
|
|
3
|
-
"generatedAt": "2026-02-
|
|
3
|
+
"generatedAt": "2026-02-05T18:37:26.606Z",
|
|
4
4
|
"buildMode": "ci",
|
|
5
5
|
"counts": {
|
|
6
|
-
"total":
|
|
6
|
+
"total": 218,
|
|
7
7
|
"totalDocs": 21,
|
|
8
8
|
"totalSpecs": 50,
|
|
9
9
|
"totalSamples": 132,
|
|
10
|
-
"totalScenarios":
|
|
10
|
+
"totalScenarios": 15
|
|
11
11
|
},
|
|
12
12
|
"repo": {
|
|
13
|
-
"commit": "
|
|
13
|
+
"commit": "790a08143b1ed7180d041f043af8bb8217d0eba3",
|
|
14
14
|
"branch": "release/3",
|
|
15
15
|
"repoUrl": "https://github.com/public-ui/kolibri"
|
|
16
16
|
}
|
|
@@ -709,7 +709,7 @@
|
|
|
709
709
|
"group": "input-text",
|
|
710
710
|
"name": "text-formatter",
|
|
711
711
|
"path": "packages/samples/react/src/components/input-text/text-formatter.tsx",
|
|
712
|
-
"code": "import { KolForm, KolInputText } from '@public-ui/react-v19';\nimport React, { useMemo, useRef, type BaseSyntheticEvent } from 'react';\nimport { Controller, useForm } from 'react-hook-form';\n\nimport { SampleDescription } from '../SampleDescription';\n\ntype InputTextElementSelection = {\n\tsetSelectionStart?: (position: number) => Promise<void>;\n\tselectionStart?: () => Promise<number | null>;\n};\n\ntype KolInputTextEvents = {\n\tonBlur?: (event: Event) => void;\n\tonChange?: (event: Event, value: unknown) => void;\n\tonFocus?: (event: Event) => void;\n\tonInput?: (event: Event, value: unknown) => void;\n};\ntype KolInputTextProps = Omit<React.ComponentProps<typeof KolInputText>, '_on' | '_value'> & {\n\t_on?: KolInputTextEvents;\n\t_value?: string;\n};\n\nconst NON_ALPHANUM = /[^a-zA-Z0-9]/g;\nconst EVERY_FOUR_CHARS = /(.{4})(?!$)/g;\n\nclass IbanFormatter {\n\tprivate electronicFormat(iban: string): string {\n\t\treturn iban.replace(NON_ALPHANUM, '').toUpperCase();\n\t}\n\n\tprivate printFormat(iban: string, separator?: string) {\n\t\treturn this.electronicFormat(iban).replace(EVERY_FOUR_CHARS, '$1' + (separator || ' '));\n\t}\n\n\tpublic parse(value: string): string {\n\t\treturn this.electronicFormat(value);\n\t}\n\n\tpublic format(value: string, ref?: HTMLKolInputTextElement | null, selectionStart?: number | null): string {\n\t\tconst setSelectionStart = (ref as InputTextElementSelection | null)?.setSelectionStart;\n\t\tif (selectionStart && setSelectionStart) {\n\t\t\tif (selectionStart % 5 === 0) selectionStart++;\n\t\t\tvoid setSelectionStart(selectionStart);\n\t\t}\n\t\treturn this.printFormat(value);\n\t}\n}\n\ntype IbanExampleFormValues = {\n\tiban: string;\n};\n\nfunction FormattedKolInputText({\n\tformatter,\n\tselectionStartRef,\n\t_on,\n\t_value,\n\t...props\n}: KolInputTextProps & {\n\tformatter: IbanFormatter;\n\tselectionStartRef: React.MutableRefObject<number | null>;\n}) {\n\tconst inputRef = useRef<HTMLKolInputTextElement | null>(null);\n\tconst normalizedOn = _on && typeof _on === 'object' ? (_on as KolInputTextEvents) : undefined;\n\tconst sanitizedSelectionRef = selectionStartRef as React.MutableRefObject<number | null>;\n\n\tconst element = inputRef.current;\n\tconst selectionStart = sanitizedSelectionRef.current;\n\tconst sanitizedFormatter: IbanFormatter = formatter;\n\n\treturn (\n\t\t<KolInputText\n\t\t\t{...props}\n\t\t\tref={inputRef}\n\t\t\t_value={sanitizedFormatter.format(_value ?? '', element, selectionStart)}\n\t\t\t_on={{\n\t\t\t\t...normalizedOn,\n\t\t\t\tonInput: (event: Event, value: unknown) => {\n\t\t\t\t\tconst selectionStartGetter = (inputRef.current as InputTextElementSelection | null)?.selectionStart;\n\t\t\t\t\tselectionStartGetter?.().then((start) => {\n\t\t\t\t\t\tsanitizedSelectionRef.current = start ?? null;\n\t\t\t\t\t});\n\t\t\t\t\tconst parsedValue = sanitizedFormatter.parse(typeof value === 'string' ? value : '');\n\t\t\t\t\tnormalizedOn?.onInput?.(event, parsedValue);\n\t\t\t\t},\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction KolFormattedIbanController(props: any) {\n\tconst { name, control, rules, defaultValue, shouldUnregister, disabled, formatter, selectionStartRef, ...componentProps } = props;\n\treturn (\n\t\t<Controller\n\t\t\tname={name}\n\t\t\tcontrol={control}\n\t\t\trules={rules}\n\t\t\tdefaultValue={defaultValue}\n\t\t\tshouldUnregister={shouldUnregister}\n\t\t\tdisabled={disabled}\n\t\t\trender={({ field, fieldState }) => {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\t\t\tconst userHandlers = componentProps._on as KolInputTextEvents | undefined;\n\t\t\t\treturn (\n\t\t\t\t\t<FormattedKolInputText\n\t\t\t\t\t\t{...(componentProps as any)}\n\t\t\t\t\t\tformatter={formatter}\n\t\t\t\t\t\tselectionStartRef={selectionStartRef}\n\t\t\t\t\t\t_value={field.value}\n\t\t\t\t\t\t_touched={fieldState.isTouched}\n\t\t\t\t\t\t_msg={\n\t\t\t\t\t\t\tfieldState.error\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t_type: 'error' as const,\n\t\t\t\t\t\t\t\t\t\t_description: fieldState.error.message || String(fieldState.error),\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_on={{\n\t\t\t\t\t\t\tonInput: (event: Event, value: unknown) => {\n\t\t\t\t\t\t\t\tfield.onChange(value);\n\t\t\t\t\t\t\t\tuserHandlers?.onInput?.(event, value);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonBlur: () => field.onBlur(),\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t);\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nexport function InputTextFormatterDemo() {\n\tconst formatter = useMemo(() => new IbanFormatter(), []);\n\n\tconst textInput1SelectionStart = useRef<number | null>(null);\n\n\tconst initialIbanExampleValues: IbanExampleFormValues = {\n\t\tiban: 'DE89370400440532013000',\n\t};\n\n\tconst ibanForm = useForm<IbanExampleFormValues>({\n\t\tdefaultValues: initialIbanExampleValues,\n\t\tmode: 'onTouched',\n\t});\n\n\tconst ibanValues = ibanForm.watch();\n\n\tconst handleIbanSubmit = (event: Event) => {\n\t\tvoid ibanForm.handleSubmit(async () => {})(event as unknown as BaseSyntheticEvent);\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<SampleDescription>\n\t\t\t\t<p>\n\t\t\t\t\tThis example demonstrates formatting a data value in an input field (example IBAN). The data value is formatted to the input field (print format) and\n\t\t\t\t\tvice versa the formatting is removed again (machine format)\n\t\t\t\t</p>\n\t\t\t</SampleDescription>\n\t\t\t<section className=\"w-full\">\n\t\t\t\t<div>\n\t\t\t\t\t<KolForm _on={{ onSubmit: handleIbanSubmit }}>\n\t\t\t\t\t\t<KolFormattedIbanController\n\t\t\t\t\t\t\tcontrol={ibanForm.control as any}\n\t\t\t\t\t\t\tname=\"iban\"\n\t\t\t\t\t\t\tid=\"field-iban\"\n\t\t\t\t\t\t\tformatter={formatter}\n\t\t\t\t\t\t\tselectionStartRef={textInput1SelectionStart}\n\t\t\t\t\t\t\trules={{ required: 'Please enter an IBAN.' }}\n\t\t\t\t\t\t\t_label=\"IBAN\"\n\t\t\t\t\t\t\t_required\n\t\t\t\t\t\t/>\n\t\t\t\t\t</KolForm>\n\t\t\t\t\t<pre className=\"text-base mt-2\">{JSON.stringify(ibanValues, null, 2)}</pre>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</>\n\t);\n}\n",
|
|
712
|
+
"code": "import { KolForm, KolHeading, KolInputText } from '@public-ui/react-v19';\nimport * as React from 'react';\nimport { Controller, useForm } from 'react-hook-form';\nimport { SampleDescription } from '../SampleDescription';\n\n/**\n * IbanFormatter handles formatting and parsing of IBAN values.\n * It groups characters in blocks of 4, separated by spaces.\n * Example: \"DE89370400440532013000\" → \"DE89 3704 0044 0532 0130 00\"\n */\nclass IbanFormatter {\n\tprivate readonly SEPARATOR = ' ';\n\tprivate readonly CHARS_PER_GROUP = 4;\n\tprivate readonly KEEP_ALPHANUM = /[^a-zA-Z0-9]/g;\n\n\t/**\n\t * Removes all non-alphanumeric characters and converts to uppercase.\n\t * This is the raw value stored in the form model.\n\t */\n\tparse(value: string): string {\n\t\treturn value.replace(this.KEEP_ALPHANUM, '').toUpperCase();\n\t}\n\n\t/**\n\t * Formats the value into groups of 4 characters separated by spaces.\n\t * This is the display value shown in the input field.\n\t */\n\tformat(value: string): string {\n\t\tconst clean = this.parse(value);\n\t\tconst regex = new RegExp(`(.{${this.CHARS_PER_GROUP}})(?!$)`, 'g');\n\t\treturn clean.replace(regex, `$1${this.SEPARATOR}`);\n\t}\n\n\t/**\n\t * Calculates the correct cursor position after formatting changes.\n\t * This ensures the cursor stays at the expected position when separators are added/removed.\n\t */\n\tadjustCursorPosition(oldValue: string, newValue: string, oldCursorPos: number): number {\n\t\tconst oldText = this.format(oldValue);\n\t\tconst newText = this.format(newValue);\n\n\t\tif (oldCursorPos >= oldText.length) return newText.length;\n\n\t\t// Count significant characters (excluding separators) up to cursor position\n\t\tlet significantChars = 0;\n\t\tfor (let i = 0; i < oldCursorPos && i < oldText.length; i++) {\n\t\t\tif (oldText[i] !== this.SEPARATOR) significantChars++;\n\t\t}\n\n\t\t// Find the corresponding position in the new formatted text\n\t\tlet count = 0;\n\t\tfor (let i = 0; i < newText.length; i++) {\n\t\t\tif (newText[i] !== this.SEPARATOR && count++ === significantChars) return i;\n\t\t}\n\t\treturn newText.length;\n\t}\n}\n\n/**\n * CurrencyFormatter handles formatting and parsing of currency values.\n * It uses the browser's locale for formatting and intelligently detects\n * decimal vs. thousand separators when parsing.\n * Example: 1000000 → \"1.000.000,00 €\" (German locale)\n */\nclass CurrencyFormatter {\n\tprivate readonly LOCALE = navigator.language;\n\tprivate readonly CURRENCY_SYMBOL = ' €';\n\n\t/**\n\t * Parses a formatted currency string back to a raw number.\n\t * Intelligently detects which character is the decimal separator:\n\t * - German format: \"1.000.000,00\" → 1000000 (dot is thousand, comma is decimal)\n\t * - English format: \"1,000,000.00\" → 1000000 (comma is thousand, dot is decimal)\n\t */\n\tparse(value: string): number {\n\t\t// Remove currency symbols and keep only digits, dots, and commas\n\t\tconst sanitized = value.replace(/[^\\d.,]/g, '');\n\n\t\t// Determine which character is the decimal separator\n\t\t// The rightmost occurrence is typically the decimal separator\n\t\tconst lastCommaIndex = sanitized.lastIndexOf(',');\n\t\tconst lastDotIndex = sanitized.lastIndexOf('.');\n\n\t\tlet normalizedValue: string;\n\n\t\tif (lastCommaIndex > lastDotIndex) {\n\t\t\t// Comma is decimal separator (e.g., German: 1.000.000,00)\n\t\t\t// Remove all dots (thousand separators) and replace comma with dot for parseFloat\n\t\t\tnormalizedValue = sanitized.replace(/\\./g, '').replace(',', '.');\n\t\t} else if (lastDotIndex > lastCommaIndex) {\n\t\t\t// Dot is decimal separator (e.g., English: 1,000,000.00)\n\t\t\t// Remove all commas (thousand separators)\n\t\t\tnormalizedValue = sanitized.replace(/,/g, '');\n\t\t} else {\n\t\t\t// No decimal separator present, remove all dots and commas\n\t\t\tnormalizedValue = sanitized.replace(/[.,]/g, '');\n\t\t}\n\n\t\treturn parseFloat(normalizedValue) || 0;\n\t}\n\n\t/**\n\t * Formats a number as currency according to the browser's locale.\n\t * Always shows 2 decimal places and appends the currency symbol.\n\t */\n\tformat(value: number | string): string {\n\t\tconst number = typeof value === 'string' ? this.parse(value) : value;\n\t\tconst formatted = new Intl.NumberFormat(this.LOCALE, {\n\t\t\tminimumFractionDigits: 2,\n\t\t\tmaximumFractionDigits: 2,\n\t\t}).format(number);\n\t\treturn formatted + this.CURRENCY_SYMBOL;\n\t}\n\n\t/**\n\t * Calculates the correct cursor position after formatting changes.\n\t * This ensures the cursor stays aligned with digits when thousand separators are added/removed.\n\t */\n\tadjustCursorPosition(oldValue: string, newValue: string, oldCursorPos: number): number {\n\t\tconst oldText = this.format(oldValue);\n\t\tconst newText = this.format(newValue);\n\n\t\tif (oldCursorPos >= oldText.length) return newText.length;\n\n\t\t// Count digits up to cursor position (ignoring separators and currency symbols)\n\t\tlet digitCount = 0;\n\t\tfor (let i = 0; i < oldCursorPos && i < oldText.length; i++) {\n\t\t\tif (/\\d/.test(oldText[i])) digitCount++;\n\t\t}\n\n\t\t// Find the corresponding position in the new formatted text\n\t\tlet count = 0;\n\t\tfor (let i = 0; i < newText.length; i++) {\n\t\t\tif (/\\d/.test(newText[i]) && count++ === digitCount) return i;\n\t\t}\n\t\treturn newText.length;\n\t}\n}\n\ntype IbanExampleFormValues = {\n\tiban: string;\n};\n\ntype CurrencyExampleFormValues = {\n\tcurrency: number;\n};\n\n/**\n * Demo component showcasing two different text formatting strategies for input fields.\n *\n * Strategy 1 - Live Formatting (IBAN):\n * - Formats text while typing\n * - Maintains intelligent cursor positioning\n * - Best for: Fixed-format values where users expect immediate visual feedback\n *\n * Strategy 2 - On-Blur Formatting (Currency):\n * - Formats only when field loses focus (onBlur event)\n * - Allows free typing without formatting interruptions\n * - Best for: Numeric values where users might type in various formats\n */\nexport function InputTextFormatterDemo() {\n\tconst ibanFormatter = new IbanFormatter();\n\tconst currencyFormatter = new CurrencyFormatter();\n\n\tconst ibanForm = useForm<IbanExampleFormValues>({\n\t\tdefaultValues: { iban: 'DE89370400440532013000' },\n\t});\n\tconst currencyForm = useForm<CurrencyExampleFormValues>({\n\t\tdefaultValues: { currency: 1000000 },\n\t});\n\n\t/**\n\t * Handles input events for the IBAN field with live formatting.\n\t * Updates the model value and adjusts cursor position to maintain UX during formatting.\n\t */\n\tconst handleIbanInput = (event: Event) => {\n\t\tconst input = event.target as HTMLInputElement;\n\t\tconst newValue = ibanFormatter.parse(input.value);\n\t\tconst newCursorPos = ibanFormatter.adjustCursorPosition(ibanForm.getValues('iban'), newValue, input.selectionStart || 0);\n\n\t\t// Update the form model with the parsed (unformatted) value\n\t\tibanForm.setValue('iban', newValue);\n\n\t\t// Use double requestAnimationFrame to ensure cursor position is set after React re-render\n\t\trequestAnimationFrame(() => {\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tinput.setSelectionRange(newCursorPos, newCursorPos);\n\t\t\t});\n\t\t});\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<SampleDescription>\n\t\t\t\t<p>This example demonstrates two different formatting strategies for input fields:</p>\n\t\t\t\t<ul>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Live Formatting (IBAN):</strong> Formatting happens while typing. The value is immediately formatted and displayed with intelligent cursor\n\t\t\t\t\t\tpositioning. Best for fixed-format values where users expect visual feedback during input.\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>On-Blur Formatting (Currency):</strong> Formatting happens when leaving the field (onBlur event). Allows free typing without interruption.\n\t\t\t\t\t\tBest for numeric values where users might input in various formats.\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>Key Concept:</strong> The form model always stores the <em>unformatted</em> value (raw IBAN string, numeric currency value), while the input\n\t\t\t\t\tfield displays the <em>formatted</em> value for better readability.\n\t\t\t\t</p>\n\t\t\t</SampleDescription>\n\t\t\t<section className=\"w-full flex flex-col\">\n\t\t\t\t<div className=\"p-2\">\n\t\t\t\t\t<KolHeading _label=\"Live Formatting - IBAN\" _level={2} />\n\t\t\t\t\t<p className=\"text-sm mb-2\">Formatting occurs during input with intelligent cursor control</p>\n\t\t\t\t\t<KolForm>\n\t\t\t\t\t\t<form onSubmit={ibanForm.handleSubmit(async () => {})}>\n\t\t\t\t\t\t\t<Controller\n\t\t\t\t\t\t\t\tname=\"iban\"\n\t\t\t\t\t\t\t\tcontrol={ibanForm.control}\n\t\t\t\t\t\t\t\trender={({ field }) => (\n\t\t\t\t\t\t\t\t\t<div className=\"block mt-2\">\n\t\t\t\t\t\t\t\t\t\t<KolInputText\n\t\t\t\t\t\t\t\t\t\t\tid=\"field-iban\"\n\t\t\t\t\t\t\t\t\t\t\t_label=\"IBAN\"\n\t\t\t\t\t\t\t\t\t\t\t_value={ibanFormatter.format(field.value ?? '')}\n\t\t\t\t\t\t\t\t\t\t\t_required\n\t\t\t\t\t\t\t\t\t\t\t_on={{\n\t\t\t\t\t\t\t\t\t\t\t\tonInput: handleIbanInput,\n\t\t\t\t\t\t\t\t\t\t\t\tonBlur: field.onBlur,\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</KolForm>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"p-2\">\n\t\t\t\t\t<KolHeading _label=\"Model (Unformatted Value)\" _level={2} />\n\t\t\t\t\t<pre className=\"text-base\">{JSON.stringify(ibanForm.watch(), null, 2)}</pre>\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<section className=\"w-full flex flex-col\">\n\t\t\t\t<div className=\"p-2\">\n\t\t\t\t\t<KolHeading _label=\"On-Blur Formatting - Currency\" _level={2} />\n\t\t\t\t\t<p className=\"text-sm mb-2\">Formatting occurs when leaving the field (onBlur) for uninterrupted input</p>\n\t\t\t\t\t<KolForm>\n\t\t\t\t\t\t<form onSubmit={currencyForm.handleSubmit(async () => {})}>\n\t\t\t\t\t\t\t<Controller\n\t\t\t\t\t\t\t\tname=\"currency\"\n\t\t\t\t\t\t\t\tcontrol={currencyForm.control}\n\t\t\t\t\t\t\t\trender={({ field }) => (\n\t\t\t\t\t\t\t\t\t<div className=\"block mt-2\">\n\t\t\t\t\t\t\t\t\t\t<KolInputText\n\t\t\t\t\t\t\t\t\t\t\tid=\"field-currency\"\n\t\t\t\t\t\t\t\t\t\t\t_label=\"Currency\"\n\t\t\t\t\t\t\t\t\t\t\t_value={currencyFormatter.format(field.value ?? 0)}\n\t\t\t\t\t\t\t\t\t\t\t_on={{\n\t\t\t\t\t\t\t\t\t\t\t\tonBlur: (event: Event) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst input = event.target as HTMLInputElement;\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst parsedValue = currencyFormatter.parse(input.value);\n\t\t\t\t\t\t\t\t\t\t\t\t\tfield.onChange(parsedValue);\n\t\t\t\t\t\t\t\t\t\t\t\t\tfield.onBlur();\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</KolForm>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"p-2\">\n\t\t\t\t\t<KolHeading _label=\"Model (Numeric Value)\" _level={2} />\n\t\t\t\t\t<pre className=\"text-base\">{JSON.stringify(currencyForm.watch(), null, 2)}</pre>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</>\n\t);\n}\n",
|
|
713
713
|
"kind": "sample"
|
|
714
714
|
},
|
|
715
715
|
{
|
|
@@ -1312,6 +1312,14 @@
|
|
|
1312
1312
|
"code": "import {\n\tKolComboboxController,\n\tKolInputCheckboxController,\n\tKolInputColorController,\n\tKolInputDateController,\n\tKolInputEmailController,\n\tKolInputFileController,\n\tKolInputNumberController,\n\tKolInputPasswordController,\n\tKolInputRadioController,\n\tKolInputRangeController,\n\tKolInputTextController,\n\tKolSelectController,\n\tKolSingleSelectController,\n\tKolTextareaController,\n} from '@public-ui/react-hook-form-adapter';\nimport { KolButton, KolForm } from '@public-ui/react-v19';\nimport React, { type BaseSyntheticEvent, type FC } from 'react';\nimport type { FieldErrors, SubmitHandler } from 'react-hook-form';\nimport { useForm } from 'react-hook-form';\n\nimport { SampleDescription } from '../../components/SampleDescription';\nimport { COUNTRY_SUGGESTIONS } from '../../shares/country';\n\ninterface FormData {\n\tfirstName: string;\n\tlastName: string;\n\temail: string;\n\tpassword: string;\n\tage: number;\n\tvolume: number;\n\tbirthday: string;\n\tfavoriteColor: string;\n\tcv: FileList | null;\n\tbio: string;\n\tcountry: string;\n\tlanguage: string;\n\tframework: string;\n\tgender: string;\n\ttermsAccepted: boolean;\n}\n\nconst defaultValues: FormData = {\n\tfirstName: '',\n\tlastName: '',\n\temail: '',\n\tpassword: '',\n\tage: 18,\n\tvolume: 50,\n\tbirthday: '',\n\tfavoriteColor: '#000000',\n\tcv: null,\n\tbio: '',\n\tcountry: '',\n\tlanguage: 'de',\n\tframework: '',\n\tgender: '',\n\ttermsAccepted: false,\n};\n\nconst languageOptions = [\n\t{ label: 'English', value: 'en' },\n\t{ label: 'German', value: 'de' },\n\t{ label: 'French', value: 'fr' },\n];\n\nconst frameworkOptions = [\n\t{ label: 'React', value: 'react' },\n\t{ label: 'Vue', value: 'vue' },\n\t{ label: 'Stencil', value: 'stencil' },\n];\n\nconst genderOptions = [\n\t{ label: 'Male', value: 'male' },\n\t{ label: 'Female', value: 'female' },\n\t{ label: 'Other', value: 'other' },\n];\n\nconst allFields: Array<keyof FormData> = [\n\t'firstName',\n\t'lastName',\n\t'email',\n\t'password',\n\t'age',\n\t'volume',\n\t'birthday',\n\t'favoriteColor',\n\t'cv',\n\t'bio',\n\t'country',\n\t'language',\n\t'framework',\n\t'gender',\n\t'termsAccepted',\n];\n\nexport const RHFBasic: FC = () => {\n\tconst { control, handleSubmit, setValue, getValues, trigger } = useForm<FormData>({\n\t\tdefaultValues,\n\t\tmode: 'onTouched',\n\t\tshouldFocusError: true,\n\t});\n\n\tconst touchAndValidateAll = () => {\n\t\tallFields.forEach((name) => {\n\t\t\tsetValue(name, getValues(name), { shouldTouch: true, shouldValidate: true });\n\t\t});\n\t};\n\n\tconst onSubmit: SubmitHandler<FormData> = (data) => {\n\t\talert(JSON.stringify(data, null, 2));\n\t};\n\n\tconst onError = (errors: FieldErrors<FormData>) => {\n\t\ttouchAndValidateAll();\n\t\tvoid trigger(undefined, { shouldFocus: true });\n\n\t\tconsole.warn('Validation errors:', errors);\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<SampleDescription>\n\t\t\t\t<p>\n\t\t\t\t\tThis sample demonstrates a form using React Hook Form with KoliBri adapters wrapped in a KolForm. All inputs are validated, and error messages are\n\t\t\t\t\tshown on submit.\n\t\t\t\t</p>\n\t\t\t</SampleDescription>\n\n\t\t\t<KolForm\n\t\t\t\tclassName=\"w-full max-w-xl\"\n\t\t\t\t_on={{\n\t\t\t\t\tonSubmit: (event) => {\n\t\t\t\t\t\tvoid handleSubmit(onSubmit, onError)(event as unknown as BaseSyntheticEvent);\n\t\t\t\t\t},\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div className=\"grid gap-4\">\n\t\t\t\t\t<KolInputTextController name=\"firstName\" control={control} _label=\"First Name\" rules={{ required: 'First name is required' }} _required />\n\t\t\t\t\t<KolInputTextController name=\"lastName\" control={control} _label=\"Last Name\" rules={{ required: 'Last name is required' }} _required />\n\t\t\t\t\t<KolInputEmailController name=\"email\" control={control} _label=\"Email\" rules={{ required: 'Email is required' }} _required />\n\t\t\t\t\t<KolInputPasswordController name=\"password\" control={control} _label=\"Password\" rules={{ required: 'Password is required' }} _required />\n\t\t\t\t\t<KolInputNumberController name=\"age\" control={control} _label=\"Age\" rules={{ required: 'Age is required', min: 0 }} _required />\n\t\t\t\t\t<KolInputRangeController name=\"volume\" control={control} _label=\"Volume (0–100)\" _min={0} _max={100} />\n\t\t\t\t\t<KolInputDateController name=\"birthday\" control={control} _label=\"Birthday\" rules={{ required: 'Birthday is required' }} />\n\t\t\t\t\t<KolInputColorController name=\"favoriteColor\" control={control} _label=\"Favorite Color\" id=\"favoriteColor\" />\n\t\t\t\t\t<KolInputFileController name=\"cv\" control={control} _label=\"Upload CV\" rules={{ required: 'Please upload your CV' }} _required />\n\t\t\t\t\t<KolTextareaController name=\"bio\" control={control} _label=\"Bio\" rules={{ required: 'Please provide a short bio' }} _required />\n\t\t\t\t\t<KolComboboxController\n\t\t\t\t\t\tcontrol={control}\n\t\t\t\t\t\trules={{ required: 'Please select a country' }}\n\t\t\t\t\t\tname=\"country\"\n\t\t\t\t\t\t_label=\"Country\"\n\t\t\t\t\t\t_suggestions={COUNTRY_SUGGESTIONS}\n\t\t\t\t\t\t_required\n\t\t\t\t\t/>\n\t\t\t\t\t<KolSelectController\n\t\t\t\t\t\tcontrol={control}\n\t\t\t\t\t\trules={{ required: 'Please select a language' }}\n\t\t\t\t\t\tname=\"language\"\n\t\t\t\t\t\t_label=\"Preferred Language\"\n\t\t\t\t\t\t_options={languageOptions}\n\t\t\t\t\t\t_required\n\t\t\t\t\t/>\n\t\t\t\t\t<KolSingleSelectController\n\t\t\t\t\t\trules={{ required: 'Please select a framework' }}\n\t\t\t\t\t\tcontrol={control}\n\t\t\t\t\t\tname=\"framework\"\n\t\t\t\t\t\t_label=\"Favorite Framework\"\n\t\t\t\t\t\t_options={frameworkOptions}\n\t\t\t\t\t\t_required\n\t\t\t\t\t/>\n\t\t\t\t\t<KolInputRadioController\n\t\t\t\t\t\tcontrol={control}\n\t\t\t\t\t\trules={{ required: 'Please select your gender' }}\n\t\t\t\t\t\tname=\"gender\"\n\t\t\t\t\t\t_label=\"Gender\"\n\t\t\t\t\t\t_options={genderOptions}\n\t\t\t\t\t\t_required\n\t\t\t\t\t/>\n\t\t\t\t\t<KolInputCheckboxController\n\t\t\t\t\t\tname=\"termsAccepted\"\n\t\t\t\t\t\tcontrol={control}\n\t\t\t\t\t\t_label=\"I accept the terms and conditions\"\n\t\t\t\t\t\trules={{ required: 'You must accept the terms' }}\n\t\t\t\t\t\t_required\n\t\t\t\t\t/>\n\n\t\t\t\t\t<KolButton _label=\"Submit\" _type=\"submit\" />\n\t\t\t\t</div>\n\t\t\t</KolForm>\n\t\t</>\n\t);\n};\n",
|
|
1313
1313
|
"kind": "scenario"
|
|
1314
1314
|
},
|
|
1315
|
+
{
|
|
1316
|
+
"id": "scenario/scenarios/react-hook-form-adapter-disabled",
|
|
1317
|
+
"group": "scenarios",
|
|
1318
|
+
"name": "react-hook-form-adapter-disabled",
|
|
1319
|
+
"path": "packages/samples/react/src/scenarios/react-hook-form/disabled.tsx",
|
|
1320
|
+
"code": "import { KolInputTextController } from '@public-ui/react-hook-form-adapter';\nimport { KolButton, KolForm } from '@public-ui/react-v19';\nimport React, { type BaseSyntheticEvent, type FC } from 'react';\nimport type { SubmitHandler } from 'react-hook-form';\nimport { useForm } from 'react-hook-form';\n\nimport { SampleDescription } from '../../components/SampleDescription';\n\ninterface FormData {\n\tfirst: string;\n\tsecond: string;\n}\n\nconst defaultValues: FormData = {\n\tfirst: '',\n\tsecond: '',\n};\n\nexport const RHFDisabled: FC = () => {\n\tconst { control, handleSubmit, watch } = useForm<FormData>({\n\t\tdefaultValues,\n\t});\n\n\tconst onSubmit: SubmitHandler<FormData> = (data) => {\n\t\talert(JSON.stringify(data, null, 2));\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<SampleDescription>\n\t\t\t\t<p>This sample shows two fields of which just one can be filled. The other gets disabled.</p>\n\t\t\t</SampleDescription>\n\n\t\t\t<KolForm\n\t\t\t\tclassName=\"w-full max-w-xl\"\n\t\t\t\t_on={{\n\t\t\t\t\tonSubmit: (event) => {\n\t\t\t\t\t\tvoid handleSubmit(onSubmit)(event as unknown as BaseSyntheticEvent);\n\t\t\t\t\t},\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div className=\"grid gap-4\">\n\t\t\t\t\t<KolInputTextController name=\"first\" control={control} _label=\"First\" _disabled={!!watch('second')} />\n\n\t\t\t\t\t<KolInputTextController name=\"second\" control={control} _label=\"Second\" _disabled={!!watch('first')} />\n\n\t\t\t\t\t<KolButton _label=\"Submit\" _type=\"submit\" />\n\t\t\t\t</div>\n\t\t\t</KolForm>\n\t\t</>\n\t);\n};\n",
|
|
1321
|
+
"kind": "scenario"
|
|
1322
|
+
},
|
|
1315
1323
|
{
|
|
1316
1324
|
"id": "scenario/scenarios/same-height-of-all-interactive-elements",
|
|
1317
1325
|
"group": "scenarios",
|