@loworbitstudio/visor 1.26.2 → 1.28.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-09-21T22:17:12.822Z",
3
+ "generated_at": "2026-09-24T03:45:50.617Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "category": "specimen",
@@ -1034,7 +1034,8 @@
1034
1034
  "sm",
1035
1035
  "md",
1036
1036
  "lg",
1037
- "dlg"
1037
+ "dlg",
1038
+ "icon"
1038
1039
  ]
1039
1040
  },
1040
1041
  "props": [
@@ -1045,9 +1046,9 @@
1045
1046
  },
1046
1047
  {
1047
1048
  "name": "size",
1048
- "type": "'sm' | 'md' | 'lg' | 'dlg'",
1049
+ "type": "'sm' | 'md' | 'lg' | 'dlg' | 'icon'",
1049
1050
  "default": "md",
1050
- "description": "Standard sizes plus `dlg` — the compact dialog-footer button (dlg-btn) from the Animal dialog substrate (VI-620): 11px/600 text, tokenized ~8/12px padding, radius-lg. Its three intents reuse the variant axis — primary via variant=\"default\", ghost via variant=\"ghost\" (renders a hairline-outlined, secondary-ink face at dlg size), danger via variant=\"destructive\" (renders a subtle-danger face at dlg size).\n"
1051
+ "description": "Standard sizes plus `dlg` — the compact dialog-footer button (dlg-btn) from the Animal dialog substrate (VI-620): 11px/600 text, tokenized ~8/12px padding, radius-lg. Its three intents reuse the variant axis — primary via variant=\"default\", ghost via variant=\"ghost\" (renders a hairline-outlined, secondary-ink face at dlg size), danger via variant=\"destructive\" (renders a subtle-danger face at dlg size). `icon` is a square icon-only mark — glyph (--button-icon-size) plus a hit-target pad (--button-icon-pad) on every side, so width always equals height. Use it with variant=\"ghost\" for a bare mark in secondary ink. Requires an aria-label; warns in development without one.\n"
1051
1052
  },
1052
1053
  {
1053
1054
  "name": "asChild",
@@ -1105,6 +1106,32 @@
1105
1106
  "--border-default",
1106
1107
  "--border-focus",
1107
1108
  "--border-strong",
1109
+ "--button-dlg-font-size",
1110
+ "--button-dlg-letter-spacing",
1111
+ "--button-dlg-radius",
1112
+ "--button-dlg-text-transform",
1113
+ "--button-icon-ghost-color",
1114
+ "--button-icon-pad",
1115
+ "--button-icon-radius",
1116
+ "--button-icon-size",
1117
+ "--button-letter-spacing",
1118
+ "--button-lg-font-size",
1119
+ "--button-lg-letter-spacing",
1120
+ "--button-lg-radius",
1121
+ "--button-lg-text-transform",
1122
+ "--button-md-font-size",
1123
+ "--button-md-letter-spacing",
1124
+ "--button-md-radius",
1125
+ "--button-md-text-transform",
1126
+ "--button-radius",
1127
+ "--button-sm-font-size",
1128
+ "--button-sm-letter-spacing",
1129
+ "--button-sm-radius",
1130
+ "--button-sm-text-transform",
1131
+ "--button-text-transform",
1132
+ "--control-edge-color",
1133
+ "--control-edge-style",
1134
+ "--control-edge-width",
1108
1135
  "--destructive",
1109
1136
  "--focus-ring-offset",
1110
1137
  "--focus-ring-width",
@@ -1586,8 +1613,13 @@
1586
1613
  "--checkbox-bg-checked",
1587
1614
  "--checkbox-border",
1588
1615
  "--checkbox-border-checked",
1616
+ "--checkbox-edgeless-bg",
1589
1617
  "--checkbox-radius",
1590
1618
  "--checkbox-size",
1619
+ "--control-edge-color",
1620
+ "--control-edge-style",
1621
+ "--control-edge-width",
1622
+ "--control-state-edge-width",
1591
1623
  "--focus-ring-width",
1592
1624
  "--hairline-strong",
1593
1625
  "--interactive-primary-bg",
@@ -1767,6 +1799,9 @@
1767
1799
  "--chip-sm-padding-x",
1768
1800
  "--chip-text-color",
1769
1801
  "--chip-text-transform",
1802
+ "--control-edge-color",
1803
+ "--control-edge-style",
1804
+ "--control-edge-width",
1770
1805
  "--focus-ring-offset",
1771
1806
  "--focus-ring-width",
1772
1807
  "--font-body",
@@ -4100,6 +4135,10 @@
4100
4135
  ],
