@spfx-fast/sharepoint-ui 0.4.3 → 0.5.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.
Files changed (282) hide show
  1. package/QUICKSTART.md +13 -128
  2. package/README.md +19 -26
  3. package/components.json +240 -5834
  4. package/dist/accordion.d.ts +11 -0
  5. package/dist/accordion.js +4 -0
  6. package/dist/alert.d.ts +3 -0
  7. package/dist/alert.js +4 -0
  8. package/dist/attachment-list.d.ts +15 -0
  9. package/dist/attachment-list.js +14 -0
  10. package/dist/avatar-group.d.ts +12 -0
  11. package/dist/avatar-group.js +7 -0
  12. package/dist/avatar.d.ts +6 -0
  13. package/dist/avatar.js +5 -0
  14. package/dist/badge.d.ts +3 -0
  15. package/dist/badge.js +4 -0
  16. package/dist/breadcrumb.d.ts +10 -0
  17. package/dist/breadcrumb.js +9 -0
  18. package/dist/button-group.d.ts +6 -0
  19. package/dist/button-group.js +4 -0
  20. package/dist/button.d.ts +3 -0
  21. package/dist/button.js +4 -0
  22. package/dist/card.d.ts +3 -0
  23. package/dist/card.js +4 -0
  24. package/dist/carousel.d.ts +11 -0
  25. package/dist/carousel.js +6 -0
  26. package/dist/checkbox.d.ts +3 -0
  27. package/dist/checkbox.js +4 -0
  28. package/dist/combobox.d.ts +9 -0
  29. package/dist/combobox.js +6 -0
  30. package/dist/command-bar.d.ts +6 -0
  31. package/dist/command-bar.js +4 -0
  32. package/dist/copy-button.d.ts +7 -0
  33. package/dist/copy-button.js +19 -0
  34. package/dist/data-grid.d.ts +25 -0
  35. package/dist/data-grid.js +39 -0
  36. package/dist/date-range-picker.d.ts +12 -0
  37. package/dist/date-range-picker.js +4 -0
  38. package/dist/dialog.d.ts +9 -0
  39. package/dist/dialog.js +21 -0
  40. package/dist/divider.d.ts +3 -0
  41. package/dist/divider.js +4 -0
  42. package/dist/drawer.d.ts +6 -0
  43. package/dist/drawer.js +5 -0
  44. package/dist/empty-state.d.ts +7 -0
  45. package/dist/empty-state.js +5 -0
  46. package/dist/field.d.ts +15 -0
  47. package/dist/field.js +11 -0
  48. package/dist/file-dropzone.d.ts +15 -0
  49. package/dist/file-dropzone.js +21 -0
  50. package/dist/filter-bar.d.ts +9 -0
  51. package/dist/filter-bar.js +9 -0
  52. package/dist/image.d.ts +5 -0
  53. package/dist/image.js +4 -0
  54. package/dist/index.d.ts +68 -78
  55. package/dist/index.js +60 -68
  56. package/dist/input.d.ts +3 -0
  57. package/dist/input.js +4 -0
  58. package/dist/label.d.ts +3 -0
  59. package/dist/label.js +4 -0
  60. package/dist/link.d.ts +3 -0
  61. package/dist/link.js +9 -0
  62. package/dist/list-view.d.ts +29 -0
  63. package/dist/list-view.js +14 -0
  64. package/dist/list.d.ts +6 -0
  65. package/dist/list.js +5 -0
  66. package/dist/lookup-picker.d.ts +12 -0
  67. package/dist/lookup-picker.js +4 -0
  68. package/dist/menu.d.ts +6 -0
  69. package/dist/menu.js +4 -0
  70. package/dist/nav.d.ts +12 -0
  71. package/dist/nav.js +4 -0
  72. package/dist/pagination.d.ts +11 -0
  73. package/dist/pagination.js +5 -0
  74. package/dist/person-picker.d.ts +13 -0
  75. package/dist/person-picker.js +4 -0
  76. package/dist/persona.d.ts +7 -0
  77. package/dist/persona.js +5 -0
  78. package/dist/popover.d.ts +6 -0
  79. package/dist/popover.js +4 -0
  80. package/dist/progress-bar.d.ts +5 -0
  81. package/dist/progress-bar.js +5 -0
  82. package/dist/radio-group.d.ts +6 -0
  83. package/dist/radio-group.js +4 -0
  84. package/dist/radio.d.ts +3 -0
  85. package/dist/radio.js +4 -0
  86. package/dist/rating.d.ts +8 -0
  87. package/dist/rating.js +5 -0
  88. package/dist/react.d.ts +1 -4715
  89. package/dist/react.js +1 -7097
  90. package/dist/select.d.ts +3 -0
  91. package/dist/select.js +4 -0
  92. package/dist/sharepoint-field.d.ts +18 -0
  93. package/dist/sharepoint-field.js +54 -0
  94. package/dist/skeleton.d.ts +6 -0
  95. package/dist/skeleton.js +9 -0
  96. package/dist/spinner.d.ts +5 -0
  97. package/dist/spinner.js +4 -0
  98. package/dist/swatch-picker.d.ts +17 -0
  99. package/dist/swatch-picker.js +11 -0
  100. package/dist/switch.d.ts +3 -0
  101. package/dist/switch.js +4 -0
  102. package/dist/table.d.ts +6 -0
  103. package/dist/table.js +4 -0
  104. package/dist/tabs.d.ts +14 -0
  105. package/dist/tabs.js +30 -0
  106. package/dist/tag-group.d.ts +6 -0
  107. package/dist/tag-group.js +4 -0
  108. package/dist/tag-picker.d.ts +12 -0
  109. package/dist/tag-picker.js +4 -0
  110. package/dist/tag.d.ts +7 -0
  111. package/dist/tag.js +5 -0
  112. package/dist/text.d.ts +5 -0
  113. package/dist/text.js +4 -0
  114. package/dist/textarea.d.ts +3 -0
  115. package/dist/textarea.js +4 -0
  116. package/dist/theme.d.ts +15 -175
  117. package/dist/theme.js +36 -239
  118. package/dist/toast.d.ts +9 -0
  119. package/dist/toast.js +12 -0
  120. package/dist/tree.d.ts +13 -0
  121. package/dist/tree.js +9 -0
  122. package/dist/view-card.d.ts +8 -0
  123. package/dist/view-card.js +4 -0
  124. package/dist/view-switcher.d.ts +13 -0
  125. package/dist/view-switcher.js +7 -0
  126. package/dist/virtual-list.d.ts +11 -0
  127. package/dist/virtual-list.js +25 -0
  128. package/measurements.json +365 -0
  129. package/package.json +14 -46
  130. package/src/theme.css +125 -0
  131. package/AGENTS.md +0 -231
  132. package/dist/appearance.d.ts +0 -6
  133. package/dist/appearance.js +0 -217
  134. package/dist/elements/accordion.d.ts +0 -9
  135. package/dist/elements/accordion.js +0 -338
  136. package/dist/elements/alert.d.ts +0 -2
  137. package/dist/elements/alert.js +0 -86
  138. package/dist/elements/avatar-group-primitives.d.ts +0 -3
  139. package/dist/elements/avatar-group-primitives.js +0 -189
  140. package/dist/elements/avatar-group.d.ts +0 -2
  141. package/dist/elements/avatar-group.js +0 -110
  142. package/dist/elements/avatar.d.ts +0 -4
  143. package/dist/elements/avatar.js +0 -251
  144. package/dist/elements/badge.d.ts +0 -8
  145. package/dist/elements/badge.js +0 -182
  146. package/dist/elements/breadcrumb.d.ts +0 -4
  147. package/dist/elements/breadcrumb.js +0 -233
  148. package/dist/elements/button-group.d.ts +0 -1
  149. package/dist/elements/button-group.js +0 -20
  150. package/dist/elements/button-variants.d.ts +0 -6
  151. package/dist/elements/button-variants.js +0 -339
  152. package/dist/elements/button.d.ts +0 -5
  153. package/dist/elements/button.js +0 -233
  154. package/dist/elements/card-primitives.d.ts +0 -3
  155. package/dist/elements/card-primitives.js +0 -83
  156. package/dist/elements/card.d.ts +0 -5
  157. package/dist/elements/card.js +0 -213
  158. package/dist/elements/carousel.d.ts +0 -92
  159. package/dist/elements/carousel.js +0 -1115
  160. package/dist/elements/color-picker.d.ts +0 -24
  161. package/dist/elements/color-picker.js +0 -1004
  162. package/dist/elements/combobox.d.ts +0 -1
  163. package/dist/elements/combobox.js +0 -1012
  164. package/dist/elements/data-grid-model.d.ts +0 -120
  165. package/dist/elements/data-grid-model.js +0 -294
  166. package/dist/elements/data-grid.d.ts +0 -30
  167. package/dist/elements/data-grid.js +0 -1576
  168. package/dist/elements/date-picker.d.ts +0 -1
  169. package/dist/elements/date-picker.js +0 -486
  170. package/dist/elements/dialog.d.ts +0 -23
  171. package/dist/elements/dialog.js +0 -409
  172. package/dist/elements/divider.d.ts +0 -2
  173. package/dist/elements/divider.js +0 -66
  174. package/dist/elements/drawer.d.ts +0 -3
  175. package/dist/elements/drawer.js +0 -308
  176. package/dist/elements/field-composition.d.ts +0 -26
  177. package/dist/elements/field-composition.js +0 -133
  178. package/dist/elements/field.d.ts +0 -1
  179. package/dist/elements/field.js +0 -67
  180. package/dist/elements/file-upload.d.ts +0 -6
  181. package/dist/elements/file-upload.js +0 -211
  182. package/dist/elements/floating-position.d.ts +0 -36
  183. package/dist/elements/floating-position.js +0 -92
  184. package/dist/elements/image.d.ts +0 -28
  185. package/dist/elements/image.js +0 -38
  186. package/dist/elements/info.d.ts +0 -9
  187. package/dist/elements/info.js +0 -102
  188. package/dist/elements/input.d.ts +0 -5
  189. package/dist/elements/input.js +0 -255
  190. package/dist/elements/label.d.ts +0 -15
  191. package/dist/elements/label.js +0 -56
  192. package/dist/elements/link.d.ts +0 -1
  193. package/dist/elements/link.js +0 -92
  194. package/dist/elements/list.d.ts +0 -19
  195. package/dist/elements/list.js +0 -297
  196. package/dist/elements/menu.d.ts +0 -33
  197. package/dist/elements/menu.js +0 -1103
  198. package/dist/elements/message-bar.d.ts +0 -10
  199. package/dist/elements/message-bar.js +0 -327
  200. package/dist/elements/nav.d.ts +0 -32
  201. package/dist/elements/nav.js +0 -794
  202. package/dist/elements/overflow.d.ts +0 -10
  203. package/dist/elements/overflow.js +0 -218
  204. package/dist/elements/persona.d.ts +0 -3
  205. package/dist/elements/persona.js +0 -125
  206. package/dist/elements/popover.d.ts +0 -77
  207. package/dist/elements/popover.js +0 -840
  208. package/dist/elements/progress-bar.d.ts +0 -4
  209. package/dist/elements/progress-bar.js +0 -82
  210. package/dist/elements/radio-group.d.ts +0 -1
  211. package/dist/elements/radio-group.js +0 -303
  212. package/dist/elements/rating-display.d.ts +0 -2
  213. package/dist/elements/rating-display.js +0 -125
  214. package/dist/elements/rating.d.ts +0 -2
  215. package/dist/elements/rating.js +0 -206
  216. package/dist/elements/rich-text-editor.d.ts +0 -32
  217. package/dist/elements/rich-text-editor.js +0 -1367
  218. package/dist/elements/search.d.ts +0 -3
  219. package/dist/elements/search.js +0 -200
  220. package/dist/elements/select.d.ts +0 -6
  221. package/dist/elements/select.js +0 -291
  222. package/dist/elements/shared.d.ts +0 -22
  223. package/dist/elements/shared.js +0 -409
  224. package/dist/elements/sharepoint-field-basic-controls.d.ts +0 -1
  225. package/dist/elements/sharepoint-field-basic-controls.js +0 -14
  226. package/dist/elements/sharepoint-fields.d.ts +0 -29
  227. package/dist/elements/sharepoint-fields.js +0 -460
  228. package/dist/elements/skeleton.d.ts +0 -6
  229. package/dist/elements/skeleton.js +0 -91
  230. package/dist/elements/slider.d.ts +0 -1
  231. package/dist/elements/slider.js +0 -248
  232. package/dist/elements/spin-button.d.ts +0 -7
  233. package/dist/elements/spin-button.js +0 -499
  234. package/dist/elements/spinner.d.ts +0 -4
  235. package/dist/elements/spinner.js +0 -124
  236. package/dist/elements/swatch-picker.d.ts +0 -5
  237. package/dist/elements/swatch-picker.js +0 -363
  238. package/dist/elements/table.d.ts +0 -21
  239. package/dist/elements/table.js +0 -261
  240. package/dist/elements/tabs.d.ts +0 -1
  241. package/dist/elements/tabs.js +0 -276
  242. package/dist/elements/tag-picker.d.ts +0 -29
  243. package/dist/elements/tag-picker.js +0 -715
  244. package/dist/elements/tags.d.ts +0 -21
  245. package/dist/elements/tags.js +0 -421
  246. package/dist/elements/teaching-popover.d.ts +0 -32
  247. package/dist/elements/teaching-popover.js +0 -412
  248. package/dist/elements/text.d.ts +0 -9
  249. package/dist/elements/text.js +0 -75
  250. package/dist/elements/textarea.d.ts +0 -1
  251. package/dist/elements/textarea.js +0 -182
  252. package/dist/elements/toast.d.ts +0 -25
  253. package/dist/elements/toast.js +0 -337
  254. package/dist/elements/toaster.d.ts +0 -6
  255. package/dist/elements/toaster.js +0 -357
  256. package/dist/elements/toggle.d.ts +0 -2
  257. package/dist/elements/toggle.js +0 -246
  258. package/dist/elements/toolbar.d.ts +0 -12
  259. package/dist/elements/toolbar.js +0 -381
  260. package/dist/elements/tooltip.d.ts +0 -76
  261. package/dist/elements/tooltip.js +0 -527
  262. package/dist/elements/tree-model.d.ts +0 -19
  263. package/dist/elements/tree-model.js +0 -34
  264. package/dist/elements/tree.d.ts +0 -5
  265. package/dist/elements/tree.js +0 -273
  266. package/dist/elements/view-card.d.ts +0 -2
  267. package/dist/elements/view-card.js +0 -78
  268. package/dist/elements/virtual-list.d.ts +0 -41
  269. package/dist/elements/virtual-list.js +0 -369
  270. package/dist/elements.d.ts +0 -73
  271. package/dist/elements.js +0 -210
  272. package/dist/sharepoint-fields.d.ts +0 -135
  273. package/dist/sharepoint-fields.js +0 -402
  274. package/dist/virtualizer-hooks.d.ts +0 -80
  275. package/dist/virtualizer-hooks.js +0 -253
  276. package/scripts/generate-token-index.mjs +0 -43
  277. package/scripts/sync-component-catalog-signatures.mjs +0 -55
  278. package/scripts/validate-component-catalog.mjs +0 -44
  279. package/skills/sharepoint-ui/SKILL.md +0 -12
  280. package/skills/sharepoint-ui/references/choosing-components.md +0 -20
  281. package/skills/sharepoint-ui/references/sharepoint-patterns.md +0 -17
  282. package/tokens.json +0 -3777
