@spfx-fast/sharepoint-ui 0.4.1 → 0.4.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.
Files changed (57) hide show
  1. package/AGENTS.md +23 -3
  2. package/QUICKSTART.md +133 -0
  3. package/README.md +21 -878
  4. package/components.json +178 -33
  5. package/dist/appearance.js +4 -4
  6. package/dist/elements/alert.js +26 -2
  7. package/dist/elements/avatar.js +1 -1
  8. package/dist/elements/button.js +1 -1
  9. package/dist/elements/carousel.js +5 -2
  10. package/dist/elements/color-picker.js +1 -1
  11. package/dist/elements/combobox.js +5 -5
  12. package/dist/elements/data-grid-model.d.ts +3 -0
  13. package/dist/elements/data-grid.d.ts +2 -1
  14. package/dist/elements/data-grid.js +19 -10
  15. package/dist/elements/date-picker.js +1 -1
  16. package/dist/elements/dialog.js +1 -1
  17. package/dist/elements/file-upload.js +1 -1
  18. package/dist/elements/floating-position.d.ts +10 -0
  19. package/dist/elements/floating-position.js +53 -0
  20. package/dist/elements/info.js +1 -1
  21. package/dist/elements/input.js +2 -2
  22. package/dist/elements/link.js +1 -1
  23. package/dist/elements/message-bar.js +33 -5
  24. package/dist/elements/nav.js +4 -1
  25. package/dist/elements/overflow.d.ts +1 -0
  26. package/dist/elements/overflow.js +2 -2
  27. package/dist/elements/popover.js +1 -1
  28. package/dist/elements/rating.js +3 -3
  29. package/dist/elements/rich-text-editor.d.ts +15 -1
  30. package/dist/elements/rich-text-editor.js +938 -79
  31. package/dist/elements/search.js +3 -10
  32. package/dist/elements/select.js +2 -2
  33. package/dist/elements/shared.d.ts +4 -1
  34. package/dist/elements/shared.js +14 -1
  35. package/dist/elements/sharepoint-field-basic-controls.d.ts +1 -0
  36. package/dist/elements/sharepoint-field-basic-controls.js +14 -0
  37. package/dist/elements/sharepoint-fields.d.ts +29 -0
  38. package/dist/elements/sharepoint-fields.js +460 -0
  39. package/dist/elements/spin-button.js +2 -2
  40. package/dist/elements/tag-picker.js +29 -11
  41. package/dist/elements/textarea.js +2 -2
  42. package/dist/elements/toaster.js +2 -1
  43. package/dist/elements/tooltip.js +18 -1
  44. package/dist/elements/tree.js +1 -1
  45. package/dist/elements.d.ts +3 -2
  46. package/dist/elements.js +4 -1
  47. package/dist/index.d.ts +6 -4
  48. package/dist/index.js +4 -2
  49. package/dist/react.d.ts +188 -19
  50. package/dist/react.js +321 -61
  51. package/dist/sharepoint-fields.d.ts +135 -0
  52. package/dist/sharepoint-fields.js +402 -0
  53. package/dist/theme.d.ts +9 -1
  54. package/dist/theme.js +78 -1
  55. package/package.json +11 -2
  56. package/skills/sharepoint-ui/SKILL.md +1 -1
  57. package/skills/sharepoint-ui/references/sharepoint-patterns.md +1 -1
package/components.json CHANGED
@@ -340,15 +340,39 @@
340
340
  "host className/style"
341
341
  ],
342
342
  "reactSignature": "{ children, selectedValue, defaultSelectedValue, selectedValues, defaultSelectedValues, selectionMode = \"single\", layout = \"grid\", size = \"medium\", shape = \"square\", spacing = \"medium\", focusMode = \"arrow\", columns = 6, onSelectionChange, ...props }: SwatchPickerProps",
343
- "reactRefType": "HTMLElement"
343
+ "reactRefType": "HTMLElement",
344
+ "relatedReactApis": [
345
+ {
346
+ "name": "useSavedSwatchPalettes(store)",
347
+ "description": "Load, save, remove, and refresh named palettes through a stable host-owned store. Use the app's SharePoint data service for tenant-wide persistence; the hook adds no network or storage dependency. See the saved palettes guide for a complete React recipe."
348
+ },
349
+ {
350
+ "name": "SwatchPaletteStore",
351
+ "description": "Implement list, save, and remove with the app's current persistence service. Keep the store object stable across React renders."
352
+ },
353
+ {
354
+ "name": "SavedSwatchPalette",
355
+ "description": "A named palette record with an id and color entries containing value, color, and optional label."
356
+ },
357
+ {
358
+ "name": "SharePointThemeSwatches",
359
+ "description": "Compose inside SwatchPicker to render every color in the nearest SharePointUIProvider theme palette. Pass palette to override the provider palette, or labelForColor to localize accessible color names."
360
+ },
361
+ {
362
+ "name": "getSharePointThemeColorPresets(palette, labelForKey?)",
363
+ "description": "Framework-neutral helper that maps every non-empty SharePoint palette entry, including custom tenant keys, to named swatch data."
364
+ },
365
+ {
366
+ "name": "SharePointThemeColorPreset",
367
+ "description": "A named palette key, controlled value, CSS color, and accessible label."
368
+ }
369
+ ]
344
370
  },
345
371
  "react": "SwatchPicker",
346
372
  "tag": "spui-swatch-picker",
347
373
  "define": "defineSwatchPicker",
348
374
  "summary": "Render accessible color and image swatches as a keyboard-operable single- or multi-selection grid or row.",
349
- "highValueAdditions": [
350
- "Offer reusable named theme-color presets alongside caller-defined palettes."
351
- ]
375
+ "highValueAdditions": []
352
376
  },
353
377
  {
354
378
  "module": "swatch-picker",
@@ -449,7 +473,13 @@
449
473
  "module": "overflow",
450
474
  "category": "actions",
451
475
  "api": {
452
- "reactSignature": "{ children, label, minimumVisible = 0, overflowDirection = \"end\", overflowAxis = \"horizontal\", hasHiddenItems = false, overflowContent, padding = 10, onOverflowChange, onOverflowItemSelect, ...props }: ElementProps & { children?: React.ReactNode; label?: string; minimumVisible?: number; overflowDirection?: \"start\" | \"end\"; overflowAxis?: OverflowAxis; hasHiddenItems?: boolean; overflowContent?: React.ReactNode; padding?: number; onOverflowChange?: (event: null, data: OverflowState) => void; onOverflowItemSelect?: (id: string) => void }",
476
+ "reactSignature": "{ children, label, minimumVisible = 0, overflowDirection = \"end\", overflowAxis = \"horizontal\", density = \"comfortable\", hasHiddenItems = false, overflowContent, padding = 10, onOverflowChange, onOverflowItemSelect, ...props }: ElementProps & { children?: React.ReactNode; label?: string; minimumVisible?: number; overflowDirection?: \"start\" | \"end\"; overflowAxis?: OverflowAxis; density?: OverflowDensity; hasHiddenItems?: boolean; overflowContent?: React.ReactNode; padding?: number; onOverflowChange?: (event: null, data: OverflowState) => void; onOverflowItemSelect?: (id: string) => void }",
477
+ "relatedReactApis": [
478
+ {
479
+ "name": "useStoredOverflowCompactMode(storageKey, defaultCompact?)",
480
+ "description": "Persist this browser's compact command-shelf choice; use the returned compact value as Overflow density and setCompact or toggleCompact from a ToggleButton. Scope storageKey to the app and SharePoint site. Custom-element consumers can set density=compact directly and persist it with their own preference store."
481
+ }
482
+ ],
453
483
  "callbackProps": [
454
484
  "onOverflowChange",
455
485
  "onOverflowItemSelect"
@@ -465,10 +495,8 @@
465
495
  "named ::part hooks"
466
496
  ]
467
497
  },
468
- "summary": "Keep priority actions visible as space changes and render the overflowed actions through the existing Menu primitives without cloning or moving controls.",
469
- "highValueAdditions": [
470
- "Add a priority-aware responsive command shelf with group labels and persisted compact mode."
471
- ]
498
+ "summary": "Keep priority actions visible as space changes, move the rest into a menu, and let a stored user preference switch the shelf to compact spacing.",
499
+ "highValueAdditions": []
472
500
  },
473
501
  {
474
502
  "react": "OverflowProvider",
@@ -586,7 +614,8 @@
586
614
  "motion accepts slide attraction physics: { kind: \"slide\", duration?: number }; duration follows Embla’s 20–60 recommendation"
587
615
  ],