4101
4136
  "tokens_used": [
4102
4137
  "--border-default",
4138
+ "--control-drop-edge-style",
4139
+ "--control-drop-edge-width",
4140
+ "--control-edge-color",
4141
+ "--control-edge-width",
4103
4142
  "--empty-state-actions-gap",
4104
4143
  "--empty-state-bg",
4105
4144
  "--empty-state-border",
@@ -4384,6 +4423,10 @@
4384
4423
  "class-variance-authority"
4385
4424
  ],
4386
4425
  "tokens_used": [
4426
+ "--field-label-color",
4427
+ "--field-label-font-size",
4428
+ "--field-label-letter-spacing",
4429
+ "--field-label-text-transform",
4387
4430
  "--font-size-sm",
4388
4431
  "--font-size-xs",
4389
4432
  "--font-weight-medium",
@@ -4503,6 +4546,10 @@
4503
4546
  "--border-default",
4504
4547
  "--border-focus",
4505
4548
  "--border-strong",
4549
+ "--control-drop-edge-style",
4550
+ "--control-drop-edge-width",
4551
+ "--control-edge-color",
4552
+ "--control-edge-width",
4506
4553
  "--focus-ring-offset",
4507
4554
  "--focus-ring-width",
4508
4555
  "--font-size-sm",
@@ -5307,6 +5354,93 @@
5307
5354
  ],
5308
5355
  "example": "<Inline gap=\"sm\">\n <Button>Save</Button>\n <Button variant=\"secondary\">Cancel</Button>\n</Inline>\n\n<Inline gap=\"xs\" wrap>\n {tags.map(t => <Badge key={t}>{t}</Badge>)}\n</Inline>\n"
5309
5356
  },