@@ -1,402 +0,0 @@
1
- export function getRichTextEditorFieldOptions(field) {
2
- const asBoolean = (value) => value === undefined ? undefined : value === true || (typeof value === "string" && value.trim().toLowerCase() === "true");
3
- const richText = asBoolean(field.RichText ?? field.richText);
4
- const restricted = asBoolean(field.RestrictedMode ?? field.restrictedMode) ?? false;
5
- const mode = (field.RichTextMode ?? field.richTextMode)?.trim().toLowerCase();
6
- const contentMode = richText === false ? "plain" : restricted ? "restricted" : mode === "fullhtml" ? "html" : mode === "compatible" || richText === true ? "rich" : "html";
7
- const fieldAllowsHyperlinks = asBoolean(field.AllowHyperlink ?? field.allowHyperlink);
8
- // Missing field metadata leaves safe, sanitized links enabled; an explicit SharePoint prohibition always wins.
9
- return {
10
- contentMode,
11
- allowHyperlink: contentMode !== "plain" && contentMode !== "restricted" && fieldAllowsHyperlinks !== false
12
- };
13
- }
14
- const booleanValue = (value) => value === true || (typeof value === "string" && value.trim().toLowerCase() === "true");
15
- const fieldTypeNames = {
16
- 1: "Integer", 2: "Text", 3: "Note", 4: "DateTime", 5: "Counter", 6: "Choice", 7: "Lookup", 8: "Boolean",
17
- 9: "Number", 10: "Currency", 11: "URL", 12: "Computed", 15: "MultiChoice", 17: "Calculated", 20: "User", 32: "OutcomeChoice"
18
- };
19
- /** Map SharePoint REST/CSOM field metadata to a control recipe without importing any UI component code. */
20
- export function getSharePointFieldAdapter(field) {
21
- const fieldTypeKind = field.FieldTypeKind ?? field.fieldTypeKind;
22
- const kind = field.TypeAsString ?? field.typeAsString ?? (fieldTypeKind == null ? "Unknown" : fieldTypeNames[fieldTypeKind] ?? `FieldTypeKind:${fieldTypeKind}`);
23
- const normalized = kind.replace(/^SP\.Field/, "").toLowerCase();
24
- const name = field.InternalName ?? field.internalName ?? "";
25
- const adapter = {
26
- control: "unsupported",
27
- fieldType: kind,
28
- name,
29
- label: field.DisplayName ?? field.displayName ?? field.Title ?? field.title ?? name,
30
- description: field.Description ?? field.description,
31
- required: booleanValue(field.Required ?? field.required),
32
- readOnly: booleanValue(field.ReadOnlyField ?? field.readOnlyField),
33
- hidden: booleanValue(field.Hidden ?? field.hidden)
34
- };
35
- const finish = (control) => ({ ...adapter, control });
36
- if (["text", "integer", "number", "currency", "url"].includes(normalized)) {
37
- const control = finish("input");
38
- control.inputType = normalized === "url" ? "url" : ["integer", "number", "currency"].includes(normalized) ? "number" : "text";
39
- if (normalized === "url") {
40
- const displayFormat = String(field.DisplayFormat ?? field.displayFormat ?? "0").trim().toLowerCase();
41
- control.urlFormat = displayFormat === "1" || displayFormat === "image" || displayFormat === "picture" ? "picture" : "hyperlink";
42
- }
43
- control.maxLength = getConfiguredMaxLength(field);
44
- if (control.inputType === "number") {
45
- const decimalPlaces = getConfiguredDecimalPlaces(field);
46
- control.step = normalized === "integer" || decimalPlaces === 0 ? 1 : decimalPlaces == null ? "any" : 10 ** -decimalPlaces;
47
- control.min = getConfiguredNumber(field.MinimumValue ?? field.minimumValue ?? field.Min ?? field.min);
48
- control.max = getConfiguredNumber(field.MaximumValue ?? field.maximumValue ?? field.Max ?? field.max);
49
- }
50
- return control;
51
- }
52
- if (normalized === "note") {
53
- const richText = getRichTextEditorFieldOptions(field);
54
- return richText.contentMode === "plain" ? finish("textarea") : { ...finish("rich-text-editor"), richText };
55
- }
56
- if (normalized === "choice" || normalized === "outcomechoice") {
57
- const format = field.EditFormat ?? field.editFormat;
58
- return {
59
- ...finish(format === 1 || String(format).toLowerCase() === "radiobuttons" ? "radio-group" : "select"),
60
- choices: field.Choices ?? field.choices ?? [],
61
- allowFillIn: booleanValue(field.FillInChoice ?? field.fillInChoice)
62
- };
63
- }
64
- if (normalized === "multichoice") {
65
- return { ...finish("multi-choice"), choices: field.Choices ?? field.choices ?? [], allowFillIn: booleanValue(field.FillInChoice ?? field.fillInChoice), multiple: true };
66
- }
67
- if (normalized === "boolean")
68
- return finish("checkbox");
69
- if (normalized === "datetime") {
70
- const displayFormat = field.DisplayFormat ?? field.displayFormat;
71
- const dateMode = displayFormat === 1 || String(displayFormat).toLowerCase() === "1" || String(displayFormat).toLowerCase() === "datetime" ? "date-time" : "date";
72
- return dateMode === "date-time"
73
- ? { ...finish("input"), inputType: "datetime-local", dateMode }
74
- : { ...finish("date-picker"), dateMode };
75
- }
76
- if (["lookup", "lookupmulti", "user", "usermulti"].includes(normalized)) {
77
- const lookupList = field.LookupList ?? field.lookupList;
78
- const lookupField = field.LookupField ?? field.lookupField;
79
- const lookupSource = normalized.startsWith("lookup") && lookupList && lookupField
80
- ? { listId: lookupList, displayField: lookupField, webId: field.LookupWebId ?? field.lookupWebId }
81
- : undefined;
82
- return { ...finish("combobox"), multiple: normalized.endsWith("multi") || booleanValue(field.AllowMultipleValues ?? field.allowMultipleValues), lookupSource };
83
- }
84
- if (["counter", "computed", "calculated"].includes(normalized))
85
- return { ...finish("read-only"), readOnly: true };
86
- return { ...adapter, unsupportedReason: "This SharePoint field type needs a specialized data source or renderer." };
87
- }
88
- /** Build a bounded REST request for a list's field definitions and their type-specific metadata. */
89
- export function getSharePointFieldMetadataUrl(listWebUrl, listId, pageSize = 100) {
90
- if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 500)
91
- throw new RangeError("pageSize must be an integer from 1 to 500.");
92
- const normalizedListId = normalizeSharePointListId(listId, "listId must be a SharePoint list GUID.");
93
- const url = parseSharePointWebUrl(listWebUrl, "listWebUrl");
94
- url.pathname = `${url.pathname.replace(/\/+$/, "")}/_api/web/lists(guid'${normalizedListId}')/fields`;
95
- url.search = new URLSearchParams({ "$select": "*", "$top": String(pageSize) }).toString();
96
- url.hash = "";
97
- return url.toString();
98
- }
99
- /** Build the REST endpoint for a single item in a SharePoint list. */
100
- export function getSharePointListItemUrl(webUrl, listId, itemId) {
101
- if (!Number.isSafeInteger(itemId) || itemId < 1)
102
- throw new RangeError("itemId must be a positive safe integer.");
103
- const normalizedListId = normalizeSharePointListId(listId, "listId must be a SharePoint list GUID.");
104
- const url = parseSharePointWebUrl(webUrl, "webUrl");
105
- url.pathname = `${url.pathname.replace(/\/+$/, "")}/_api/web/lists(guid'${normalizedListId}')/items(${itemId})`;
106
- url.search = "";
107
- url.hash = "";
108
- return url.toString();
109
- }
110
- /** Build a paged REST URL for options in a lookup field's source list. Pass that list's web URL. */
111
- export function getSharePointLookupOptionsUrl(field, lookupWebUrl, pageSize = 100) {
112
- const adapter = getSharePointFieldAdapter(field);
113
- const source = adapter.lookupSource;
114
- if (adapter.control !== "combobox" || !source)
115
- throw new TypeError("This field does not include SharePoint lookup-list metadata.");
116
- if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 500)
117
- throw new RangeError("pageSize must be an integer from 1 to 500.");
118
- const listId = normalizeSharePointListId(source.listId, "LookupList must contain a SharePoint list GUID.");
119
- if (!/^[A-Za-z_][A-Za-z\d_]*$/.test(source.displayField))
120
- throw new TypeError("LookupField must be a SharePoint internal field name.");
121
- const url = parseSharePointWebUrl(lookupWebUrl, "lookupWebUrl");
122
- url.pathname = `${url.pathname.replace(/\/+$/, "")}/_api/web/lists(guid'${listId}')/items`;
123
- url.search = new URLSearchParams({
124
- "$select": `Id,${source.displayField}`,
125
- "$orderby": `${source.displayField} asc`,
126
- "$top": String(pageSize)
127
- }).toString();
128
- url.hash = "";
129
- return url.toString();
130
- }
131
- /** Resolve a SharePoint OData next link and reject links that would send host credentials to another origin. */
132
- export function getSharePointNextPageUrl(webUrl, nextLink) {
133
- if (nextLink == null)
134
- return null;
135
- if (typeof nextLink !== "string")
136
- throw new TypeError("nextLink must be a URL string or null.");
137
- if (nextLink.trim() === "")
138
- return null;
139
- const base = new URL(webUrl);
140
- if (!/^https?:$/.test(base.protocol) || base.username || base.password)
141
- throw new TypeError("webUrl must be an HTTP(S) SharePoint web URL.");
142
- const next = new URL(nextLink, base);
143
- if (!/^https?:$/.test(next.protocol) || next.origin !== base.origin || next.username || next.password) {
144
- throw new TypeError("SharePoint next links must resolve to the same HTTP(S) origin as webUrl.");
145
- }
146
- next.hash = "";
147
- return next.toString();
148
- }
149
- /** Serialize selected SharePoint lookup/person IDs for an item create or update body. */
150
- export function serializeSharePointLookupValue(field, value) {
151
- const adapter = getSharePointFieldAdapter(field);
152
- const type = adapter.fieldType.replace(/^SP\.Field/, "").toLowerCase();
153
- if (adapter.control !== "combobox" || !["lookup", "lookupmulti", "user", "usermulti"].includes(type)) {
154
- throw new TypeError("Only SharePoint Lookup and User fields can be serialized as ID selections.");
155
- }
156
- if (!adapter.name)
157
- throw new TypeError("The SharePoint field must include InternalName.");
158
- assertSharePointFieldValueWritable(adapter, value);
159
- const key = `${adapter.name}Id`;
160
- if (adapter.multiple) {
161
- if (value == null)
162
- return { [key]: { results: [] } };
163
- if (!isLookupIdArray(value))
164
- throw new TypeError("A multi-value field requires an array of selected IDs.");
165
- return { [key]: { results: [...new Set(value.map(parseLookupId))] } };
166
- }
167
- if (isLookupIdArray(value))
168
- throw new TypeError("A single-value field accepts one selected ID or null.");
169
- return { [key]: value == null || value === "" ? null : parseLookupId(value) };
170
- }
171
- /** Serialize SharePoint Choice and MultiChoice values for an item create or update body. */
172
- export function serializeSharePointChoiceValue(field, value) {
173
- const adapter = getSharePointFieldAdapter(field);
174
- const type = adapter.fieldType.replace(/^SP\.Field/, "").toLowerCase();
175
- const multiple = type === "multichoice";
176
- if (!["choice", "outcomechoice", "multichoice"].includes(type)) {
177
- throw new TypeError("Only SharePoint Choice and MultiChoice fields can be serialized as choice values.");
178
- }
179
- if (!adapter.name)
180
- throw new TypeError("The SharePoint field must include InternalName.");
181
- assertSharePointFieldValueWritable(adapter, value);
182
- const choices = field.Choices ?? field.choices ?? [];
183
- const allowFillIn = booleanValue(field.FillInChoice ?? field.fillInChoice);
184
- const validateChoice = (choice) => {
185
- if (choices.length && !choices.includes(choice) && !allowFillIn)
186
- throw new RangeError(`“${choice}” is not an allowed value for ${adapter.label}.`);
187
- return choice;
188
- };
189
- if (multiple) {
190
- if (value == null)
191
- return { [adapter.name]: { results: [] } };
192
- if (!isChoiceValueArray(value))
193
- throw new TypeError("A MultiChoice field requires an array of selected values.");
194
- return { [adapter.name]: { results: [...new Set(value.map(validateChoice))] } };
195
- }
196
- if (isChoiceValueArray(value))
197
- throw new TypeError("A Choice field accepts one value or null.");
198
- return { [adapter.name]: value == null || value === "" ? null : validateChoice(value) };
199
- }
200
- /** Serialize a common SharePoint list-field value into a REST item create/update body. */
201
- export function serializeSharePointFieldValue(field, value) {
202
- const adapter = getSharePointFieldAdapter(field);
203
- const type = adapter.fieldType.replace(/^SP\.Field/, "").toLowerCase();
204
- if (!adapter.name)
205
- throw new TypeError("The SharePoint field must include InternalName.");
206
- if (["lookup", "lookupmulti", "user", "usermulti"].includes(type)) {
207
- return serializeSharePointLookupValue(field, value);
208
- }
209
- if (["choice", "outcomechoice", "multichoice"].includes(type)) {
210
- return serializeSharePointChoiceValue(field, value);
211
- }
212
- assertSharePointFieldValueWritable(adapter, value);
213
- if (["text", "note"].includes(type)) {
214
- if (value != null && typeof value !== "string")
215
- throw new TypeError(`${adapter.label} requires a string value or null.`);
216
- const maxLength = getConfiguredMaxLength(field);
217
- if (typeof value === "string" && maxLength != null && value.length > maxLength)
218
- throw new RangeError(`${adapter.label} allows at most ${maxLength} characters.`);
219
- return { [adapter.name]: value };
220
- }
221
- if (type === "boolean") {
222
- if (value != null && typeof value !== "boolean")
223
- throw new TypeError(`${adapter.label} requires a boolean value or null.`);
224
- return { [adapter.name]: value };
225
- }
226
- if (["integer", "number", "currency"].includes(type)) {
227
- if (value == null || value === "")
228
- return { [adapter.name]: null };
229
- const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" && /^[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?$/i.test(value.trim()) ? Number(value) : NaN;
230
- if (!Number.isFinite(numeric) || (type === "integer" && !Number.isSafeInteger(numeric))) {
231
- throw new TypeError(`${adapter.label} requires a ${type === "integer" ? "safe integer" : "finite number"}, numeric string, or null.`);
232
- }
233
- const allowedDecimals = type === "integer" ? 0 : getConfiguredDecimalPlaces(field);
234
- const precisionSource = typeof value === "string" || typeof value === "number" ? value : numeric;
235
- if (allowedDecimals != null && getNumericDecimalPlaces(precisionSource) > allowedDecimals) {
236
- throw new RangeError(`${adapter.label} allows at most ${allowedDecimals} decimal places.`);
237
- }
238
- const minimum = getConfiguredNumber(field.MinimumValue ?? field.minimumValue ?? field.Min ?? field.min);
239
- const maximum = getConfiguredNumber(field.MaximumValue ?? field.maximumValue ?? field.Max ?? field.max);
240
- if (minimum != null && numeric < minimum)
241
- throw new RangeError(`${adapter.label} must be at least ${minimum}.`);
242
- if (maximum != null && numeric > maximum)
243
- throw new RangeError(`${adapter.label} must be at most ${maximum}.`);
244
- return { [adapter.name]: numeric };
245
- }
246
- if (type === "datetime") {
247
- if (value == null || value === "")
248
- return { [adapter.name]: null };
249
- if (value instanceof Date) {
250
- if (!Number.isFinite(value.getTime()))
251
- throw new TypeError(`${adapter.label} requires a valid date, ISO date string, or null.`);
252
- return { [adapter.name]: value.toISOString() };
253
- }
254
- if (typeof value !== "string")
255
- throw new TypeError(`${adapter.label} requires a valid date, ISO date string, or null.`);
256
- const iso = serializeSharePointDateValue(value);
257
- if (!iso)
258
- throw new TypeError(`${adapter.label} requires a valid ISO date or date-time string.`);
259
- return { [adapter.name]: iso };
260
- }
261
- if (type === "url") {
262
- if (value == null || value === "")
263
- return { [adapter.name]: null };
264
- const link = typeof value === "string" ? { url: value, description: value.trim() } : isSharePointUrlValue(value) ? value : undefined;
265
- if (!link)
266
- throw new TypeError(`${adapter.label} requires a URL string, URL value, or null.`);
267
- const url = link.url.trim();
268
- const maxLength = Math.min(255, getConfiguredMaxLength(field) ?? 255);
269
- if (url.length > maxLength)
270
- throw new RangeError(`${adapter.label} URL cannot exceed ${maxLength} characters.`);
271
- if (url && !isSharePointFieldUrl(url))
272
- throw new TypeError(`${adapter.label} must be an HTTP(S) or server-relative URL.`);
273
- const description = link.description ?? url;
274
- if (typeof description !== "string")
275
- throw new TypeError(`${adapter.label} URL description must be a string.`);
276
- if (description.length > 255)
277
- throw new RangeError(`${adapter.label} URL description cannot exceed 255 characters.`);
278
- return { [adapter.name]: { __metadata: { type: "SP.FieldUrlValue" }, Url: url, Description: description } };
279
- }
280
- throw new TypeError(adapter.unsupportedReason ?? `${adapter.fieldType} fields cannot be serialized by this helper.`);
281
- }
282
- /** Build one SharePoint item patch from values keyed by field InternalName; omitted values stay untouched. */
283
- export function serializeSharePointFieldValues(fields, values) {
284
- const update = {};
285
- const fieldsByName = new Map();
286
- for (const field of fields) {
287
- const adapter = getSharePointFieldAdapter(field);
288
- if (!adapter.name)
289
- throw new TypeError("Every SharePoint field must include InternalName.");
290
- if (fieldsByName.has(adapter.name))
291
- throw new TypeError(`Duplicate SharePoint field InternalName: ${adapter.name}.`);
292
- fieldsByName.set(adapter.name, { field, adapter });
293
- }
294
- for (const [name, value] of Object.entries(values)) {
295
- const entry = fieldsByName.get(name);
296
- if (!entry)
297
- throw new TypeError(`No SharePoint field metadata was supplied for ${name}.`);
298
- const { field, adapter } = entry;
299
- if (value === undefined) {
300
- if (adapter.required)
301
- throw new TypeError(`${adapter.label} is required.`);
302
- continue;
303
- }
304
- for (const [name, serializedValue] of Object.entries(serializeSharePointFieldValue(field, value))) {
305
- if (Object.prototype.hasOwnProperty.call(update, name))
306
- throw new TypeError(`Multiple SharePoint fields map to update property ${name}.`);
307
- Object.defineProperty(update, name, { configurable: true, enumerable: true, value: serializedValue, writable: true });
308
- }
309
- }
310
- return update;
311
- }
312
- function isLookupIdArray(value) {
313
- return Array.isArray(value);
314
- }
315
- function isChoiceValueArray(value) {
316
- return Array.isArray(value);
317
- }
318
- function isSharePointFieldUrl(value) {
319
- if (value.startsWith("/") && !value.startsWith("//"))
320
- return true;
321
- try {
322
- const url = new URL(value);
323
- return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
324
- }
325
- catch {
326
- return false;
327
- }
328
- }
329
- function normalizeSharePointListId(value, errorMessage) {
330
- const normalized = value.trim().replace(/[{}]/g, "");
331
- if (!/^[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}$/i.test(normalized))
332
- throw new TypeError(errorMessage);
333
- return normalized;
334
- }
335
- function parseSharePointWebUrl(value, label) {
336
- const url = new URL(value);
337
- if (!/^https?:$/.test(url.protocol) || url.username || url.password)
338
- throw new TypeError(`${label} must be an HTTP(S) SharePoint web URL.`);
339
- return url;
340
- }
341
- function isSharePointUrlValue(value) {
342
- return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && typeof value.url === "string";
343
- }
344
- function isEmptySharePointFieldValue(value) {
345
- if (value == null)
346
- return true;
347
- if (typeof value === "string")
348
- return value.trim() === "";
349
- if (Array.isArray(value))
350
- return value.length === 0;
351
- return isSharePointUrlValue(value) && value.url.trim() === "";
352
- }
353
- function assertSharePointFieldValueWritable(adapter, value) {
354
- if (adapter.readOnly)
355
- throw new TypeError(`${adapter.label} is read-only and cannot be serialized for an item update.`);
356
- if (adapter.required && isEmptySharePointFieldValue(value))
357
- throw new TypeError(`${adapter.label} is required.`);
358
- }
359
- function serializeSharePointDateValue(value) {
360
- const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
361
- const dateTime = /^(\d{4})-(\d{2})-(\d{2})T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(Z|[+-]\d{2}:\d{2})?$/.exec(value);
362
- const match = dateOnly ?? dateTime;
363
- if (!match)
364
- return undefined;
365
- const day = new Date(`${match[1]}-${match[2]}-${match[3]}T00:00:00.000Z`);
366
- if (!Number.isFinite(day.getTime()) || day.toISOString().slice(0, 10) !== `${match[1]}-${match[2]}-${match[3]}`)
367
- return undefined;
368
- const parsed = new Date(value);
369
- if (!Number.isFinite(parsed.getTime()))
370
- return undefined;
371
- if (dateOnly || dateTime?.[4])
372
- return value;
373
- return parsed.toISOString();
374
- }
375
- function getConfiguredDecimalPlaces(field) {
376
- const decimals = field.Decimals ?? field.decimals;
377
- const value = typeof decimals === "number" ? decimals : typeof decimals === "string" && /^\d+$/.test(decimals.trim()) ? Number(decimals) : undefined;
378
- return value != null && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
379
- }
380
- function getConfiguredMaxLength(field) {
381
- const value = field.MaxLength ?? field.maxLength;
382
- const parsed = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : undefined;
383
- return parsed != null && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
384
- }
385
- function getConfiguredNumber(value) {
386
- const parsed = typeof value === "number" ? value : typeof value === "string" && /^[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?$/i.test(value.trim()) ? Number(value.trim()) : undefined;
387
- return parsed != null && Number.isFinite(parsed) ? parsed : undefined;
388
- }
389
- function getNumericDecimalPlaces(value) {
390
- const [coefficient, exponentText] = String(value).trim().toLowerCase().split("e");
391
- const [integer = "", fraction = ""] = coefficient.split(".");
392
- const exponent = Number(exponentText ?? 0);
393
- const significantFractionLength = fraction.replace(/0+$/, "").length;
394
- const removableIntegerZeros = exponent < 0 ? integer.match(/0+$/)?.[0].length ?? 0 : 0;
395
- return Math.max(0, significantFractionLength - exponent - removableIntegerZeros);
396
- }
397
- function parseLookupId(value) {
398
- const id = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value.trim()) : value;
399
- if (typeof id !== "number" || !Number.isSafeInteger(id) || id < 1)
400
- throw new RangeError("SharePoint lookup IDs must be positive safe integers.");
401
- return id;
402
- }
@@ -1,80 +0,0 @@
1
- import * as React from "react";
2
- export type VirtualizerMeasureProps = {
3
- defaultItemSize: number;
4
- direction?: "vertical" | "horizontal";
5
- bufferItems?: number;
6
- bufferSize?: number;
7
- };
8
- export type VirtualizerContextMeasureProps = {
9
- contextIndex: number;
10
- setContextIndex: (index: number) => void;
11
- childProgressiveSizes: React.MutableRefObject<number[]>;
12
- };
13
- export type VirtualizerMeasureDynamicProps = VirtualizerMeasureProps & {
14
- virtualizerContext: VirtualizerContextMeasureProps;
15
- numItems: number;
16
- getItemSize: (index: number) => number;
17
- };
18
- export interface IndexedResizeCallbackElement {
19
- handleResize: () => void;
20
- }
21
- /** Measure a fixed-size viewport and calculate a bounded item window. */
22
- export declare function useStaticVirtualizerMeasure<TElement extends HTMLElement = HTMLElement>(props: VirtualizerMeasureProps): {
23
- virtualizerLength: number;
24
- bufferItems: number;
25
- bufferSize: number;
26
- scrollRef: (element: TElement | null) => void;
27
- containerSizeRef: React.MutableRefObject<number>;
28
- };
29
- /** Measure only enough variable-size rows to cover the current scroll viewport. */
30
- export declare function useDynamicVirtualizerMeasure<TElement extends HTMLElement = HTMLElement>(props: VirtualizerMeasureDynamicProps): {
31
- virtualizerLength: number;
32
- bufferItems: number;
33
- bufferSize: number;
34
- scrollRef: (element: TElement | null) => void;
35
- containerSizeRef: React.RefObject<number>;
36
- updateScrollPosition: (position: number) => void;
37
- };
38
- /** Track measured widths/heights by item index for legacy dynamic virtualizer composition. */
39
- export declare function useMeasureList<TElement extends HTMLElement & IndexedResizeCallbackElement = HTMLElement & IndexedResizeCallbackElement>(currentIndex: number, _refLength: number, totalLength: number, defaultItemSize: number): {
40
- widthArray: React.MutableRefObject<number[]>;
41
- heightArray: React.MutableRefObject<number[]>;
42
- createIndexedRef: (index: number) => (element: TElement | null) => void;
43
- refArray: React.MutableRefObject<Array<TElement | undefined | null>>;
44
- sizeUpdateCount: number;
45
- };
46
- export type ResizeCallbackWithRef = (entries: ResizeObserverEntry[], observer: ResizeObserver, scrollRef?: React.MutableRefObject<HTMLElement | null>) => void;
47
- export type ScrollToItemStaticParams = {
48
- index: number;
49
- itemSize: number;
50
- totalItems: number;
51
- scrollViewRef: React.RefObject<HTMLElement | null>;
52
- axis?: "horizontal" | "vertical";
53
- reversed?: boolean;
54
- behavior?: ScrollBehavior;
55
- };
56
- export type ScrollToItemDynamicParams = {
57
- index: number;
58
- itemSizes: React.RefObject<number[] | null>;
59
- totalSize: number;
60
- scrollViewRef: React.RefObject<HTMLElement | null>;
61
- axis?: "horizontal" | "vertical";
62
- reversed?: boolean;
63
- behavior?: ScrollBehavior;
64
- };
65
- /** Create an observer from the document's own window, including when an SPFx part is rendered in an iframe. */
66
- export declare function createResizeObserverFromDocument(targetDocument: Document | null | undefined, callback: ResizeObserverCallback): ResizeObserver | null;
67
- /** Mirror a four-sided LTR observer margin for an RTL root. */
68
- export declare function getRTLRootMargin(ltrRootMargin: string, target?: Element | Document | null, win?: Window | null): string;
69
- /** Observe a changing element list with native IntersectionObserver and direction-aware margins. */
70
- export declare function useIntersectionObserver(callback: IntersectionObserverCallback, options?: IntersectionObserverInit): {
71
- setObserverList: React.Dispatch<React.SetStateAction<Element[] | undefined>>;
72
- setObserverInit: (next: IntersectionObserverInit | undefined) => void;
73
- observer: React.MutableRefObject<IntersectionObserver | undefined>;
74
- };
75
- /** Attach a resize observer callback ref; it is safe when ResizeObserver is unavailable. */
76
- export declare function useResizeObserverRef_unstable(resizeCallback: ResizeCallbackWithRef): (instance: HTMLElement | null) => void;
77
- /** Scroll an element to a fixed-size virtual item. */
78
- export declare function scrollToItemStatic({ index, itemSize, totalItems, scrollViewRef, axis, reversed, behavior }: ScrollToItemStaticParams): void;
79
- /** Scroll an element to a measured item; summing the prefix is O(index), so keep the measured list modest. */
80
- export declare function scrollToItemDynamic({ index, itemSizes, totalSize, scrollViewRef, axis, reversed, behavior }: ScrollToItemDynamicParams): void;