588
616
  "highValueAdditions": [
589
- "Enable drag with keyboard-equivalent announcements and preserve end padding when slides are centered."
617
+ "Enable drag with keyboard-equivalent announcements and preserve end padding when slides are centered.",
618
+ "Use CarouselNav appearance=thumbnails with CarouselNavImageButton for a horizontally scrollable image-preview strip."
590
619
  ],
591
620
  "reactRefType": "HTMLElement"
592
621
  },
@@ -918,8 +947,11 @@
918
947
  "default",
919
948
  "leading",
920
949
  "eyebrow",
950
+ "title",
951
+ "subtitle",
952
+ "description",
921
953
  "status",
922
- "footer"
954
+ "actions"
923
955
  ],
924
956
  "styling": [
925
957
  "SharePointUIProvider tokens",
@@ -1413,6 +1445,7 @@
1413
1445
  "note": "React Field composes a single native or custom form control. createFormField returns a light-DOM wrapper for framework-neutral consumers.",
1414
1446
  "api": {
1415
1447
  "reactSignature": "{ children, label, hint, validationMessage, validationMessageIcon, validationState, required, disabled, orientation = \"vertical\", size = \"medium\", className, style, ...props }: FieldProps",
1448
+ "reactRefType": "HTMLDivElement",
1416
1449
  "callbackProps": [],
1417
1450
  "slots": [
1418
1451
  "label",
@@ -1451,6 +1484,37 @@
1451
1484
  },
1452
1485
  "summary": "Edit a single-line value with an accessible label and controlled change events."
1453
1486
  },
1487
+ {
1488
+ "react": "SharePointField",
1489
+ "tag": "spui-sharepoint-field",
1490
+ "define": "defineSharePointField",
1491
+ "module": "sharepoint-fields",
1492
+ "category": "forms",
1493
+ "summary": "Render SharePoint field metadata with accessible controls in React or as a framework-free custom element.",
1494
+ "api": {
1495
+ "reactSignature": "{ field, value, defaultValue, options: providedOptions, onValueChange, disabled = false, hint, validationMessage, renderUnsupported, addChoiceLabel = \"Add another choice\", emptyOptionLabel = \"Select an option\", className, style, ...hostProps }: SharePointFieldProps",
1496
+ "callbackProps": [
1497
+ "onValueChange"
1498
+ ],
1499
+ "slots": [],
1500
+ "styling": [
1501
+ "SharePointUIProvider tokens",
1502
+ "Field label, hint, and validation states",
1503
+ "id, ARIA, data attributes, className, and style"
1504
+ ],
1505
+ "customElementProperties": [
1506
+ "field",
1507
+ "value",
1508
+ "defaultValue",
1509
+ "options",
1510
+ "onValueChange",
1511
+ "renderUnsupported"
1512
+ ],
1513
+ "customElementEvents": [
1514
+ "spui-field-value-change"
1515
+ ]
1516
+ }
1517
+ },
1454
1518
  {
1455
1519
  "react": "SearchBox",
1456
1520
  "tag": "spui-search-box",
@@ -1486,7 +1550,7 @@
1486
1550
  "module": "text",
1487
1551
  "category": "typography",
1488
1552
  "api": {
1489
- "reactSignature": "{ as = \"span\", size = 300, weight, font = \"base\", align = \"start\", italic = false, underline = false, strikethrough = false, truncate = false, wrap = true, block = false, children, ...props }: TextProps",
1553
+ "reactSignature": "{ as = \"span\", size = 300, weight, font = \"base\", align = \"start\", italic = false, underline = false, strikethrough = false, truncate = false, wrap = true, block = false, children, style, ...props }: TextProps",
1490
1554
  "callbackProps": [],
1491
1555
  "slots": [
1492
1556
  "default"
@@ -1816,7 +1880,7 @@
1816
1880
  "registered": false,
1817
1881
  "note": "Uses a native HTML label to preserve browser label association and click-to-focus behavior; createLabel returns an HTMLLabelElement for non-React consumers.",
1818
1882
  "api": {
1819
- "reactSignature": "{ htmlFor, size = \"medium\", weight = \"regular\", required = false, disabled = false, children, style, ...props }: React.LabelHTMLAttributes<HTMLLabelElement> & { size?: LabelSize; weight?: LabelWeight; required?: LabelRequired; disabled?: boolean }",
1883
+ "reactSignature": "{ htmlFor, size = \"medium\", weight = \"regular\", required = false, disabled = false, children, style, ...props }: LabelProps",
1820
1884
  "reactRefType": "HTMLLabelElement",
1821
1885
  "callbackProps": [],
1822
1886
  "slots": [
@@ -1975,6 +2039,10 @@
1975
2039
  "api": {
1976
2040
  "reactSignature": "inputProps: ComboBoxProps",
1977
2041
  "relatedReactApis": [
2042
+ {
2043
+ "name": "ComboBoxProps.requiredMessage",
2044
+ "description": "Sets the native form validation message; defaults to SharePointUIProvider.strings.required."
2045
+ },
1978
2046
  {
1979
2047
  "name": "ComboBoxProps.selectedLabel",
1980
2048
  "description": "In multiselect mode, names the selected-options group; defaults to SharePointUIProvider.strings.selectedOptionsLabel."
@@ -2239,14 +2307,69 @@
2239
2307
  "api": {
2240
2308
  "reactSignature": "inputProps: RichTextEditorProps",
2241
2309
  "relatedReactApis": [
2310
+ {
2311
+ "name": "RichTextEditorProps.field",
2312
+ "description": "Pass SharePoint Note metadata directly; the editor derives its capability ceiling. contentMode and allowHyperlink may narrow that ceiling, but cannot enable features the field forbids."
2313
+ },
2314
+ {
2315
+ "name": "RichTextEditorProps.contentMode",
2316
+ "description": "Choose plain, restricted, rich, or sanitized html output. With field metadata, this prop may narrow but cannot exceed the destination field's capabilities. Omit it for field-derived mode or unconstrained sanitized html."
2317
+ },
2318
+ {
2319
+ "name": "getRichTextEditorFieldOptions(field)",
2320
+ "description": "Maps SharePoint RichText, RestrictedMode, RichTextMode, and AllowHyperlink metadata to editor capabilities. Explicit restrictions are preserved; missing restrictions default to sanitized html. React consumers can pass field={metadata} directly."
2321
+ },
2242
2322
  {
2243
2323
  "name": "RichTextEditorProps.editorLabel",
2244
2324
  "description": "Accessible name used when no aria-label or visible label is supplied; defaults to SharePointUIProvider.strings.richTextEditorLabel."
2325
+ },
2326
+ {
2327
+ "name": "RichTextEditorProps color labels",
2328
+ "description": "SharePointUIProvider.strings.richTextTextColor, richTextHighlightColor, richTextCustomColor, richTextThemeAccent, richTextFontFamily, richTextFontSize, and colorSwatch localize color and font controls and their palette labels. These are available for compatible-rich and FullHtml fields; restricted/plain fields hide them. Safe inline font/color styles are sanitized before values are emitted."
2329
+ },
2330
+ {
2331
+ "name": "RichTextEditorProps.onValueChange",
2332
+ "description": "Receives sanitized HTML after edits. With a controlled value prop, update that prop from this callback; the editor preserves its live selection while React applies the committed value."
2333
+ },
2334
+ {
2335
+ "name": "RichTextEditorProps.onImageUpload",
2336
+ "description": "Upload the selected file and return its URL string, or return { src, alt } to provide meaningful image alternative text. The object form requires alt; use alt: \"\" only for decorative images. String results use filename-based alternative text."
2337
+ },
2338
+ {
2339
+ "name": "RichTextEditor mobile selection",
2340
+ "description": "On touch devices, press and hold text to use the browser's native selection handles. The editor does not force blur or refocus during the gesture; toolbar formatting uses the saved range."
2341
+ },
2342
+ {
2343
+ "name": "RichTextEditorProps.onImageUploadError",
2344
+ "description": "Receives the failed image filename and error when the application-managed upload fails or returns an unsafe URL."
2345
+ },
2346
+ {
2347
+ "name": "RichTextEditorProps.linkPicker",
2348
+ "description": "Opens the host application's SharePoint picker with the current link, then fills the editor for review before the user applies it. Return null to cancel."
2349
+ },
2350
+ {
2351
+ "name": "RichTextEditorProps.onLinkPickerError",
2352
+ "description": "Receives an error when the host-provided SharePoint link picker fails."
2353
+ },
2354
+ {
2355
+ "name": "SharePointUIProvider.strings.richTextLink*",
2356
+ "description": "Customize the link editor's accessible name, field labels, action labels, picker states, and validation messages for the active language."
2357
+ },
2358
+ {
2359
+ "name": "SharePointUIProvider.strings.richText*",
2360
+ "description": "Localize the text-style menu, block names, formatting buttons, table actions, link editor, image controls, and required message."
2361
+ },
2362
+ {
2363
+ "name": "RichTextEditor full HTML formatting",
2364
+ "description": "Subscript, superscript, and left/center/right/justified paragraph alignment are available only when the effective contentMode is html. Localize them with SharePointUIProvider.strings.richTextSubscript, richTextSuperscript, richTextAlignLeft, richTextAlignCenter, richTextAlignRight, and richTextAlignJustify."
2245
2365
  }
2246
2366
  ],
2247
2367
  "callbackProps": [
2248
2368
  "onValueChange",
2249
- "onImageUpload"
2369
+ "onImageUpload",
2370
+ "onImageUploadError",
2371
+ "linkPicker",
2372
+ "onLinkPickerError"
2250
2373
  ],
2251
2374
  "slots": [],
2252
2375
  "styling": [
@@ -2257,7 +2380,10 @@
2257
2380
  ],
2258
2381
  "reactRefType": "HTMLElement"
2259
2382
  },
2260
- "summary": "Edit sanitized rich HTML with controlled or uncontrolled form values, accessible formatting history, table editing, and application-managed durable image upload for SharePoint persistence."
2383
+ "summary": "Edit sanitized content with block styles, font family and size, text and highlight colors where the SharePoint field permits them, lists, tables, safe links, and host-managed images. Field metadata selects plain, restricted, compatible-rich, or FullHtml behavior.",
2384
+ "highValueAdditions": [
2385
+ "Preview SharePoint page titles and destination details before inserting internal links."
2386
+ ]
2261
2387
  },
2262
2388
  {
2263
2389
  "react": "Spinner",
@@ -2395,7 +2521,7 @@
2395
2521
  ],
2396
2522
  "reactRefType": "HTMLElement"
2397
2523
  },
2398
- "summary": "Group related messages with consistent spacing and optional entry motion."
2524
+ "summary": "Stack related status messages with optional entry, exit, and reflow motion."
2399
2525
  },
2400
2526
  {
2401
2527
  "react": "MessageBarTitle",
@@ -2674,7 +2800,8 @@
2674
2800
  "category": "feedback",
2675
2801
  "reactOnly": true,
2676
2802
  "api": {
2677
- "reactSignature": "{ children, ...props }: ElementProps & { children?: React.ReactNode }",
2803
+ "reactSignature": "{ children, ...props }: DrawerFooterProps",
2804
+ "reactRefType": "HTMLDivElement",
2678
2805
  "callbackProps": [],
2679
2806
  "slots": [
2680
2807
  "actions"
@@ -2694,7 +2821,8 @@
2694
2821
  "category": "feedback",
2695
2822
  "reactOnly": true,
2696
2823
  "api": {
2697
- "reactSignature": "{ children, ...props }: ElementProps & { children?: React.ReactNode }",
2824
+ "reactSignature": "{ children, ...props }: DrawerBodyProps",
2825
+ "reactRefType": "HTMLDivElement",
2698
2826
  "callbackProps": [],
2699
2827
  "slots": [
2700
2828
  "default"
@@ -2709,7 +2837,7 @@
2709
2837
  },
2710
2838
  {
2711
2839
  "react": "DrawerHeaderTitle",
2712
- "tag": "span",
2840
+ "tag": "fragment",
2713
2841
  "module": "drawer",
2714
2842
  "category": "feedback",
2715
2843
  "reactOnly": true,
@@ -2736,7 +2864,8 @@
2736
2864
  "category": "feedback",
2737
2865
  "reactOnly": true,
2738
2866
  "api": {
2739
- "reactSignature": "{ children, label, ...props }: ElementProps & { children?: React.ReactNode; label?: string }",
2867
+ "reactSignature": "{ children, label, ...props }: DrawerHeaderNavigationProps",
2868
+ "reactRefType": "HTMLElement",
2740
2869
  "callbackProps": [],
2741
2870
  "slots": [
2742
2871
  "default"
@@ -3145,6 +3274,7 @@
3145
3274
  "onMultiSortChange",
3146
3275
  "onFilterValueChange",
3147
3276
  "onCellEdit",
3277
+ "onCellEditError",
3148
3278
  "validateCellEdit",
3149
3279
  "getRowHeight",
3150
3280
  "onCellsEdit",
@@ -3162,7 +3292,7 @@
3162
3292
  "editing": {
3163
3293
  "columnOptions": "editable?: boolean; editType?: text | number | date",
3164
3294
  "activation": "Enter, F2, or double-click",
3165
- "commit": "Enter or focus leaving the editor; async onCellEdit errors keep the editor open",
3295
+ "commit": "Enter or focus leaving the editor; async onCellEdit errors keep the editor open and call onCellEditError",
3166
3296
  "cancel": "Escape",
3167
3297
  "validation": "validateCellEdit returns an accessible error message",
3168
3298
  "persistence": "Callback receives row id, column id, previous value, new value, and item. The host owns SharePoint persistence and should supply refreshed items after save."
@@ -3501,8 +3631,12 @@
3501
3631
  "module": "tag-picker",
3502
3632
  "category": "form",
3503
3633
  "api": {
3504
- "reactSignature": "{ children, label, selectedLabel, selectedOptions, defaultSelectedOptions, selectedValues, defaultSelectedValues, inputValue, open, defaultOpen, positioning, disableAutoFocus = false, inline = false, noPopover = false, disabled, onOpenChange, onOptionSelect, onInputValueChange, onChange, ...props }: TagPickerProps",
3634
+ "reactSignature": "{ children, label, selectedLabel, requiredMessage, selectedOptions, defaultSelectedOptions, selectedValues, defaultSelectedValues, inputValue, open, defaultOpen, positioning, disableAutoFocus = false, inline = false, noPopover = false, disabled, onOpenChange, onOptionSelect, onInputValueChange, onChange, ...props }: TagPickerProps",
3505
3635
  "relatedReactApis": [
3636
+ {
3637
+ "name": "TagPickerProps.requiredMessage",
3638
+ "description": "Sets the native form validation message; defaults to SharePointUIProvider.strings.required."
3639
+ },
3506
3640
  {
3507
3641
  "name": "TagPickerProps.selectedLabel",
3508
3642
  "description": "Names the selected-options group; defaults to SharePointUIProvider.strings.selectedOptionsLabel."
@@ -3513,7 +3647,7 @@
3513
3647
  },
3514
3648
  {
3515
3649
  "name": "TagPickerProps.positioning",
3516
- "description": "Positions the options surface relative to the control; supports placement shorthands, alignment, fallbacks, and offsets."
3650
+ "description": "Positions and sizes the options surface within viewport or clipping-ancestor boundaries; supports placement, alignment, fallbacks, offsets, and custom bounds."
3517
3651
  },
3518
3652
  {
3519
3653
  "name": "TagPickerProps.disableAutoFocus",
@@ -3538,10 +3672,9 @@
3538
3672
  ],
3539
3673
  "reactRefType": "HTMLElement"
3540
3674
  },
3541
- "summary": "Search, choose, and remove multiple values with keyboard and native form support.",
3675
+ "summary": "Search, choose, and remove multiple values with keyboard, native form support, and popup placement that adapts to SharePoint sections.",
3542
3676
  "highValueAdditions": [
3543
- "Add composed control, input, selected-tag group, and list primitives while preserving the single state engine.",
3544
- "Position the options surface against viewport and clipping ancestors, including SharePoint sections."
3677
+ "Add composed control, input, selected-tag group, and list primitives while preserving the single state engine."
3545
3678
  ]
3546
3679
  },
3547
3680
  {
@@ -3551,7 +3684,8 @@
3551
3684
  "reactOnly": true,
3552
3685
  "category": "form-trigger",
3553
3686
  "api": {
3554
- "reactSignature": "{ children, appearance = \"outline\", size = \"medium\", disabled = false, ...props }: ElementProps & { children?: React.ReactNode; appearance?: \"filled-darker\" | \"filled-lighter\" | \"outline\" | \"underline\"; size?: \"small\" | \"medium\" | \"large\"; disabled?: boolean }",
3687
+ "reactSignature": "{ children, appearance = \"outline\", size = \"medium\", disabled = false, ...props }: TagPickerButtonProps",
3688
+ "reactRefType": "HTMLButtonElement",
3555
3689
  "callbackProps": [],
3556
3690
  "slots": [
3557
3691
  "button"
@@ -3698,7 +3832,13 @@
3698
3832
  "module": "rating",
3699
3833
  "category": "forms",
3700
3834
  "api": {
3701
- "reactSignature": "{ value, defaultValue = 0, max = 5, step = 1, size = \"extra-large\", color = \"neutral\", label, itemLabel, iconFilled, iconOutline, fullValueInput, halfValueInput, name, disabled = false, readOnly = false, required = false, onChange, ...props }: RatingProps",
3835
+ "reactSignature": "{ value, defaultValue = 0, max = 5, step = 1, size = \"extra-large\", color = \"neutral\", label, requiredMessage, itemLabel, iconFilled, iconOutline, fullValueInput, halfValueInput, name, disabled = false, readOnly = false, required = false, onChange, ...props }: RatingProps",
3836
+ "relatedReactApis": [
3837
+ {
3838
+ "name": "RatingProps.requiredMessage",
3839
+ "description": "Sets the native form validation message; defaults to SharePointUIProvider.strings.required."
3840
+ }
3841
+ ],
3702
3842
  "callbackProps": [
3703
3843
  "itemLabel",
3704
3844
  "onChange"
@@ -4891,7 +5031,7 @@
4891
5031
  "module": "nav",
4892
5032
  "category": "navigation",
4893
5033
  "api": {
4894
- "reactSignature": "{ children, label = \"Navigation\", selectedValue, defaultSelectedValue, selectedCategoryValue, defaultSelectedCategoryValue, openCategories, defaultOpenCategories = [], open, defaultOpen, collapsed, defaultCollapsed, collapsible = false, multiple = true, density = \"medium\", openLabel, closeLabel, onItemSelect, onCategoryToggle, onNavItemSelect, onNavCategoryItemToggle, onOpenChange, onCollapsedChange, onClick, onClickCapture, onKeyDown, onKeyDownCapture, onKeyUp, onKeyUpCapture, onKeyPress, onKeyPressCapture, onFocus, onFocusCapture, onBlur, onBlurCapture, onAuxClick, onAuxClickCapture, onContextMenu, onContextMenuCapture, onDoubleClick, onDoubleClickCapture, onMouseDown, onMouseDownCapture, onMouseMove, onMouseMoveCapture, onMouseOut, onMouseOutCapture, onMouseOver, onMouseOverCapture, onMouseUp, onMouseUpCapture, onGotPointerCapture, onGotPointerCaptureCapture, onLostPointerCapture, onLostPointerCaptureCapture, onPointerCancel, onPointerCancelCapture, onPointerDown, onPointerDownCapture, onPointerMove, onPointerMoveCapture, onPointerOut, onPointerOutCapture, onPointerOver, onPointerOverCapture, onPointerUp, onPointerUpCapture, onMouseEnter, onMouseLeave, onPointerEnter, onPointerLeave, ...props }: Omit<ElementProps, \"onSelect\" | \"onClick\" | \"onClickCapture\"> & { onClick?: React.MouseEventHandler<HTMLElement>; onClickCapture?: React.MouseEventHandler<HTMLElement>; children?: React.ReactNode; label?: string; selectedValue?: string | null; defaultSelectedValue?: string | null; selectedCategoryValue?: string | null; defaultSelectedCategoryValue?: string | null; openCategories?: readonly string[]; defaultOpenCategories?: readonly string[]; open?: boolean; defaultOpen?: boolean; collapsed?: boolean; defaultCollapsed?: boolean; collapsible?: boolean; multiple?: boolean; density?: \"small\" | \"medium\" | \"large\"; tabbable?: boolean | \"true\" | \"false\"; openLabel?: string; closeLabel?: string; onItemSelect?: (value: string, href: string | null, label: string) => void; onCategoryToggle?: (value: string, open: boolean, openCategories: string[]) => void; onNavItemSelect?: (event: SharePointUIEvent, data: NavReactItemSelectData) => void; onNavCategoryItemToggle?: (event: SharePointUIEvent, data: NavReactCategoryToggleData) => void; onOpenChange?: (open: boolean) => void; onCollapsedChange?: (collapsed: boolean) => void }",
5034
+ "reactSignature": "{ children, label = \"Navigation\", selectedValue, defaultSelectedValue, selectedCategoryValue, defaultSelectedCategoryValue, openCategories, defaultOpenCategories = [], open, defaultOpen, collapsed, defaultCollapsed, fullWidth = false, collapsible = false, multiple = true, density = \"medium\", openLabel, closeLabel, onItemSelect, onCategoryToggle, onNavItemSelect, onNavCategoryItemToggle, onOpenChange, onCollapsedChange, onClick, onClickCapture, onKeyDown, onKeyDownCapture, onKeyUp, onKeyUpCapture, onKeyPress, onKeyPressCapture, onFocus, onFocusCapture, onBlur, onBlurCapture, onAuxClick, onAuxClickCapture, onContextMenu, onContextMenuCapture, onDoubleClick, onDoubleClickCapture, onMouseDown, onMouseDownCapture, onMouseMove, onMouseMoveCapture, onMouseOut, onMouseOutCapture, onMouseOver, onMouseOverCapture, onMouseUp, onMouseUpCapture, onGotPointerCapture, onGotPointerCaptureCapture, onLostPointerCapture, onLostPointerCaptureCapture, onPointerCancel, onPointerCancelCapture, onPointerDown, onPointerDownCapture, onPointerMove, onPointerMoveCapture, onPointerOut, onPointerOutCapture, onPointerOver, onPointerOverCapture, onPointerUp, onPointerUpCapture, onMouseEnter, onMouseLeave, onPointerEnter, onPointerLeave, ...props }: Omit<ElementProps, \"onSelect\" | \"onClick\" | \"onClickCapture\"> & { onClick?: React.MouseEventHandler<HTMLElement>; onClickCapture?: React.MouseEventHandler<HTMLElement>; children?: React.ReactNode; label?: string; selectedValue?: string | null; defaultSelectedValue?: string | null; selectedCategoryValue?: string | null; defaultSelectedCategoryValue?: string | null; openCategories?: readonly string[]; defaultOpenCategories?: readonly string[]; open?: boolean; defaultOpen?: boolean; collapsed?: boolean; defaultCollapsed?: boolean; fullWidth?: boolean; collapsible?: boolean; multiple?: boolean; density?: \"small\" | \"medium\" | \"large\"; tabbable?: boolean | \"true\" | \"false\"; openLabel?: string; closeLabel?: string; onItemSelect?: (value: string, href: string | null, label: string) => void; onCategoryToggle?: (value: string, open: boolean, openCategories: string[]) => void; onNavItemSelect?: (event: SharePointUIEvent, data: NavReactItemSelectData) => void; onNavCategoryItemToggle?: (event: SharePointUIEvent, data: NavReactCategoryToggleData) => void; onOpenChange?: (open: boolean) => void; onCollapsedChange?: (collapsed: boolean) => void }",
4895
5035
  "callbackProps": [
4896
5036
  "onItemSelect",
4897
5037
  "onCategoryToggle",
@@ -5393,7 +5533,7 @@
5393
5533
  "host className/style",
5394
5534
  "named ::part hooks"
5395
5535
  ],
5396
- "reactSignature": "{ children, label, appearance, slot = \"navigation\", ...props }: ElementProps & { children?: React.ReactNode; label?: string; appearance?: \"brand\"; slot?: string }",
5536
+ "reactSignature": "{ children, label, appearance, slot = \"navigation\", ...props }: ElementProps & { children?: React.ReactNode; label?: string; appearance?: \"brand\" | \"thumbnails\"; slot?: string }",
5397
5537
  "slots": [
5398
5538
  "navigation buttons"
5399
5539
  ],
@@ -5402,11 +5542,11 @@
5402
5542
  "react": "CarouselNav",
5403
5543
  "tag": "spui-carousel-nav",
5404
5544
  "define": "defineCarouselNav",
5405
- "signature": "{ children, label, appearance, ...props }: ElementProps & { children?: React.ReactNode; label?: string; appearance?: \"brand\" }",
5545
+ "signature": "{ children, label, appearance, ...props }: ElementProps & { children?: React.ReactNode; label?: string; appearance?: \"brand\" | \"thumbnails\" }",
5406
5546
  "slots": [
5407
5547
  "navigation buttons"
5408
5548
  ],
5409
- "summary": "Group carousel position buttons with RTL-aware roving keyboard focus."
5549
+ "summary": "Group carousel position buttons with RTL-aware roving keyboard focus; use the scrollable thumbnails appearance with image buttons for gallery navigation."
5410
5550
  },
5411
5551
  {
5412
5552
  "module": "carousel",
@@ -5417,7 +5557,8 @@
5417
5557
  "SharePointUIProvider tokens",
5418
5558
  "CSS custom properties",
5419
5559
  "host className/style",
5420
- "named ::part hooks"
5560
+ "named ::part hooks",
5561
+ "appearance: brand or thumbnails"
5421
5562
  ],
5422
5563
  "reactSignature": "{ children, index, label, selected = false, disabled = false, ...props }: ElementProps & { children?: React.ReactNode; index: number; label?: string; selected?: boolean; disabled?: boolean }",
5423
5564
  "slots": [
@@ -5801,8 +5942,12 @@
5801
5942
  }
5802
5943
  ],
5803
5944
  "agentContract": {
5945
+ "composition": "Import named components from @spfx-fast/sharepoint-ui/react. Put one SharePointUIProvider around the view. Compose with children by default; use only the slots and component props listed for that component.",
5804
5946
  "reactProps": "Each component entry exposes its React parameter signature; callback props describe emitted value/state changes.",
5947
+ "state": "State props, initial default props, callback names, and callback arguments vary by component. Follow that component's signature and callbackProps; update controlled state in its callback.",
5805
5948
  "slots": "Use the listed slots for content. Prefer ViewCard over custom markup for common SharePoint records.",
5949
+ "customElements": "Import the component's define function from @spfx-fast/sharepoint-ui/elements/{module} and register it once. Set objects and callbacks as DOM properties. For event names and statically visible detail keys, use the source module's event inventory at https://vystartasv.github.io/sharepoint-ui/agents/events/{module}.json; it is module-scoped, so check event.target when composed tags emit events.",
5950
+ "styling": "Theme, typography, locale, and direction inherit from SharePointUIProvider. Style the host with tokens, documented CSS variables, or ::part hooks; do not target shadow-DOM internals.",
5806
5951
  "localization": "Set lang and dir from the host language. SPFx uses pageContext.cultureInfo.currentUICultureName and isRightToLeft. Supply SharePointUIProvider strings for built-in control labels.",
5807
5952
  "theme": "Call applySharePointTheme at the SPFx web-part boundary; it forwards SharePoint palette, semantic colors, effects, spacing, font ramps, and default font styles as --spui-* variables. React can use SharePointUIProvider appearance and density (compact, comfortable, touch), theme.brandColor for derived accent/state roles, fluentTokens for raw Fluent variables, theme for typed semantic/design roles, and tokens for direct --spui-* overrides. Nested providers merge semantic groups and inherit density. Explicit theme roles override generated brand roles; direct tokens and element inline styles override these. Hex brand colors derive an AA-aware black-or-white on-accent foreground; set colors.accentForeground for other CSS color formats. CSS custom properties and ::part hooks remain public styling surfaces.",
5808
5953
  "persistence": "Keep SharePoint reads and writes in the web part or its service; controls expose values and do not persist data.",
@@ -34,7 +34,7 @@ export function getSharePointUIStyleTokens(appearance = "fluent", overrides, bas
34
34
  "--spui-ui-warning-surface": "var(--spui-semantic-warning-background, #fff4ce)",
35
35
  "--spui-ui-danger": "var(--spui-semantic-error-text, #b10e1c)",
36
36
  "--spui-ui-danger-surface": "var(--spui-semantic-error-background, #fde7e9)",
37
- "--spui-focus-ring": "var(--spui-ui-border-focus)"
37
+ "--spui-focus-ring": "var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent)))"
38
38
  });
39
39
  else {
40
40
  const presetText = String(tokens["--spui-ui-text"]);
@@ -61,7 +61,7 @@ export function getSharePointUIStyleTokens(appearance = "fluent", overrides, bas
61
61
  "--spui-ui-warning-surface": `var(--spui-semantic-warning-background, ${tokens["--spui-ui-warning-surface"]})`,
62
62
  "--spui-ui-danger": `var(--spui-semantic-error-text, ${tokens["--spui-ui-danger"]})`,
63
63
  "--spui-ui-danger-surface": `var(--spui-semantic-error-background, ${tokens["--spui-ui-danger-surface"]})`,
64
- "--spui-focus-ring": "var(--spui-ui-border-focus)"
64
+ "--spui-focus-ring": "var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent)))"
65
65
  });
66
66
  }
67
67
  if (selectedBase === "ios") {
@@ -85,7 +85,7 @@ export function getSharePointUIStyleTokens(appearance = "fluent", overrides, bas
85
85
  "--spui-ui-warning-surface": "light-dark(var(--spui-semantic-warning-background, #fff3d6), color-mix(in srgb, var(--spui-semantic-warning-background, #342900) 28%, black 72%))",
86
86
  "--spui-ui-danger": "light-dark(var(--spui-semantic-error-text, #d70015), color-mix(in srgb, var(--spui-semantic-error-text, #d70015) 30%, white 70%))",
87
87
  "--spui-ui-danger-surface": "light-dark(var(--spui-semantic-error-background, #ffe5e8), color-mix(in srgb, var(--spui-semantic-error-background, #3a1114) 28%, black 72%))",
88
- "--spui-focus-ring": "var(--spui-ui-border-focus)"
88
+ "--spui-focus-ring": "var(--spui-semantic-focus-border, var(--colorStrokeFocus2, var(--spui-ui-accent)))"
89
89
  });
90
90
  }
91
91
  // Stable shared roles for applications that style slots or compose their own
@@ -202,7 +202,7 @@ export function getSharePointUIStyleTokens(appearance = "fluent", overrides, bas
202
202
  "--spui-control-height": scalePresetLength(tokens["--spui-base-control-height"], 1.04),
203
203
  "--spui-card-padding": scalePresetLength(tokens["--spui-base-card-padding"], 1.12),
204
204
  "--spui-control-gap": scalePresetLength(tokens["--spui-base-control-gap"], 1.12),
205
- "--spui-color-scheme": "light"
205
+ "--spui-color-scheme": tokens["--spui-color-scheme"] ?? "light"
206
206
  });
207
207
  if (appearance === "glass") {
208
208
  // Directly specified finished values win; base values still flow through
@@ -1,4 +1,10 @@
1
1
  import { base } from "./shared.js";
2
+ const alertIconPaths = {
3
+ info: "M10 2.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM10 9v5m0-7h.01",
4
+ success: "M10 2.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM6.5 10l2.3 2.3 4.7-5.1",
5
+ warning: "m10 2.5 8 14H2l8-14ZM10 7v4m0 3.5h.01",
6
+ error: "M10 2.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM7 7l6 6m0-6-6 6",
7
+ };
2
8
  export function defineAlert() {
3
9
  if (customElements.get("spui-alert"))
4
10
  return;
@@ -23,6 +29,9 @@ export function defineAlert() {
23
29
  const avatar = this.shadowRoot?.querySelector('slot[name="avatar"]');
24
30
  if (icon && avatar)
25
31
  icon.hidden = avatar.assignedElements().length > 0;
32
+ const iconPath = icon?.querySelector(".icon path");
33
+ if (iconPath)
34
+ iconPath.setAttribute("d", alertIconPaths[intent ?? "info"] ?? alertIconPaths.info);
26
35
  }
27
36
  render() {
28
37
  const shadow = this.attachShadow({ mode: "open" });
@@ -37,6 +46,13 @@ export function defineAlert() {
37
46
  defaultIcon.className = "icon";
38
47
  defaultIcon.setAttribute("aria-hidden", "true");
39
48
  icon.append(defaultIcon);
49
+ const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
50
+ iconSvg.setAttribute("viewBox", "0 0 20 20");
51
+ iconSvg.setAttribute("focusable", "false");
52
+ const iconPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
53
+ iconPath.setAttribute("d", alertIconPaths.info);
54
+ iconSvg.append(iconPath);
55
+ defaultIcon.append(iconSvg);
40
56
  const content = document.createElement("div");
41
57
  content.className = "content";
42
58
  content.part.add("content");
@@ -49,12 +65,20 @@ export function defineAlert() {
49
65
  actions.part.add("actions");
50
66
  const button = document.createElement("button");
51
67
  button.type = "button";
52
- button.textContent = "×";
53
68
  button.hidden = !this.hasAttribute("dismissible");
54
69
  button.part.add("dismiss");
70
+ const closeSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
71
+ closeSvg.classList.add("dismiss-icon");
72
+ closeSvg.setAttribute("viewBox", "0 0 20 20");
73
+ closeSvg.setAttribute("aria-hidden", "true");
74
+ closeSvg.setAttribute("focusable", "false");
75
+ const closePath = document.createElementNS("http://www.w3.org/2000/svg", "path");
76
+ closePath.setAttribute("d", "m5 5 10 10M15 5 5 15");
77
+ closeSvg.append(closePath);
78
+ button.append(closeSvg);
55
79
  button.addEventListener("click", () => { this.hidden = true; this.dispatchEvent(new CustomEvent("spui-dismiss", { bubbles: true, composed: true })); });
56
80
  const style = document.createElement("style");
57
- style.textContent = `${base}:host{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border:1px solid var(--spui-control-border,var(--spui-ui-stroke,#d1d1d1));border-inline-start:4px solid var(--spui-ui-accent,#0f6cbd);border-radius:var(--spui-control-radius,6px);background:var(--spui-ui-canvas,#f5f5f5);line-height:1.45}:host([hidden]){display:none}.content{flex:1;min-width:0}.icon{font-weight:700;color:var(--spui-ui-accent,#0f6cbd)}.icon:before{content:'ℹ'}slot[name=actions]::slotted(*){display:flex;align-items:center;gap:8px}slot[name=action]::slotted(button){border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;cursor:pointer}slot[name=action]::slotted(button:focus-visible){outline:2px solid var(--spui-focus-ring,#005a9e);outline-offset:2px}:host([intent=success]){border-inline-start-color:var(--spui-ui-success,#107c10);background:var(--spui-success-surface,var(--spui-ui-success-surface,#dff6dd))}:host([intent=success]) .icon{color:var(--spui-ui-success,#107c10)}:host([intent=success]) .icon:before{content:'✓'}:host([intent=warning]){border-inline-start-color:var(--spui-ui-warning,#835b00);background:var(--spui-warning-surface,var(--spui-ui-warning-surface,#fff4ce))}:host([intent=warning]) .icon{color:var(--spui-ui-warning,#835b00)}:host([intent=warning]) .icon:before{content:'⚠'}:host([intent=error]){border-inline-start-color:var(--spui-ui-danger,#b10e1c);background:var(--spui-danger-surface,var(--spui-ui-danger-surface,#fde7e9))}:host([intent=error]) .icon{color:var(--spui-ui-danger,#b10e1c)}:host([intent=error]) .icon:before{content:'!'}:host([appearance=inverted]){color:var(--colorNeutralForegroundInverted2,var(--spui-ui-surface,#fff));background:var(--colorNeutralBackgroundInverted,var(--spui-ui-text,#242424));border-color:var(--spui-alert-inverted-border,transparent)}:host([appearance=inverted]) .icon{color:currentColor}button{border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;font-size:18px;line-height:1;padding:2px 6px;cursor:pointer}button:hover{background:var(--spui-ui-surface-hover,#0001)}button:focus-visible{outline:2px solid var(--spui-focus-ring,#005a9e);outline-offset:2px}button[hidden]{display:none}:host([data-spui-appearance=glass]),:host-context([data-spui-appearance=glass]){backdrop-filter:var(--spui-card-backdrop,none);border-color:var(--spui-card-border,#ffffff80)}@media(forced-colors:active){:host{border-color:CanvasText;color:CanvasText;background:Canvas}.icon{color:CanvasText}}`;
81
+ style.textContent = `${base}:host{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border:1px solid var(--spui-control-border,var(--spui-ui-stroke,#d1d1d1));border-inline-start:2px solid var(--spui-ui-accent,#0f6cbd);border-radius:var(--spui-control-radius,6px);background:var(--spui-ui-canvas,#f5f5f5);line-height:1.45}:host([hidden]){display:none}.content{flex:1;min-width:0}.icon{display:inline-flex;flex:none;align-items:center;justify-content:center;color:var(--spui-ui-accent,#0f6cbd)}.icon svg,.dismiss-icon{display:block;width:1em;height:1em;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.icon svg{width:18px;height:18px}slot[name=actions]::slotted(*){display:flex;align-items:center;gap:8px}slot[name=action]::slotted(button){border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;cursor:pointer}slot[name=action]::slotted(button:focus-visible){outline:2px solid var(--spui-focus-ring,#005a9e);outline-offset:2px}:host([intent=success]){border-inline-start-color:var(--spui-ui-success,#107c10);background:var(--spui-success-surface,var(--spui-ui-success-surface,#dff6dd))}:host([intent=success]) .icon{color:var(--spui-ui-success,#107c10)}:host([intent=warning]){border-inline-start-color:var(--spui-ui-warning,#835b00);background:var(--spui-warning-surface,var(--spui-ui-warning-surface,#fff4ce))}:host([intent=warning]) .icon{color:var(--spui-ui-warning,#835b00)}:host([intent=error]){border-inline-start-color:var(--spui-ui-danger,#b10e1c);background:var(--spui-danger-surface,var(--spui-ui-danger-surface,#fde7e9))}:host([intent=error]) .icon{color:var(--spui-ui-danger,#b10e1c)}:host([appearance=inverted]){color:var(--colorNeutralForegroundInverted2,var(--spui-ui-surface,#fff));background:var(--colorNeutralBackgroundInverted,var(--spui-ui-text,#242424));border-color:var(--spui-alert-inverted-border,transparent)}:host([appearance=inverted]) .icon{color:currentColor}button{border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;line-height:1;padding:2px 6px;cursor:pointer}button:hover{background:var(--spui-ui-surface-hover,#0001)}button:focus-visible{outline:2px solid var(--spui-focus-ring,#005a9e);outline-offset:2px}button[hidden]{display:none}:host([data-spui-appearance=glass]),:host-context([data-spui-appearance=glass]){backdrop-filter:var(--spui-card-backdrop,none);border-color:var(--spui-card-border,#ffffff80)}@media(forced-colors:active){:host{border-color:CanvasText;color:CanvasText;background:Canvas}.icon{color:CanvasText}}`;
58
82
  shadow.append(style, avatar, icon, content, action, actions, button);
59
83
  }
60
84
  }
@@ -85,7 +85,7 @@ export function defineAvatar() {
85
85
  this.fallback.innerHTML = '<svg viewBox="0 0 24 24" focusable="false"><path d="M12 12a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9Zm0 2c-4.4 0-8 2.2-8 5v2h16v-2c0-2.8-3.6-5-8-5Z"/></svg>';
86
86
  this.surface.append(this.image, this.initials, this.fallback);
87
87
  const style = document.createElement("style");
88
- style.textContent = `${base}:host{--spui-avatar-size:32px;display:inline-grid;flex:none;width:var(--spui-avatar-size);height:var(--spui-avatar-size);vertical-align:middle;line-height:1}:host([size]){width:var(--spui-avatar-size);height:var(--spui-avatar-size)}.surface{position:relative;box-sizing:border-box;display:grid;place-items:center;width:100%;height:100%;overflow:hidden;border:1px solid var(--spui-avatar-border,var(--spui-ui-stroke,var(--spui-semantic-body-divider,#d1d1d1)));border-radius:var(--spui-avatar-radius,50%);background:var(--spui-avatar-background,var(--spui-semantic-body-subtext,var(--colorNeutralBackground3,#f0f0f0)));color:var(--spui-avatar-foreground,var(--spui-semantic-body-text,var(--colorNeutralForeground1,#323130)));font:600 calc(var(--spui-avatar-size) * .36)/1 var(--spui-font-family,inherit);user-select:none}.image{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover}.initials{z-index:0;max-width:100%;overflow:hidden;padding:0 2px;text-overflow:clip;white-space:nowrap}.fallback{z-index:0;display:grid;place-items:center;width:62%;height:62%;color:currentColor}.initials:not(:empty)~.fallback{display:none}.fallback svg{display:block;width:100%;height:100%;fill:currentColor}:host([data-image-state=loading]) .image,:host([data-image-state=error]) .image{visibility:hidden}:host([data-image-state=loaded]) .initials,:host([data-image-state=loaded]) .fallback{visibility:hidden}:host([shape=square]) .surface{border-radius:var(--spui-avatar-radius,var(--spui-control-radius,6px))}:host([color=brand]) .surface{background:var(--spui-avatar-brand-background,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c)))));color:var(--spui-avatar-brand-foreground,var(--spui-semantic-primary-button-text,var(--colorNeutralForegroundOnBrand,#fff)))}:host([color=colorful]) .surface,:host([data-named-color]) .surface{background:var(--spui-avatar-colorful-background,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c)))));color:var(--spui-avatar-colorful-foreground,var(--spui-semantic-primary-button-text,#fff))}:host([active=active]:not([active-appearance])) .surface,:host([active=active][active-appearance=ring]) .surface{box-shadow:0 0 0 2px var(--spui-ui-surface,#fff),0 0 0 4px var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c))}:host([active=active][active-appearance=shadow]) .surface{box-shadow:var(--spui-avatar-active-shadow,var(--spui-card-shadow,0 2px 8px rgb(0 0 0 / 24%)))}:host([active=active][active-appearance=ring-shadow]) .surface{box-shadow:0 0 0 2px var(--spui-ui-surface,#fff),0 0 0 4px var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c)),var(--spui-avatar-active-shadow,var(--spui-card-shadow,0 2px 8px rgb(0 0 0 / 24%)))}:host([active=inactive]){opacity:.56}:host([data-custom-icon=true]) .initials,:host([data-custom-icon=true]) .fallback{display:none}:host([data-custom-icon=true]) .icon{z-index:0;display:grid;place-items:center;width:70%;height:70%;color:inherit}:host([data-custom-badge=true]) .badge{position:absolute;z-index:2;inset-block-end:0;inset-inline-end:0;display:grid;place-items:center;min-width:var(--spui-avatar-badge-size,10px);min-height:var(--spui-avatar-badge-size,10px);border:2px solid var(--spui-avatar-badge-border,var(--spui-ui-surface,#fff));border-radius:50%;background:var(--spui-avatar-badge-background,var(--spui-semantic-success-background,#13a10e));color:var(--spui-avatar-badge-foreground,#fff);font:600 9px/1 var(--spui-font-family,inherit)}:host(:not([data-custom-badge=true])) .badge{display:none}@media(forced-colors:active){.surface{border-color:CanvasText;background:Canvas;color:CanvasText;forced-color-adjust:none}:host([data-named-color]) .surface{border-color:CanvasText;background:Canvas;color:CanvasText}.image{forced-color-adjust:auto}:host([active=active]) .surface{outline:2px solid Highlight;outline-offset:2px;box-shadow:none}}`;
88
+ style.textContent = `${base}:host{--spui-avatar-size:32px;display:inline-grid;flex:none;width:var(--spui-avatar-size);height:var(--spui-avatar-size);vertical-align:middle;line-height:1}:host([size]){width:var(--spui-avatar-size);height:var(--spui-avatar-size)}.surface{position:relative;box-sizing:border-box;display:grid;place-items:center;width:100%;height:100%;overflow:hidden;border:1px solid var(--spui-avatar-border,var(--spui-ui-stroke,var(--spui-semantic-body-divider,#d1d1d1)));border-radius:var(--spui-avatar-radius,50%);background:var(--spui-avatar-background,var(--spui-semantic-body-subtext,var(--colorNeutralBackground3,#f0f0f0)));color:var(--spui-avatar-foreground,var(--spui-semantic-body-text,var(--colorNeutralForeground1,#323130)));font:600 calc(var(--spui-avatar-size) * .36)/1 var(--spui-font-family,inherit);user-select:none}.image{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover}.initials{position:absolute;inset:0;z-index:0;display:flex;align-items:center;justify-content:center;max-width:100%;overflow:hidden;padding:0 2px;text-align:center;line-height:1;white-space:nowrap}.fallback{z-index:0;display:grid;place-items:center;width:62%;height:62%;color:currentColor}.initials:not(:empty)~.fallback{display:none}.fallback svg{display:block;width:100%;height:100%;fill:currentColor}:host([data-image-state=loading]) .image,:host([data-image-state=error]) .image{visibility:hidden}:host([data-image-state=loaded]) .initials,:host([data-image-state=loaded]) .fallback{visibility:hidden}:host([shape=square]) .surface{border-radius:var(--spui-avatar-radius,var(--spui-control-radius,6px))}:host([color=brand]) .surface{background:var(--spui-avatar-brand-background,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c)))));color:var(--spui-avatar-brand-foreground,var(--spui-semantic-primary-button-text,var(--colorNeutralForegroundOnBrand,#fff)))}:host([color=colorful]) .surface,:host([data-named-color]) .surface{background:var(--spui-avatar-colorful-background,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c)))));color:var(--spui-avatar-colorful-foreground,var(--spui-semantic-primary-button-text,#fff))}:host([active=active]:not([active-appearance])) .surface,:host([active=active][active-appearance=ring]) .surface{box-shadow:0 0 0 2px var(--spui-ui-surface,#fff),0 0 0 4px var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c))}:host([active=active][active-appearance=shadow]) .surface{box-shadow:var(--spui-avatar-active-shadow,var(--spui-card-shadow,0 2px 8px rgb(0 0 0 / 24%)))}:host([active=active][active-appearance=ring-shadow]) .surface{box-shadow:0 0 0 2px var(--spui-ui-surface,#fff),0 0 0 4px var(--spui-avatar-active-color,var(--spui-ui-accent,#78716c)),var(--spui-avatar-active-shadow,var(--spui-card-shadow,0 2px 8px rgb(0 0 0 / 24%)))}:host([active=inactive]){opacity:.56}:host([data-custom-icon=true]) .initials,:host([data-custom-icon=true]) .fallback{display:none}:host([data-custom-icon=true]) .icon{z-index:0;display:grid;place-items:center;width:70%;height:70%;color:inherit}:host([data-custom-badge=true]) .badge{position:absolute;z-index:2;inset-block-end:0;inset-inline-end:0;display:grid;place-items:center;min-width:var(--spui-avatar-badge-size,10px);min-height:var(--spui-avatar-badge-size,10px);border:2px solid var(--spui-avatar-badge-border,var(--spui-ui-surface,#fff));border-radius:50%;background:var(--spui-avatar-badge-background,var(--spui-semantic-success-background,#13a10e));color:var(--spui-avatar-badge-foreground,#fff);font:600 9px/1 var(--spui-font-family,inherit)}:host(:not([data-custom-badge=true])) .badge{display:none}@media(forced-colors:active){.surface{border-color:CanvasText;background:Canvas;color:CanvasText;forced-color-adjust:none}:host([data-named-color]) .surface{border-color:CanvasText;background:Canvas;color:CanvasText}.image{forced-color-adjust:auto}:host([active=active]) .surface{outline:2px solid Highlight;outline-offset:2px;box-shadow:none}}`;
89
89
  style.textContent += `.surface{background:var(--spui-avatar-background,var(--spui-palette-neutral-light,var(--colorNeutralBackground3,#f0f0f0)));font-size:clamp(10px,calc(var(--spui-avatar-size) * .36),48px)}`;
90
90
  style.textContent += `@media(forced-colors:active){.surface{border-color:CanvasText;background:Canvas;color:CanvasText}}`;
91
91
  const icon = document.createElement("span");
@@ -1,5 +1,5 @@
1
1
  import { base, stopHostAccessibleNameSync, syncHostAccessibleName } from "./shared.js";
2
- const buttonCss = `${base}button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:var(--spui-control-height,32px);padding-inline:var(--spui-control-padding-inline,14px);border:1px solid var(--spui-control-border,var(--spui-ui-stroke,var(--spui-semantic-button-border,var(--colorNeutralStroke1,#8a8886))));border-radius:var(--spui-control-radius,4px);background:var(--spui-control-background,var(--spui-ui-surface,var(--spui-semantic-button-background,var(--colorNeutralBackground1,#fff))));box-shadow:var(--spui-control-shadow,none);color:var(--spui-ui-text,var(--spui-semantic-button-text,var(--colorNeutralForeground1,#323130)));font:inherit;line-height:1.25;text-align:center;text-decoration:none;cursor:pointer;transition:background-color var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),border-color var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),transform var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}button:hover:not(:disabled):not([aria-disabled=true]){background:var(--spui-button-secondary-hover,color-mix(in srgb,var(--spui-control-background,var(--spui-ui-surface,#fff)) 90%,var(--spui-ui-text,#242424) 10%))}button:active:not(:disabled):not([aria-disabled=true]){transform:scale(.98)}button.primary{border-color:var(--spui-primary-background,var(--spui-ui-accent,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,#0078d4))));background:var(--spui-primary-background,var(--spui-ui-accent,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,#0078d4))));color:var(--spui-ui-accent-text,var(--spui-semantic-primary-button-text,var(--colorNeutralForegroundOnBrand,#fff)))}button.primary:hover:not(:disabled):not([aria-disabled=true]){background:var(--spui-primary-hover,var(--spui-ui-accent-hover,var(--colorBrandBackgroundHover,#106ebe)))}button.primary:active:not(:disabled):not([aria-disabled=true]){background:var(--spui-ui-accent-pressed,var(--colorBrandBackgroundPressed,var(--spui-ui-accent-hover,#106ebe)))}button.outline{background:transparent}button.subtle,button.transparent{border-color:transparent;box-shadow:none}button.subtle{background:var(--spui-button-subtle-background,transparent)}button.transparent{background:transparent}button:disabled,button[aria-disabled=true]{opacity:.55;cursor:not-allowed}:host([size=small]) button{min-height:24px;padding-inline:8px;font-size:12px}:host([size=large]) button{min-height:40px;padding-inline:16px;font-size:16px}:host([shape=circular]) button{border-radius:999px}:host([shape=square]) button{border-radius:0}:host([data-spui-split-primary]) button{border-start-end-radius:0;border-end-end-radius:0}:host([data-spui-split-trigger]) button{border-start-start-radius:0;border-end-start-radius:0}:host([icon-only]) button{min-width:var(--spui-control-height,32px);padding-inline:0}:host([size=small][icon-only]) button{min-width:24px}:host([size=large][icon-only]) button{min-width:40px}:host([vertical]) button{flex-direction:column}.icon{display:inline-flex;align-items:center;justify-content:center;line-height:0}:host([vertical]) .icon{font-size:24px}.content{display:inline-flex;align-items:center;justify-content:center}:host([icon-position=after]) .icon{order:2}:host([icon-position=after]) .content{order:1}@media(prefers-reduced-motion:reduce){button{transition:none}}@media(forced-colors:active){button{border-color:ButtonText}button.primary{border-color:Highlight;background:Highlight;color:HighlightText}}`;
2
+ const buttonCss = `${base}button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:var(--spui-control-height,32px);padding-inline:var(--spui-control-padding-inline,14px);border:1px solid var(--spui-control-border,var(--spui-ui-stroke,var(--spui-semantic-button-border,var(--colorNeutralStroke1,#8a8886))));border-radius:var(--spui-control-radius,4px);background:var(--spui-control-background,var(--spui-ui-surface,var(--spui-semantic-button-background,var(--colorNeutralBackground1,#fff))));box-shadow:var(--spui-control-shadow,none);color:var(--spui-ui-text,var(--spui-semantic-button-text,var(--colorNeutralForeground1,#323130)));font:inherit;line-height:1.25;text-align:center;text-decoration:none;cursor:pointer;transition:background-color var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),border-color var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1)),transform var(--spui-motion-duration-normal,.15s) var(--spui-motion-easing-standard,cubic-bezier(.2,0,0,1))}button:hover:not(:disabled):not([aria-disabled=true]){background:var(--spui-button-secondary-hover,color-mix(in srgb,var(--spui-control-background,var(--spui-ui-surface,#fff)) 90%,var(--spui-ui-text,#242424) 10%))}button:active:not(:disabled):not([aria-disabled=true]){transform:scale(.98)}button.primary{border-color:var(--spui-primary-background,var(--spui-ui-accent,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,#0078d4))));background:var(--spui-primary-background,var(--spui-ui-accent,var(--spui-semantic-primary-button-background,var(--colorBrandBackground,#0078d4))));color:var(--spui-ui-accent-text,var(--spui-semantic-primary-button-text,var(--colorNeutralForegroundOnBrand,#fff)))}button.primary:hover:not(:disabled):not([aria-disabled=true]){background:var(--spui-primary-hover,var(--spui-ui-accent-hover,var(--colorBrandBackgroundHover,#106ebe)))}button.primary:active:not(:disabled):not([aria-disabled=true]){background:var(--spui-ui-accent-pressed,var(--colorBrandBackgroundPressed,var(--spui-ui-accent-hover,#106ebe)))}button.outline{background:transparent}button.subtle,button.transparent{border-color:transparent;box-shadow:none}button.subtle{background:var(--spui-button-subtle-background,transparent)}button.transparent{background:transparent}:host([data-spui-appearance=glass]) button:not(.primary),:host-context([data-spui-appearance=glass]) button:not(.primary){backdrop-filter:var(--spui-card-backdrop,blur(16px) saturate(150%))}button:disabled,button[aria-disabled=true]{opacity:.55;cursor:not-allowed}:host([size=small]) button{min-height:24px;padding-inline:8px;font-size:12px}:host([size=large]) button{min-height:40px;padding-inline:16px;font-size:16px}:host([shape=circular]) button{border-radius:999px}:host([shape=square]) button{border-radius:0}:host([data-spui-split-primary]) button{border-start-end-radius:0;border-end-end-radius:0}:host([data-spui-split-trigger]) button{border-start-start-radius:0;border-end-start-radius:0}:host([icon-only]) button{min-width:var(--spui-control-height,32px);padding-inline:0}:host([size=small][icon-only]) button{min-width:24px}:host([size=large][icon-only]) button{min-width:40px}:host([vertical]) button{flex-direction:column}.icon{display:inline-flex;align-items:center;justify-content:center;line-height:0}:host([vertical]) .icon{font-size:24px}.content{display:inline-flex;align-items:center;justify-content:center}:host([icon-position=after]) .icon{order:2}:host([icon-position=after]) .content{order:1}@media(prefers-reduced-motion:reduce){button{transition:none}}@media(forced-colors:active){button{border-color:ButtonText}button.primary{border-color:Highlight;background:Highlight;color:HighlightText}}`;
3
3
  export const buttonControlCss = buttonCss.replace(/\bbutton(?=[.#:[\s,{])/g, ":is(button,a)");
4
4
  const buttonSheets = new WeakMap();
5
5
  const buttonSheetUnsupported = new WeakSet();
@@ -30,7 +30,7 @@ export function defineCarouselNav() {
30
30
  return;
31
31
  const root = this.attachShadow({ mode: "open" });
32
32
  const style = document.createElement("style");
33
- style.textContent = `${base}:host{display:flex;align-items:center;justify-content:center;gap:4px;min-width:0}:host([appearance=brand]){--spui-carousel-indicator:var(--spui-ui-accent,#0078d4)}slot{display:contents}`;
33
+ style.textContent = `${base}:host{display:flex;align-items:center;justify-content:center;gap:4px;min-width:0}:host([appearance=brand]){--spui-carousel-indicator:var(--spui-ui-accent,#0078d4)}:host([appearance=thumbnails]){justify-content:flex-start;max-width:100%;overflow-x:auto;overscroll-behavior-inline:contain;scroll-snap-type:inline mandatory;scrollbar-width:thin;padding-block:4px}:host([appearance=thumbnails]) div[role=group]{display:flex;align-items:center;gap:8px;width:max-content;min-width:100%}:host([appearance=thumbnails]) ::slotted(spui-carousel-nav-image-button){scroll-snap-align:start}slot{display:contents}`;
34
34
  const group = document.createElement("div");
35
35
  group.setAttribute("role", "group");
36
36
  group.setAttribute("aria-label", this.getAttribute("aria-label") || "Slide navigation");
@@ -63,6 +63,9 @@ function defineCarouselNavControl(tag, image) {
63
63
  this.button.type = "button";
64
64
  if (image) {
65
65
  this.picture = document.createElement("img");
66
+ this.picture.loading = "lazy";
67
+ this.picture.decoding = "async";
68
+ this.picture.draggable = false;
66
69
  const imageSlot = document.createElement("slot");
67
70
  imageSlot.name = "image";
68
71
  imageSlot.append(this.picture);
@@ -460,7 +463,7 @@ export function defineCarousel() {
460
463
  this.slides.forEach(slide => { this.restoreSlideState(slide); this.restoreSlideLayout(slide); });
461
464
  }
462
465
  attributeChangedCallback(name) {
463
- if (!this.isConnected)
466
+ if (!this.isConnected || !this.controls)
464
467
  return;
465
468
  if ((name === "selected-index" || name === "active-index") && this.hasAttribute("controlled"))
466
469
  this.setIndex(Number(this.getAttribute(name)), false);