5357
+ "inline-edit": {
5358
+ "category": "form",
5359
+ "description": "Inline-editable text — the text, then a pencil. A click or Enter turns it into an input in place, at the same size; Enter, blur or Tab commits, Escape restores, and clearing it brings back a muted default.",
5360
+ "when_to_use": [
5361
+ "A title the user can rename where it stands — a section heading, a photo set's name, a venue group, a rider's title",
5362
+ "A single-line value whose default should come back when the user clears it",
5363
+ "Renaming at the size of the heading being renamed, with no layout jump into an editing state"
5364
+ ],
5365
+ "when_not_to_use": [
5366
+ "Multi-line text — use Textarea",
5367
+ "Rich text — use a dedicated rich-text editor",
5368
+ "A labelled card tile with a save button and an AI action — use EditableBlock",
5369
+ "A field inside a form that is always editable — use Input with Field"
5370
+ ],
5371
+ "variants": {
5372
+ "state": {
5373
+ "rest": "The text (or the muted default), then the pencil. No edge.",
5374
+ "editing": "A single-line input in the text's place, at the same size, drawing the shared control edge."
5375
+ }
5376
+ },
5377
+ "props": [
5378
+ {
5379
+ "name": "value",
5380
+ "type": "string",
5381
+ "description": "The committed text. An empty string means no override, and `defaultValue` shows instead."
5382
+ },
5383
+ {
5384
+ "name": "defaultValue",
5385
+ "type": "string",
5386
+ "default": "\"\"",
5387
+ "description": "Shown, muted, while `value` is empty, and used as the input's placeholder."
5388
+ },
5389
+ {
5390
+ "name": "onCommit",
5391
+ "type": "(value: string) => void",
5392
+ "description": "Called with the trimmed text on Enter, blur or Tab, only when it differs from `value`. An empty string means \"restore the default\". The parent updates `value`."
5393
+ },
5394
+ {
5395
+ "name": "label",
5396
+ "type": "string",
5397
+ "description": "The field's name (e.g. \"Section title\"). Names the input, and the pencil as \"Edit {label}\"."
5398
+ },
5399
+ {
5400
+ "name": "editLabel",
5401
+ "type": "string",
5402
+ "description": "Accessible name of the pencil. Defaults to \"Edit {label}\"."
5403
+ },
5404
+ {
5405
+ "name": "as",
5406
+ "type": "'span' | 'div' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'",
5407
+ "default": "\"span\"",
5408
+ "description": "The element to render. Typography inherits from it, so a title keeps its heading's size and colour."
5409
+ },
5410
+ {
5411
+ "name": "defaultEditing",
5412
+ "type": "boolean",
5413
+ "default": "false",
5414
+ "description": "Opens in the editing state on mount, without moving focus."
5415
+ }
5416
+ ],
5417
+ "dependencies": [
5418
+ "@phosphor-icons/react",
5419
+ "@loworbitstudio/visor-core"
5420
+ ],
5421
+ "tokens_used": [
5422
+ "--border-default",
5423
+ "--border-focus",
5424
+ "--button-icon-ghost-color",
5425
+ "--button-icon-pad",
5426
+ "--button-icon-size",
5427
+ "--control-edge-color",
5428
+ "--control-edge-style",
5429
+ "--control-edge-width",
5430
+ "--control-state-edge-width",
5431
+ "--focus-ring-offset",
5432
+ "--focus-ring-width",
5433
+ "--motion-duration-150",
5434
+ "--motion-easing-default",
5435
+ "--radius-sm",
5436
+ "--spacing-1",
5437
+ "--spacing-2",
5438
+ "--text-primary",
5439
+ "--text-secondary",
5440
+ "--text-tertiary"
5441
+ ],
5442
+ "example": "const [title, setTitle] = React.useState(\"\")\n\n<InlineEdit\n as=\"h2\"\n label=\"Section title\"\n value={title}\n defaultValue=\"Track record\"\n onCommit={setTitle}\n/>\n"
5443
+ },
5310
5444
  "input": {
5311
5445
  "category": "form",
5312
5446
  "description": "A text input component with focus and validation states.",
@@ -5353,6 +5487,10 @@
5353
5487
  "--border-error",
5354
5488
  "--border-focus",
5355
5489
  "--border-strong",
5490
+ "--control-edge-color",
5491
+ "--control-edge-style",
5492
+ "--control-edge-width",
5493
+ "--control-state-edge-width",
5356
5494
  "--field-control-bg",
5357
5495
  "--focus-ring-offset",
5358
5496
  "--focus-ring-width",
@@ -7033,6 +7171,78 @@
7033
7171
  ],
7034
7172
  "example": "<RadiusScale steps={[\n { token: \"--radius-sm\", name: \"sm\", px: 2 },\n { token: \"--radius-md\", name: \"md\", px: 4 },\n { token: \"--radius-full\", name: \"full\", px: 9999 },\n]} />\n"
7035
7173
  },
