@loworbitstudio/visor 1.23.0 → 1.24.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.
- package/dist/CHANGELOG.json +1 -1
- package/dist/index.js +3 -2
- package/dist/registry.json +5 -4
- package/dist/visor-manifest.json +36 -10
- package/package.json +1 -1
package/dist/CHANGELOG.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -1627,9 +1627,10 @@ function checkDesignCommand(pathArg, options) {
|
|
|
1627
1627
|
composition: options.composition ?? false
|
|
1628
1628
|
});
|
|
1629
1629
|
const useJson = options.json || options.format === "json";
|
|
1630
|
+
const shouldFail = options.fail !== false;
|
|
1630
1631
|
if (useJson) {
|
|
1631
1632
|
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
1632
|
-
if (
|
|
1633
|
+
if (shouldFail && result.summary.errorCount > 0) process.exitCode = 1;
|
|
1633
1634
|
return;
|
|
1634
1635
|
}
|
|
1635
1636
|
const { errors, warnings, summary, composition } = result;
|
|
@@ -1680,7 +1681,7 @@ function checkDesignCommand(pathArg, options) {
|
|
|
1680
1681
|
printCompositionScope();
|
|
1681
1682
|
printKitMembership();
|
|
1682
1683
|
logger.blank();
|
|
1683
|
-
if (
|
|
1684
|
+
if (shouldFail && summary.errorCount > 0) process.exit(1);
|
|
1684
1685
|
}
|
|
1685
1686
|
function checkThemeModeCommand(pathArg, options) {
|
|
1686
1687
|
let result;
|
package/dist/registry.json
CHANGED
|
@@ -1930,11 +1930,12 @@
|
|
|
1930
1930
|
{
|
|
1931
1931
|
"name": "phone-input",
|
|
1932
1932
|
"type": "registry:ui",
|
|
1933
|
-
"description": "An international phone number input with country code selection,
|
|
1933
|
+
"description": "An international phone number input with country code selection, format-as-you-type, and E.164 validation.",
|
|
1934
1934
|
"category": "form",
|
|
1935
1935
|
"dependencies": [
|
|
1936
1936
|
"@loworbitstudio/visor-core",
|
|
1937
|
-
"intl-tel-input"
|
|
1937
|
+
"intl-tel-input",
|
|
1938
|
+
"@intl-tel-input/react"
|
|
1938
1939
|
],
|
|
1939
1940
|
"registryDependencies": [
|
|
1940
1941
|
"utils"
|
|
@@ -1943,12 +1944,12 @@
|
|
|
1943
1944
|
{
|
|
1944
1945
|
"path": "components/ui/phone-input/phone-input.tsx",
|
|
1945
1946
|
"type": "registry:ui",
|
|
1946
|
-
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport \"intl-tel-input/styles\"\nimport { cn } from \"../../../lib/utils\"\n\nexport interface PhoneInputProps {\n /** HTML id attribute */\n id?: string\n /** HTML name attribute */\n name?: string\n /**
|
|
1947
|
+
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport IntlTelInput from \"@intl-tel-input/react\"\nimport type { IntlTelInputRef } from \"@intl-tel-input/react\"\nimport type { CountrySelectorMode, Iso2, ValidationError } from \"intl-tel-input\"\nimport \"intl-tel-input/styles\"\nimport { cn } from \"../../../lib/utils\"\n\n// Password managers mangle tel fields. Typed through `input` props because the\n// library's `inputProps` surface does not model arbitrary data attributes.\nconst PASSWORD_MANAGER_OPT_OUT = {\n \"data-1p-ignore\": true,\n \"data-lpignore\": \"true\",\n \"data-bwignore\": true,\n \"data-form-type\": \"other\",\n} as React.ComponentPropsWithoutRef<\"input\">\n\nexport interface PhoneInputPortal {\n /** Element the country selector is portaled into. */\n container: HTMLElement\n /**\n * Theme-scoping class applied to the portaled country selector. Required —\n * a selector portaled out of the tree escapes the consumer's theme class,\n * so the scope has to travel with it.\n */\n scopeClassName: string\n}\n\nexport interface PhoneInputProps {\n /** HTML id attribute */\n id?: string\n /** HTML name attribute */\n name?: string\n /** Phone number in E.164 (e.g. \"+14155551234\"). Displayed formatted for the detected country. */\n value?: string | null\n /** Placeholder text */\n placeholder?: string\n /** Whether the field is required */\n required?: boolean\n /** Whether the field is disabled */\n disabled?: boolean\n /** Whether the field is read-only. Pair with `countrySelectorMode=\"OFF\"` to also close off the country dropdown. */\n readOnly?: boolean\n /** Country selector behaviour. `\"OFF\"` renders the selected country as a non-interactive element. */\n countrySelectorMode?: CountrySelectorMode\n /** ISO 3166-1 alpha-2 country selected on mount. */\n initialCountry?: Iso2\n /** Size variant — matches Input component sizes */\n size?: \"sm\" | \"md\" | \"lg\"\n /** Additional CSS classes applied to the wrapper */\n className?: string\n /**\n * Portal the country selector out of the input's DOM position. All-or-nothing:\n * the scoping class is not separable from the container, so the selector can\n * never escape its theme. Omit for the default inline selector.\n */\n portal?: PhoneInputPortal\n /**\n * Called with the E.164 number and its validity. `e164` is `null` for anything\n * that is not a complete, valid number — a partial is never emitted.\n */\n onChange?: (e164: string | null, isValid: boolean) => void\n /** Called on blur with the validation error for a non-empty, invalid number (otherwise `null`). */\n onBlur?: (error: ValidationError | null) => void\n}\n\nconst PhoneInput = React.forwardRef<IntlTelInputRef, PhoneInputProps>(\n (\n {\n id,\n name,\n value,\n placeholder,\n required = false,\n disabled = false,\n readOnly = false,\n countrySelectorMode,\n initialCountry = \"us\",\n size = \"md\",\n className,\n portal,\n onChange,\n onBlur,\n },\n ref\n ) => {\n const itiRef = React.useRef<IntlTelInputRef>(null)\n // `undefined` until the first emission — an initial `{ null, false }` would be\n // indistinguishable from the first partial the user types and would swallow it.\n const lastEmitted = React.useRef<\n { e164: string | null; isValid: boolean } | undefined\n >(undefined)\n\n React.useImperativeHandle(ref, () => ({\n getInstance: () => itiRef.current?.getInstance() ?? null,\n getInput: () => itiRef.current?.getInput() ?? null,\n }))\n\n // `emit` must be identity-stable. The wrapper derives its internal `update`\n // from the onChange* props and keys its value-writeback effect on\n // `[value, update]`; a new `emit` each render re-runs that effect, and while\n // the field is unfocused and holding a partial (so the controlled value is\n // null) it calls setNumber(\"\") and erases what the user typed. Reading the\n // consumer's callback from a ref keeps the effect keyed on `value` alone.\n const onChangeRef = React.useRef(onChange)\n onChangeRef.current = onChange\n\n // The library's own `onChangeNumber` emits `getNumber()` verbatim, which for a\n // partial is a `+<dial code>` prefix glued to the formatted national string\n // (typing \"21337\" yields \"+1213-37\"). Derive the value at emit time instead so\n // a malformed number can never reach the consumer.\n const emit = React.useCallback(() => {\n const iti = itiRef.current?.getInstance()\n const isValid = iti?.isValidNumber() ?? false\n const e164 = isValid ? iti!.getNumber() : null\n const last = lastEmitted.current\n if (last && last.e164 === e164 && last.isValid === isValid) return\n lastEmitted.current = { e164, isValid }\n onChangeRef.current?.(e164, isValid)\n }, [])\n\n const handleBlur = React.useCallback(() => {\n const iti = itiRef.current?.getInstance()\n const input = itiRef.current?.getInput()\n // The error callback never fires while a number is still partial, so read the\n // error directly — a visibly-present partial that saves as nothing is silent\n // data loss. Gate on validity: getValidationError() reports \"IS_POSSIBLE\" for\n // a number that is fine, which is not an error.\n const error =\n iti && input?.value && !iti.isValidNumber()\n ? (iti.getValidationError() ?? null)\n : null\n onBlur?.(error)\n }, [onBlur])\n\n return (\n <div data-slot=\"phone-input\" data-size={size} className={cn(className)}>\n <IntlTelInput\n ref={itiRef}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n onChangeNumber={emit}\n onChangeCountry={emit}\n onChangeValidity={emit}\n initialCountry={initialCountry}\n initialCountryLookup={null}\n countrySelectorMode={countrySelectorMode}\n separateDialCode\n formatAsYouType\n strictMode\n countrySearch\n loadUtils={() => import(\"intl-tel-input/utils\")}\n dropdownParent={portal?.container ?? null}\n classNames={\n portal\n ? { countrySelectorContainer: portal.scopeClassName }\n : undefined\n }\n inputProps={{\n id,\n name,\n placeholder,\n required,\n autoComplete: \"tel\",\n onBlur: handleBlur,\n ...PASSWORD_MANAGER_OPT_OUT,\n }}\n />\n </div>\n )\n }\n)\nPhoneInput.displayName = \"PhoneInput\"\n\nexport { PhoneInput }\n"
|
|
1947
1948
|
},
|
|
1948
1949
|
{
|
|
1949
1950
|
"path": "components/ui/phone-input/phone-input.css",
|
|
1950
1951
|
"type": "registry:ui",
|
|
1951
|
-
"content": "/* intl-tel-input
|
|
1952
|
+
"content": "/* intl-tel-input v29 theming — Visor design tokens\n *\n * Global (not a CSS Module) because intl-tel-input renders its own DOM, and the\n * country selector can be portaled out of the React tree.\n *\n * v29 exposes CSS custom properties for the country selector, so the dropdown is\n * themed by binding those rather than by overriding rules. Every binding falls\n * back to the vendor default, so a theme missing a token behaves exactly as the\n * unstyled library does. The input chrome still needs explicit rules — the\n * library ships no colours for the `<input>` itself.\n */\n\n.iti {\n display: block;\n width: 100%;\n\n /* v29 country-selector variables → Visor tokens (vendor defaults as fallbacks) */\n --iti-country-selector-bg: var(--surface-popover, white);\n --iti-border-color: var(--border-default, #ccc);\n --iti-icon-color: var(--text-secondary, #555);\n --iti-hover-color: var(--surface-interactive-hover, rgba(0, 0, 0, 0.05));\n --iti-strict-reject-flash-color: var(\n --surface-error-subtle,\n rgba(255, 0, 0, 0.12)\n );\n\n /* The country list sets no colour of its own — it inherits. Without this the\n list would take its colour from whatever encloses the input. */\n color: var(--text-primary, #111827);\n}\n\n/* The input element itself — match Visor Input component (input.module.css) */\n.iti input.iti__tel-input {\n width: 100% !important;\n min-width: 0 !important;\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb) !important;\n background-color: var(--surface-interactive-default, #f9fafb) !important;\n /* padding-left is set dynamically by intl-tel-input to fit the flag + dial code */\n color: var(--text-primary, #111827) !important;\n line-height: 1.5 !important;\n transition: border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n box-shadow var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out) !important;\n outline: none !important;\n}\n\n/* Size: sm — compact */\n[data-size=\"sm\"] .iti input.iti__tel-input {\n height: 2.25rem !important;\n padding-top: var(--spacing-1, 0.25rem) !important;\n padding-bottom: var(--spacing-1, 0.25rem) !important;\n padding-right: var(--spacing-3, 0.75rem) !important;\n font-size: var(--font-size-sm, 0.875rem) !important;\n border-radius: var(--radius-sm, 0.25rem) !important;\n}\n\n/* Size: md (default) */\n[data-size=\"md\"] .iti input.iti__tel-input {\n height: auto !important;\n padding-top: 0.875rem !important;\n padding-bottom: 0.875rem !important;\n padding-right: 1rem !important;\n font-size: var(--font-size-sm, 0.875rem) !important;\n border-radius: var(--radius-sm, 0.5rem) !important;\n}\n\n/* Size: lg */\n[data-size=\"lg\"] .iti input.iti__tel-input {\n height: auto !important;\n padding-top: 1.125rem !important;\n padding-bottom: 1.125rem !important;\n padding-right: 1.25rem !important;\n font-size: var(--font-size-base, 1rem) !important;\n border-radius: var(--radius-sm, 0.5rem) !important;\n}\n\n.iti input.iti__tel-input::placeholder {\n color: var(--text-secondary, #9ca3af) !important;\n}\n\n.iti input.iti__tel-input:focus {\n border-color: var(--border-focus, #111827) !important;\n box-shadow: 0 0 0 var(--focus-ring-width, 2px) color-mix(in srgb, var(--border-focus, #111827) 15%, transparent) !important;\n}\n\n.iti input.iti__tel-input:disabled {\n pointer-events: none !important;\n cursor: not-allowed !important;\n opacity: var(--opacity-50, 0.5) !important;\n}\n\n/* Country selector panel — v29 hardcodes a 3px radius and an untokenised shadow */\n.iti__country-selector {\n border-radius: var(--radius-md, 0.375rem) !important;\n box-shadow: var(--shadow-md) !important;\n}\n\n/* With countrySearch on, v29 pins the panel height so it does not jump while the\n list is filtered — but it measures that height in a throwaway container on\n document.body, outside this page's typography. The search field's line-height\n is inherited, so it measured shorter there than it renders here and the pinned\n height came up short, letting the square-cornered list and its scrollbar spill\n past the panel's rounded bottom corner. Clipping is the guard; pinning\n line-height on the search field is the fix — it makes the measurement and the\n render agree, so nothing needs to be clipped in the first place. */\n.iti--inline-country-selector .iti__country-selector {\n overflow: hidden !important;\n}\n\n/* The search field is an <input>: it takes UA `Field`/`FieldText` colours rather\n than inheriting, so on a dark theme it would render as a white box inside a\n dark panel. The library ships no colours for it. */\n.iti__search-input {\n background-color: transparent !important;\n color: var(--text-primary, #111827) !important;\n font-size: var(--font-size-sm, 0.875rem) !important;\n /* Must be an explicit value, not inherited — see the panel-clipping note above.\n An inherited line-height resolves differently in v29's off-page measuring\n container than it does here, which throws off the pinned panel height. */\n line-height: 1.5 !important;\n outline: none !important;\n}\n\n.iti__search-input::placeholder {\n color: var(--text-secondary, #9ca3af) !important;\n}\n\n.iti__search-input:focus-visible {\n box-shadow: inset 0 0 0 var(--focus-ring-width, 2px) color-mix(in srgb, var(--border-focus, #111827) 15%, transparent) !important;\n}\n\n/* Dial code beside the country name in the list */\n.iti__dial-code {\n color: var(--text-secondary, #9ca3af) !important;\n}\n\n/* Selected country trigger overlaying the left of the input */\n.iti__selected-country-primary {\n border-radius: var(--radius-md, 0.375rem) 0 0 var(--radius-md, 0.375rem) !important;\n}\n\n.iti__selected-dial-code {\n color: var(--text-primary, #111827) !important;\n}\n"
|
|
1952
1953
|
},
|
|
1953
1954
|
{
|
|
1954
1955
|
"path": "components/ui/phone-input/phone-input.module.css",
|
package/dist/visor-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.4.0",
|
|
3
|
-
"generated_at": "2026-09-
|
|
3
|
+
"generated_at": "2026-09-09T19:18:41.930Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"accessibility-specimen": {
|
|
6
6
|
"category": "specimen",
|
|
@@ -6623,7 +6623,7 @@
|
|
|
6623
6623
|
},
|
|
6624
6624
|
"phone-input": {
|
|
6625
6625
|
"category": "form",
|
|
6626
|
-
"description": "An international phone number input with country code selection,
|
|
6626
|
+
"description": "An international phone number input with country code selection, format-as-you-type, and E.164 validation.",
|
|
6627
6627
|
"when_to_use": [
|
|
6628
6628
|
"Phone number entry fields (domestic or international)",
|
|
6629
6629
|
"Contact forms requiring phone numbers",
|
|
@@ -6646,8 +6646,8 @@
|
|
|
6646
6646
|
},
|
|
6647
6647
|
{
|
|
6648
6648
|
"name": "value",
|
|
6649
|
-
"type": "string",
|
|
6650
|
-
"description": "
|
|
6649
|
+
"type": "string | null",
|
|
6650
|
+
"description": "Phone number in E.164 (e.g. \"+14155551234\"). Displayed formatted for the detected country."
|
|
6651
6651
|
},
|
|
6652
6652
|
{
|
|
6653
6653
|
"name": "placeholder",
|
|
@@ -6657,13 +6657,33 @@
|
|
|
6657
6657
|
{
|
|
6658
6658
|
"name": "required",
|
|
6659
6659
|
"type": "boolean",
|
|
6660
|
+
"default": "false",
|
|
6660
6661
|
"description": "Whether the field is required."
|
|
6661
6662
|
},
|
|
6662
6663
|
{
|
|
6663
6664
|
"name": "disabled",
|
|
6664
6665
|
"type": "boolean",
|
|
6666
|
+
"default": "false",
|
|
6665
6667
|
"description": "Whether the field is disabled."
|
|
6666
6668
|
},
|
|
6669
|
+
{
|
|
6670
|
+
"name": "readOnly",
|
|
6671
|
+
"type": "boolean",
|
|
6672
|
+
"default": "false",
|
|
6673
|
+
"description": "Whether the field is read-only. Pair with countrySelectorMode=\"OFF\" to also close off the country dropdown."
|
|
6674
|
+
},
|
|
6675
|
+
{
|
|
6676
|
+
"name": "countrySelectorMode",
|
|
6677
|
+
"type": "\"OFF\" | \"DROPDOWN\" | \"FULLSCREEN\" | \"AUTO\"",
|
|
6678
|
+
"default": "\"AUTO\"",
|
|
6679
|
+
"description": "Country selector behaviour. \"OFF\" renders the selected country as a non-interactive element."
|
|
6680
|
+
},
|
|
6681
|
+
{
|
|
6682
|
+
"name": "initialCountry",
|
|
6683
|
+
"type": "Iso2",
|
|
6684
|
+
"default": "\"us\"",
|
|
6685
|
+
"description": "ISO 3166-1 alpha-2 country selected on mount."
|
|
6686
|
+
},
|
|
6667
6687
|
{
|
|
6668
6688
|
"name": "size",
|
|
6669
6689
|
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
@@ -6675,22 +6695,28 @@
|
|
|
6675
6695
|
"type": "string",
|
|
6676
6696
|
"description": "Additional CSS classes applied to the wrapper."
|
|
6677
6697
|
},
|
|
6698
|
+
{
|
|
6699
|
+
"name": "portal",
|
|
6700
|
+
"type": "{ container: HTMLElement; scopeClassName: string }",
|
|
6701
|
+
"description": "Portal the country selector out of the input's DOM position. All-or-nothing — the theme-scoping class is not separable from the container, so a portaled selector can never escape its theme. Omit for the default inline selector.\n"
|
|
6702
|
+
},
|
|
6678
6703
|
{
|
|
6679
6704
|
"name": "onChange",
|
|
6680
|
-
"type": "(
|
|
6681
|
-
"description": "Called with the
|
|
6705
|
+
"type": "(e164: string | null, isValid: boolean) => void",
|
|
6706
|
+
"description": "Called with the E.164 number and its validity. e164 is null for anything that is not a complete, valid number — a partial is never emitted."
|
|
6682
6707
|
},
|
|
6683
6708
|
{
|
|
6684
6709
|
"name": "onBlur",
|
|
6685
|
-
"type": "() => void",
|
|
6686
|
-
"description": "Called
|
|
6710
|
+
"type": "(error: ValidationError | null) => void",
|
|
6711
|
+
"description": "Called on blur with the validation error for a non-empty, invalid number (otherwise null)."
|
|
6687
6712
|
}
|
|
6688
6713
|
],
|
|
6689
6714
|
"dependencies": [
|
|
6690
|
-
"intl-tel-input"
|
|
6715
|
+
"intl-tel-input",
|
|
6716
|
+
"@intl-tel-input/react"
|
|
6691
6717
|
],
|
|
6692
6718
|
"tokens_used": [],
|
|
6693
|
-
"example": "<PhoneInput\n name=\"phone\"\n placeholder=\"Enter phone number\"\n onChange={(
|
|
6719
|
+
"example": "<PhoneInput\n name=\"phone\"\n placeholder=\"Enter phone number\"\n onChange={(e164, isValid) => {\n console.log(e164, isValid);\n }}\n/>\n"
|
|
6694
6720
|
},
|
|
6695
6721
|
"popover": {
|
|
6696
6722
|
"category": "feedback",
|