7174
+ "save-status": {
7175
+ "category": "feedback",
7176
+ "description": "Autosave readout — Saved / Saving / Unsaved / Couldn't save in a fixed-width slot that never reflows its row, with a retry mark after a failed save. aria-live polite; draws no edge.",
7177
+ "when_to_use": [
7178
+ "Showing where an autosaved value stands, beside the section or field it saves",
7179
+ "Any editor with no Save button, driven by the useAutosave hook",
7180
+ "Offering a retry after a save failed, without a toast"
7181
+ ],
7182
+ "when_not_to_use": [
7183
+ "Confirming a one-off action the user just took — use SuccessFeedback or a toast",
7184
+ "A save/cancel footer model — use the admin editor patterns' footer buttons",
7185
+ "Conflicts between tabs or users — use ConflictBanner"
7186
+ ],
7187
+ "variants": {
7188
+ "status": {
7189
+ "saved": "Saved — muted, the resting state.",
7190
+ "saving": "Saving — a write is in flight.",
7191
+ "unsaved": "Unsaved — a change is waiting for the debounce.",
7192
+ "refused": "Couldn't save — validation refused the value or the write failed; shows a retry mark when onRetry is passed."
7193
+ }
7194
+ },
7195
+ "props": [
7196
+ {
7197
+ "name": "status",
7198
+ "type": "'saved' | 'saving' | 'unsaved' | 'refused'",
7199
+ "description": "Where the value stands — pass useAutosave(...).status."
7200
+ },
7201
+ {
7202
+ "name": "onRetry",
7203
+ "type": "() => void",
7204
+ "description": "Shows a retry mark while status is refused. Pass useAutosave(...).retry."
7205
+ },
7206
+ {
7207
+ "name": "labels",
7208
+ "type": "Partial<Record<'saved' | 'saving' | 'unsaved' | 'refused', string>>",
7209
+ "description": "Override any of the four labels, e.g. to translate them."
7210
+ },
7211
+ {
7212
+ "name": "retryLabel",
7213
+ "type": "string",
7214
+ "default": "\"Retry saving\"",
7215
+ "description": "Accessible name of the retry mark."
7216
+ }
7217
+ ],
7218
+ "dependencies": [
7219
+ "@phosphor-icons/react",
7220
+ "@loworbitstudio/visor-core"
7221
+ ],
7222
+ "tokens_used": [
7223
+ "--border-focus",
7224
+ "--button-icon-ghost-color",
7225
+ "--button-icon-pad",
7226
+ "--button-icon-size",
7227
+ "--focus-ring-offset",
7228
+ "--focus-ring-width",
7229
+ "--font-size-xs",
7230
+ "--motion-duration-150",
7231
+ "--motion-easing-default",
7232
+ "--radius-sm",
7233
+ "--save-status-font-family",
7234
+ "--save-status-letter-spacing",
7235
+ "--save-status-text-transform",
7236
+ "--save-status-width",
7237
+ "--spacing-1",
7238
+ "--spacing-2",
7239
+ "--text-error",
7240
+ "--text-primary",
7241
+ "--text-secondary",
7242
+ "--text-tertiary"
7243
+ ],
7244
+ "example": "const autosave = useAutosave(bio, saveBio)\n\n<SaveStatus status={autosave.status} onRetry={autosave.retry} />\n"
7245
+ },
7036
7246
  "score-indicator": {
7037
7247
  "category": "admin",
7038
7248
  "description": "Compact circular ring visualization for percentage or ratio metrics — health score, uptime, engagement, capacity.",
@@ -7595,6 +7805,10 @@
7595
7805
  "--border-error",
7596
7806
  "--border-focus",
7597
7807
  "--border-strong",
7808
+ "--control-edge-color",
7809
+ "--control-edge-style",
7810
+ "--control-edge-width",
7811
+ "--control-state-edge-width",
7598
7812
  "--field-control-bg",
7599
7813
  "--field-menu-bg",
7600
7814
  "--focus-ring-width",
@@ -8445,11 +8659,12 @@
8445
8659
  },
8446
8660
  "spinner": {
8447
8661
  "category": "data-display",
8448
- "description": "Inline loading spinner — a rotating border ring with a subtle track and a tone-colored leading edge. Three sizes (xs/sm/md), two tones (default/primary), and full reduced-motion support.",
8662
+ "description": "Loading spinner — an inline rotating border ring, or a dotted thinking-orb (nine animations) for waits over larger surfaces. Three sizes (xs/sm/md), two tones (default/primary), and full reduced-motion support.",
8449
8663
  "when_to_use": [
8450
8664
  "Indicating an async operation is in progress within a button, form field, or inline context",
8451
8665
  "Replacing text or an icon while content is loading (inline, non-blocking)",
8452
- "Decorative loading affordance inside a card or panel where a full-page skeleton would be too heavy"
8666
+ "Decorative loading affordance inside a card or panel where a full-page skeleton would be too heavy",
8667
+ "A wait over a large visual (a card re-rendering under a scrim, an agent thinking) — use variant=\"orb\" at md (64px)"
8453
8668
  ],
8454
8669
  "when_not_to_use": [
8455
8670
  "Full-page or full-section loading states (use Skeleton instead)",
@@ -8457,6 +8672,10 @@
8457
8672
  "Indicating indeterminate progress for a long-running task visible to the user (use a labeled progress bar)"
8458
8673
  ],
8459
8674
  "variants": {
8675
+ "variant": [
8676
+ "ring",
8677
+ "orb"
8678
+ ],
8460
8679
  "size": [
8461
8680
  "xs",
8462
8681
  "sm",
@@ -8472,13 +8691,25 @@
8472
8691
  "name": "size",
8473
8692
  "type": "\"xs\" | \"sm\" | \"md\"",
8474
8693
  "default": "\"md\"",
8475
- "description": "Ring size. xs=12px, sm=16px, md=24px."
8694
+ "description": "Size. Ring — xs=12px, sm=16px, md=24px. Orb — xs=20px, sm=32px, md=64px (20 and 64 are hand-tuned designs; 32 is interpolated)."
8476
8695
  },
8477
8696
  {
8478
8697
  "name": "tone",
8479
8698
  "type": "\"default\" | \"primary\"",
8480
8699
  "default": "\"default\"",
8481
- "description": "Color treatment for the leading edge. default=text-tertiary, primary=primary brand token."
8700
+ "description": "Color treatment. Ring leading edge — default=text-tertiary, primary=primary brand token. Orb ink — default=the surrounding text colour, primary=primary brand token."
8701
+ },
8702
+ {
8703
+ "name": "variant",
8704
+ "type": "\"ring\" | \"orb\"",
8705
+ "default": "\"ring\"",
8706
+ "description": "ring is the inline rotating border. orb is a dotted thinking-orb canvas (thinking-orbs) for waits over larger surfaces."
8707
+ },
8708
+ {
8709
+ "name": "orb",
8710
+ "type": "\"working\" | \"searching\" | \"solving\" | \"listening\" | \"connecting\" | \"weaving\" | \"composing\" | \"breathing\" | \"shaping\"",
8711
+ "default": "\"working\"",
8712
+ "description": "Which orb animation to show. Read only when variant=\"orb\"."
8482
8713
  },
8483
8714
  {
8484
8715
  "name": "label",
@@ -8488,6 +8719,7 @@
8488
8719
  ],
8489
8720
  "dependencies": [
8490
8721
  "class-variance-authority",
8722
+ "thinking-orbs",
8491
8723
  "@loworbitstudio/visor-core"
8492
8724
  ],
8493
8725
  "tokens_used": [
@@ -8512,7 +8744,7 @@
8512
8744
  "--stroke-width-thin",
8513
8745
  "--text-tertiary"
8514
8746
  ],
8515
- "example": "<Spinner />\n<Spinner size=\"xs\" tone=\"primary\" />\n<Spinner size=\"sm\" label=\"Loading results…\" />\n"
8747
+ "example": "<Spinner />\n<Spinner size=\"xs\" tone=\"primary\" />\n<Spinner size=\"sm\" label=\"Loading results…\" />\n<Spinner variant=\"orb\" orb=\"working\" label=\"Rendering preview…\" />\n"
8516
8748
  },
8517
8749
  "stack": {
8518
8750
  "category": "layout",
@@ -9399,10 +9631,16 @@
9399
9631
  "@radix-ui/react-switch"
9400
9632
  ],
9401
9633
  "tokens_used": [
9634
+ "--_switch-knob-size",
9635
+ "--_switch-knob-start",
9636
+ "--_switch-pad",
9402
9637
  "--border-default",
9403
9638
  "--border-error",
9404
9639
  "--border-focus",
9405
9640
  "--border-strong",
9641
+ "--control-edge-style",
9642
+ "--control-edge-width",
9643
+ "--control-state-edge-width",
9406
9644
  "--focus-ring-width",
9407
9645
  "--interactive-primary-bg",
9408
9646
  "--motion-duration-150",
@@ -9410,7 +9648,11 @@
9410
9648
  "--primary",
9411
9649
  "--shadow-sm",
9412
9650
  "--surface-page",
9413
- "--switch-track-bg"
9651
+ "--switch-edge-color",
9652
+ "--switch-knob-inset",
9653
+ "--switch-track-bg",
9654
+ "--switch-track-height",
9655
+ "--switch-track-width"
9414
9656
  ],
9415
9657
  "example": "<div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <Switch id=\"notifications\" />\n <Label htmlFor=\"notifications\">Enable notifications</Label>\n</div>\n"
9416
9658
  },
@@ -9686,6 +9928,10 @@
9686
9928
  "--border-default",
9687
9929
  "--border-focus",
9688
9930
  "--border-strong",
9931
+ "--control-edge-color",
9932
+ "--control-edge-style",
9933
+ "--control-edge-width",
9934
+ "--control-state-edge-width",
9689
9935
  "--focus-ring-width",
9690
9936
  "--font-size-sm",
9691
9937
  "--font-size-xs",
@@ -9736,6 +9982,7 @@
9736
9982
  "primary",
9737
9983
  "secondary",
9738
9984
  "tertiary",
9985
+ "warning",
9739
9986
  "inherit"
9740
9987
  ],
9741
9988
  "leading": [
@@ -9765,7 +10012,7 @@
9765
10012
  },
9766
10013
  {
9767
10014
  "name": "color",
9768
- "type": "'primary' | 'secondary' | 'tertiary' | 'inherit'",
10015
+ "type": "'primary' | 'secondary' | 'tertiary' | 'warning' | 'inherit'",
9769
10016
  "default": "'primary'"
9770
10017
  },
9771
10018
  {
@@ -9793,9 +10040,25 @@
9793
10040
  "--line-height-relaxed",
9794
10041
  "--line-height-snug",
9795
10042
  "--line-height-tight",
10043
+ "--text-lg-font-size",
10044
+ "--text-lg-letter-spacing",
10045
+ "--text-lg-text-transform",
10046
+ "--text-md-font-size",
10047
+ "--text-md-letter-spacing",
10048
+ "--text-md-text-transform",
9796
10049
  "--text-primary",
9797
10050
  "--text-secondary",
9798
- "--text-tertiary"
10051
+ "--text-sm-font-size",
10052
+ "--text-sm-letter-spacing",
10053
+ "--text-sm-text-transform",
10054
+ "--text-tertiary",
10055
+ "--text-warning",
10056
+ "--text-xl-font-size",
10057
+ "--text-xl-letter-spacing",
10058
+ "--text-xl-text-transform",
10059
+ "--text-xs-font-size",
10060
+ "--text-xs-letter-spacing",
10061
+ "--text-xs-text-transform"
9799
10062
  ],
9800
10063
  "example": "<Text>Default body text paragraph.</Text>\n\n<Text size=\"sm\" color=\"secondary\">\n Helper text or caption.\n</Text>\n\n<Text as=\"span\" weight=\"semibold\" size=\"lg\">\n Inline emphasized text\n</Text>\n"
9801
10064
  },
@@ -9841,6 +10104,10 @@
9841
10104
  "--border-error",
9842
10105
  "--border-focus",
9843
10106
  "--border-strong",
10107
+ "--control-edge-color",
10108
+ "--control-edge-style",
10109
+ "--control-edge-width",
10110
+ "--control-state-edge-width",
9844
10111
  "--focus-ring-width",
9845
10112
  "--font-size-base",
9846
10113
  "--font-size-sm",
@@ -10013,6 +10280,74 @@
10013
10280
  ],
10014
10281
  "example": "<Timeline>\n <TimelineItem status=\"complete\">\n <TimelineContent>\n <TimelineTitle>Order placed</TimelineTitle>\n <TimelineTimestamp dateTime=\"2024-01-15\">Jan 15</TimelineTimestamp>\n </TimelineContent>\n </TimelineItem>\n <TimelineItem status=\"active\">\n <TimelineContent>\n <TimelineTitle>In transit</TimelineTitle>\n <TimelineDescription>Package is on the way.</TimelineDescription>\n </TimelineContent>\n </TimelineItem>\n <TimelineItem status=\"upcoming\">\n <TimelineContent>\n <TimelineTitle>Delivered</TimelineTitle>\n </TimelineContent>\n </TimelineItem>\n</Timeline>\n"
10015
10282
  },
10283
+ "tin-input": {
10284
+ "category": "form",
10285
+ "description": "A write-only, masked 9-digit TIN/EIN input with a last-four echo that never re-displays the value.",
10286
+ "when_to_use": [
10287
+ "Collecting a Social Security number or an EIN (a W-9, a payout or tax form)",
10288
+ "Any 9-digit taxpayer ID that is stored server-side and must never be shown back",
10289
+ "Showing a TIN already on file by its last four, with a way to replace it"
10290
+ ],
10291
+ "when_not_to_use": [
10292
+ "Passwords (use PasswordInput — it has a show/hide toggle, which a TIN must not)",
10293
+ "Verification codes (use OTPInput)",
10294
+ "Numbers the user needs to read back or edit in place (use Input or NumberInput)"
10295
+ ],
10296
+ "props": [
10297
+ {
10298
+ "name": "kind",
10299
+ "type": "'ssn' | 'ein'",
10300
+ "required": true,
10301
+ "description": "Display grouping — SSN"
10302
+ },
10303
+ {
10304
+ "name": "onValueChange",
10305
+ "type": "(digits: string | null) => void",
10306
+ "required": true,
10307
+ "description": "Called with the 9 digits once the entry is complete, and with null while it is incomplete or cleared."
10308
+ },
10309
+ {
10310
+ "name": "lastFour",
10311
+ "type": "string",
10312
+ "description": "Last four digits of a TIN already on file. Renders a read-only \"On file · ending 1234\" with a Replace button. A new value brings the echo back after a Replace. Only ever pass four digits."
10313
+ },
10314
+ {
10315
+ "name": "onReplace",
10316
+ "type": "() => void",
10317
+ "description": "Called when Replace swaps the on-file echo for an empty field."
10318
+ },
10319
+ {
10320
+ "name": "error",
10321
+ "type": "string",
10322
+ "description": "Error message, rendered as a FieldError and bound by aria-describedby; sets aria-invalid."
10323
+ },
10324
+ {
10325
+ "name": "size",
10326
+ "type": "'sm' | 'md' | 'lg'",
10327
+ "default": "md",
10328
+ "description": "Size of the field and the Replace button. lg meets 44px touch targets."
10329
+ },
10330
+ {
10331
+ "name": "disabled",
10332
+ "type": "boolean",
10333
+ "default": "false",
10334
+ "description": "Disables the field and the Replace button."
10335
+ },
10336
+ {
10337
+ "name": "id",
10338
+ "type": "string",
10339
+ "description": "Id for the input, so a FieldLabel htmlFor associates the label."
10340
+ }
10341
+ ],
10342
+ "dependencies": [
10343
+ "@loworbitstudio/visor-core"
10344
+ ],
10345
+ "tokens_used": [
10346
+ "--spacing-2",
10347
+ "--text-secondary"
10348
+ ],
10349
+ "example": "<Field>\n <FieldLabel htmlFor=\"tin\">Social Security number</FieldLabel>\n <TinInput id=\"tin\" kind=\"ssn\" onValueChange={setTin} />\n</Field>\n\n<TinInput id=\"tin\" kind=\"ein\" lastFour=\"6789\" onValueChange={setTin} />\n"
10350
+ },
10016
10351
  "toast": {
10017
10352
  "category": "feedback",
10018
10353
  "description": "Ephemeral notification messages that appear and auto-dismiss.",
@@ -12029,6 +12364,68 @@
12029
12364
  }
12030
12365
  ]
12031
12366
  },
12367
+ "use-autosave": {
12368
+ "description": "Autosave for one value: debounces the write (600ms), runs one write at a time so the newest value wins, flushes a pending change on pagehide, tab hide and unmount, and reports saved · saving · unsaved · refused with retry().",
12369
+ "params": [
12370
+ {
12371
+ "name": "value",
12372
+ "type": "T",
12373
+ "required": true,
12374
+ "description": "The value to keep saved. The first render's value counts as saved and is not written."
12375
+ },
12376
+ {
12377
+ "name": "write",
12378
+ "type": "(value: T, context: { leaving: boolean }) => unknown",
12379
+ "required": true,
12380
+ "description": "Persists the value. Return a promise; a rejection (or a throw) puts the hook in refused. `leaving` is true when the page is being left, so use `keepalive` or `sendBeacon`."
12381
+ },
12382
+ {
12383
+ "name": "options.delay",
12384
+ "type": "number",
12385
+ "required": false,
12386
+ "description": "Debounce in milliseconds after the last change. Defaults to 600."
12387
+ },
12388
+ {
12389
+ "name": "options.validate",
12390
+ "type": "(value: T) => boolean",
12391
+ "required": false,
12392
+ "description": "Return false to refuse a value. A refused value is never written, stays in the control, and puts the hook in refused."
12393
+ },
12394
+ {
12395
+ "name": "options.isEqual",
12396
+ "type": "(a: T, b: T) => boolean",
12397
+ "required": false,
12398
+ "description": "Decides whether a value is already saved. Defaults to Object.is; pass one for object values."
12399
+ }
12400
+ ],
12401
+ "returns": [
12402
+ {
12403
+ "name": "status",
12404
+ "type": "'saved' | 'saving' | 'unsaved' | 'refused'",
12405
+ "description": "Where the value stands. Pass it to SaveStatus."
12406
+ },
12407
+ {
12408
+ "name": "reason",
12409
+ "type": "'invalid' | 'failed' | null",
12410
+ "description": "Why the hook is refused — validate rejected the value, or write failed."
12411
+ },
12412
+ {
12413
+ "name": "error",
12414
+ "type": "unknown",
12415
+ "description": "What write threw or rejected with, while reason is failed."
12416
+ },
12417
+ {
12418
+ "name": "retry",
12419
+ "type": "() => void",
12420
+ "description": "Write the current value now."
12421
+ },
12422
+ {
12423
+ "name": "flush",
12424
+ "type": "() => void",
12425
+ "description": "Write a pending change now instead of waiting for the debounce."
12426
+ }
12427
+ ]
12428
+ },
12032
12429
  "use-click-outside": {
12033
12430
  "description": "Fires a callback when a click or touch event occurs outside the referenced element. Useful for closing dropdowns and popovers.",
12034
12431
  "params": [
@@ -12721,6 +13118,7 @@
12721
13118
  "offline-banner",
12722
13119
  "popover",
12723
13120
  "progress",
13121
+ "save-status",
12724
13122
  "skeleton",
12725
13123
  "slow-network-bar",
12726
13124
  "success-feedback",
@@ -12768,6 +13166,7 @@
12768
13166
  "file-upload",
12769
13167
  "form",
12770
13168
  "form-error",
13169
+ "inline-edit",
12771
13170
  "input",
12772
13171
  "label",
12773
13172
  "number-input",
@@ -12783,6 +13182,7 @@
12783
13182
  "switch",
12784
13183
  "tag-input",
12785
13184
  "textarea",
13185
+ "tin-input",
12786
13186
  "toggle-group"
12787
13187
  ],
12788
13188
  "media": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor",
3
- "version": "1.26.2",
3
+ "version": "1.28.0",
4
4
  "description": "CLI for the Visor design system — add components, hooks, and utilities to your project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@aws-sdk/client-s3": "^3.1021.0",
48
48
  "@babel/parser": "^7.26.0",
49
- "@loworbitstudio/visor-theme-engine": "^0.21.0",
49
+ "@loworbitstudio/visor-theme-engine": "^0.22.0",
50
50
  "commander": "^13.1.0",
51
51
  "diff": "^8.0.4",
52
52
  "picocolors": "^1.1.1",