@apvee/spfx-react-toolkit 1.0.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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2012 -0
  3. package/lib/core/atoms.internal.d.ts +53 -0
  4. package/lib/core/atoms.internal.d.ts.map +1 -0
  5. package/lib/core/atoms.internal.js +35 -0
  6. package/lib/core/atoms.internal.js.map +1 -0
  7. package/lib/core/context.internal.d.ts +23 -0
  8. package/lib/core/context.internal.d.ts.map +1 -0
  9. package/lib/core/context.internal.js +34 -0
  10. package/lib/core/context.internal.js.map +1 -0
  11. package/lib/core/index.d.ts +6 -0
  12. package/lib/core/index.d.ts.map +1 -0
  13. package/lib/core/index.js +6 -0
  14. package/lib/core/index.js.map +1 -0
  15. package/lib/core/provider-application-customizer.d.ts +57 -0
  16. package/lib/core/provider-application-customizer.d.ts.map +1 -0
  17. package/lib/core/provider-application-customizer.js +45 -0
  18. package/lib/core/provider-application-customizer.js.map +1 -0
  19. package/lib/core/provider-base.internal.d.ts +20 -0
  20. package/lib/core/provider-base.internal.d.ts.map +1 -0
  21. package/lib/core/provider-base.internal.js +126 -0
  22. package/lib/core/provider-base.internal.js.map +1 -0
  23. package/lib/core/provider-field-customizer.d.ts +58 -0
  24. package/lib/core/provider-field-customizer.d.ts.map +1 -0
  25. package/lib/core/provider-field-customizer.js +46 -0
  26. package/lib/core/provider-field-customizer.js.map +1 -0
  27. package/lib/core/provider-listview-commandset.d.ts +60 -0
  28. package/lib/core/provider-listview-commandset.d.ts.map +1 -0
  29. package/lib/core/provider-listview-commandset.js +48 -0
  30. package/lib/core/provider-listview-commandset.js.map +1 -0
  31. package/lib/core/provider-webpart.d.ts +48 -0
  32. package/lib/core/provider-webpart.d.ts.map +1 -0
  33. package/lib/core/provider-webpart.js +36 -0
  34. package/lib/core/provider-webpart.js.map +1 -0
  35. package/lib/core/types.d.ts +84 -0
  36. package/lib/core/types.d.ts.map +1 -0
  37. package/lib/core/types.js +4 -0
  38. package/lib/core/types.js.map +1 -0
  39. package/lib/hooks/index.d.ts +34 -0
  40. package/lib/hooks/index.d.ts.map +1 -0
  41. package/lib/hooks/index.js +34 -0
  42. package/lib/hooks/index.js.map +1 -0
  43. package/lib/hooks/useSPFxAadHttpClient.d.ts +231 -0
  44. package/lib/hooks/useSPFxAadHttpClient.d.ts.map +1 -0
  45. package/lib/hooks/useSPFxAadHttpClient.js +299 -0
  46. package/lib/hooks/useSPFxAadHttpClient.js.map +1 -0
  47. package/lib/hooks/useSPFxContainerInfo.d.ts +41 -0
  48. package/lib/hooks/useSPFxContainerInfo.d.ts.map +1 -0
  49. package/lib/hooks/useSPFxContainerInfo.js +47 -0
  50. package/lib/hooks/useSPFxContainerInfo.js.map +1 -0
  51. package/lib/hooks/useSPFxContainerSize.d.ts +119 -0
  52. package/lib/hooks/useSPFxContainerSize.d.ts.map +1 -0
  53. package/lib/hooks/useSPFxContainerSize.js +150 -0
  54. package/lib/hooks/useSPFxContainerSize.js.map +1 -0
  55. package/lib/hooks/useSPFxContext.d.ts +14 -0
  56. package/lib/hooks/useSPFxContext.d.ts.map +1 -0
  57. package/lib/hooks/useSPFxContext.js +16 -0
  58. package/lib/hooks/useSPFxContext.js.map +1 -0
  59. package/lib/hooks/useSPFxCorrelationInfo.d.ts +51 -0
  60. package/lib/hooks/useSPFxCorrelationInfo.d.ts.map +1 -0
  61. package/lib/hooks/useSPFxCorrelationInfo.js +58 -0
  62. package/lib/hooks/useSPFxCorrelationInfo.js.map +1 -0
  63. package/lib/hooks/useSPFxCrossSitePermissions.d.ts +81 -0
  64. package/lib/hooks/useSPFxCrossSitePermissions.d.ts.map +1 -0
  65. package/lib/hooks/useSPFxCrossSitePermissions.js +132 -0
  66. package/lib/hooks/useSPFxCrossSitePermissions.js.map +1 -0
  67. package/lib/hooks/useSPFxDisplayMode.d.ts +61 -0
  68. package/lib/hooks/useSPFxDisplayMode.d.ts.map +1 -0
  69. package/lib/hooks/useSPFxDisplayMode.js +69 -0
  70. package/lib/hooks/useSPFxDisplayMode.js.map +1 -0
  71. package/lib/hooks/useSPFxEnvironmentInfo.d.ts +63 -0
  72. package/lib/hooks/useSPFxEnvironmentInfo.d.ts.map +1 -0
  73. package/lib/hooks/useSPFxEnvironmentInfo.js +91 -0
  74. package/lib/hooks/useSPFxEnvironmentInfo.js.map +1 -0
  75. package/lib/hooks/useSPFxFluent9ThemeInfo.d.ts +105 -0
  76. package/lib/hooks/useSPFxFluent9ThemeInfo.d.ts.map +1 -0
  77. package/lib/hooks/useSPFxFluent9ThemeInfo.js +136 -0
  78. package/lib/hooks/useSPFxFluent9ThemeInfo.js.map +1 -0
  79. package/lib/hooks/useSPFxHubSiteInfo.d.ts +80 -0
  80. package/lib/hooks/useSPFxHubSiteInfo.d.ts.map +1 -0
  81. package/lib/hooks/useSPFxHubSiteInfo.js +127 -0
  82. package/lib/hooks/useSPFxHubSiteInfo.js.map +1 -0
  83. package/lib/hooks/useSPFxInstanceInfo.d.ts +41 -0
  84. package/lib/hooks/useSPFxInstanceInfo.d.ts.map +1 -0
  85. package/lib/hooks/useSPFxInstanceInfo.js +40 -0
  86. package/lib/hooks/useSPFxInstanceInfo.js.map +1 -0
  87. package/lib/hooks/useSPFxListInfo.d.ts +64 -0
  88. package/lib/hooks/useSPFxListInfo.d.ts.map +1 -0
  89. package/lib/hooks/useSPFxListInfo.js +70 -0
  90. package/lib/hooks/useSPFxListInfo.js.map +1 -0
  91. package/lib/hooks/useSPFxLocaleInfo.d.ts +123 -0
  92. package/lib/hooks/useSPFxLocaleInfo.d.ts.map +1 -0
  93. package/lib/hooks/useSPFxLocaleInfo.js +109 -0
  94. package/lib/hooks/useSPFxLocaleInfo.js.map +1 -0
  95. package/lib/hooks/useSPFxLogger.d.ts +108 -0
  96. package/lib/hooks/useSPFxLogger.d.ts.map +1 -0
  97. package/lib/hooks/useSPFxLogger.js +117 -0
  98. package/lib/hooks/useSPFxLogger.js.map +1 -0
  99. package/lib/hooks/useSPFxMSGraphClient.d.ts +200 -0
  100. package/lib/hooks/useSPFxMSGraphClient.d.ts.map +1 -0
  101. package/lib/hooks/useSPFxMSGraphClient.js +264 -0
  102. package/lib/hooks/useSPFxMSGraphClient.js.map +1 -0
  103. package/lib/hooks/useSPFxOneDriveAppData.d.ts +264 -0
  104. package/lib/hooks/useSPFxOneDriveAppData.d.ts.map +1 -0
  105. package/lib/hooks/useSPFxOneDriveAppData.js +395 -0
  106. package/lib/hooks/useSPFxOneDriveAppData.js.map +1 -0
  107. package/lib/hooks/useSPFxPageContext.d.ts +37 -0
  108. package/lib/hooks/useSPFxPageContext.d.ts.map +1 -0
  109. package/lib/hooks/useSPFxPageContext.js +49 -0
  110. package/lib/hooks/useSPFxPageContext.js.map +1 -0
  111. package/lib/hooks/useSPFxPageType.d.ts +82 -0
  112. package/lib/hooks/useSPFxPageType.d.ts.map +1 -0
  113. package/lib/hooks/useSPFxPageType.js +137 -0
  114. package/lib/hooks/useSPFxPageType.js.map +1 -0
  115. package/lib/hooks/useSPFxPerformance.d.ts +72 -0
  116. package/lib/hooks/useSPFxPerformance.d.ts.map +1 -0
  117. package/lib/hooks/useSPFxPerformance.js +167 -0
  118. package/lib/hooks/useSPFxPerformance.js.map +1 -0
  119. package/lib/hooks/useSPFxPermissions.d.ts +61 -0
  120. package/lib/hooks/useSPFxPermissions.d.ts.map +1 -0
  121. package/lib/hooks/useSPFxPermissions.js +73 -0
  122. package/lib/hooks/useSPFxPermissions.js.map +1 -0
  123. package/lib/hooks/useSPFxPnP.d.ts +539 -0
  124. package/lib/hooks/useSPFxPnP.d.ts.map +1 -0
  125. package/lib/hooks/useSPFxPnP.js +533 -0
  126. package/lib/hooks/useSPFxPnP.js.map +1 -0
  127. package/lib/hooks/useSPFxPnPContext.d.ts +290 -0
  128. package/lib/hooks/useSPFxPnPContext.d.ts.map +1 -0
  129. package/lib/hooks/useSPFxPnPContext.js +340 -0
  130. package/lib/hooks/useSPFxPnPContext.js.map +1 -0
  131. package/lib/hooks/useSPFxPnPList.d.ts +545 -0
  132. package/lib/hooks/useSPFxPnPList.d.ts.map +1 -0
  133. package/lib/hooks/useSPFxPnPList.js +906 -0
  134. package/lib/hooks/useSPFxPnPList.js.map +1 -0
  135. package/lib/hooks/useSPFxPnPSearch.d.ts +540 -0
  136. package/lib/hooks/useSPFxPnPSearch.d.ts.map +1 -0
  137. package/lib/hooks/useSPFxPnPSearch.js +672 -0
  138. package/lib/hooks/useSPFxPnPSearch.js.map +1 -0
  139. package/lib/hooks/useSPFxProperties.d.ts +80 -0
  140. package/lib/hooks/useSPFxProperties.d.ts.map +1 -0
  141. package/lib/hooks/useSPFxProperties.js +95 -0
  142. package/lib/hooks/useSPFxProperties.js.map +1 -0
  143. package/lib/hooks/useSPFxSPHttpClient.d.ts +218 -0
  144. package/lib/hooks/useSPFxSPHttpClient.d.ts.map +1 -0
  145. package/lib/hooks/useSPFxSPHttpClient.js +287 -0
  146. package/lib/hooks/useSPFxSPHttpClient.js.map +1 -0
  147. package/lib/hooks/useSPFxServiceScope.d.ts +107 -0
  148. package/lib/hooks/useSPFxServiceScope.d.ts.map +1 -0
  149. package/lib/hooks/useSPFxServiceScope.js +105 -0
  150. package/lib/hooks/useSPFxServiceScope.js.map +1 -0
  151. package/lib/hooks/useSPFxSiteInfo.d.ts +116 -0
  152. package/lib/hooks/useSPFxSiteInfo.d.ts.map +1 -0
  153. package/lib/hooks/useSPFxSiteInfo.js +109 -0
  154. package/lib/hooks/useSPFxSiteInfo.js.map +1 -0
  155. package/lib/hooks/useSPFxStorage.d.ts +81 -0
  156. package/lib/hooks/useSPFxStorage.d.ts.map +1 -0
  157. package/lib/hooks/useSPFxStorage.js +140 -0
  158. package/lib/hooks/useSPFxStorage.js.map +1 -0
  159. package/lib/hooks/useSPFxTeams.d.ts +63 -0
  160. package/lib/hooks/useSPFxTeams.d.ts.map +1 -0
  161. package/lib/hooks/useSPFxTeams.js +198 -0
  162. package/lib/hooks/useSPFxTeams.js.map +1 -0
  163. package/lib/hooks/useSPFxTenantProperty.d.ts +389 -0
  164. package/lib/hooks/useSPFxTenantProperty.d.ts.map +1 -0
  165. package/lib/hooks/useSPFxTenantProperty.js +683 -0
  166. package/lib/hooks/useSPFxTenantProperty.js.map +1 -0
  167. package/lib/hooks/useSPFxThemeInfo.d.ts +27 -0
  168. package/lib/hooks/useSPFxThemeInfo.d.ts.map +1 -0
  169. package/lib/hooks/useSPFxThemeInfo.js +33 -0
  170. package/lib/hooks/useSPFxThemeInfo.js.map +1 -0
  171. package/lib/hooks/useSPFxUserInfo.d.ts +47 -0
  172. package/lib/hooks/useSPFxUserInfo.d.ts.map +1 -0
  173. package/lib/hooks/useSPFxUserInfo.js +47 -0
  174. package/lib/hooks/useSPFxUserInfo.js.map +1 -0
  175. package/lib/hooks/useSPFxUserPhoto.d.ts +270 -0
  176. package/lib/hooks/useSPFxUserPhoto.d.ts.map +1 -0
  177. package/lib/hooks/useSPFxUserPhoto.js +346 -0
  178. package/lib/hooks/useSPFxUserPhoto.js.map +1 -0
  179. package/lib/index.d.ts +3 -0
  180. package/lib/index.d.ts.map +1 -0
  181. package/lib/index.js +3 -0
  182. package/lib/index.js.map +1 -0
  183. package/lib/utils/index.d.ts +1 -0
  184. package/lib/utils/index.d.ts.map +1 -0
  185. package/lib/utils/index.js +3 -0
  186. package/lib/utils/index.js.map +1 -0
  187. package/lib/utils/resize-observer.internal.d.ts +10 -0
  188. package/lib/utils/resize-observer.internal.d.ts.map +1 -0
  189. package/lib/utils/resize-observer.internal.js +34 -0
  190. package/lib/utils/resize-observer.internal.js.map +1 -0
  191. package/lib/utils/theme-subscription.internal.d.ts +11 -0
  192. package/lib/utils/theme-subscription.internal.d.ts.map +1 -0
  193. package/lib/utils/theme-subscription.internal.js +58 -0
  194. package/lib/utils/theme-subscription.internal.js.map +1 -0
  195. package/lib/utils/type-guards.internal.d.ts +35 -0
  196. package/lib/utils/type-guards.internal.d.ts.map +1 -0
  197. package/lib/utils/type-guards.internal.js +88 -0
  198. package/lib/utils/type-guards.internal.js.map +1 -0
  199. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.d.ts +13 -0
  200. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.d.ts.map +1 -0
  201. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js +67 -0
  202. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.js.map +1 -0
  203. package/lib/webparts/spFxReactToolkitTest/SpFxReactToolkitTestWebPart.manifest.json +21 -0
  204. package/lib/webparts/spFxReactToolkitTest/assets/welcome-dark.png +0 -0
  205. package/lib/webparts/spFxReactToolkitTest/assets/welcome-light.png +0 -0
  206. package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.d.ts +8 -0
  207. package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.d.ts.map +1 -0
  208. package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.js +2 -0
  209. package/lib/webparts/spFxReactToolkitTest/components/ISpFxReactToolkitTestProps.js.map +1 -0
  210. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts +8 -0
  211. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.d.ts.map +1 -0
  212. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js +1351 -0
  213. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.js.map +1 -0
  214. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.css +2 -0
  215. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.d.ts +18 -0
  216. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.d.ts.map +1 -0
  217. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js +19 -0
  218. package/lib/webparts/spFxReactToolkitTest/components/SpFxReactToolkitTest.module.scss.js.map +1 -0
  219. package/lib/webparts/spFxReactToolkitTest/loc/en-us.js +16 -0
  220. package/package.json +95 -0
package/README.md ADDED
@@ -0,0 +1,2012 @@
1
+ # SPFx React Toolkit
2
+
3
+ > A comprehensive React runtime and hooks library for SharePoint Framework (SPFx) with 33+ type-safe hooks. Simplifies SPFx development with instance-scoped state isolation and ergonomic hooks API across WebParts, Extensions, and Command Sets.
4
+
5
+ ![SPFx React Toolkit](./assets/banner.png)
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [Overview](#overview)
12
+ - [Features](#features)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [WebPart Setup](#webpart-setup)
16
+ - [ApplicationCustomizer Setup](#applicationcustomizer-setup)
17
+ - [FieldCustomizer Setup](#fieldcustomizer-setup)
18
+ - [ListView CommandSet Setup](#listview-commandset-setup)
19
+ - [Using Hooks in Components](#using-hooks-in-components)
20
+ - [Core API](#core-api)
21
+ - [Provider Components](#provider-components)
22
+ - [TypeScript Types](#typescript-types)
23
+ - [Hooks API](#hooks-api)
24
+ - [Context & Configuration](#context--configuration)
25
+ - [User & Site Information](#user--site-information)
26
+ - [UI & Layout](#ui--layout)
27
+ - [Storage](#storage)
28
+ - [HTTP Clients](#http-clients)
29
+ - [Performance & Diagnostics](#performance--diagnostics)
30
+ - [Permissions & Security](#permissions--security)
31
+ - [Environment & Platform](#environment--platform)
32
+ - [PnPjs Integration (Optional)](#pnpjs-integration-optional)
33
+ - [Installation](#pnpjs-installation)
34
+ - [Available PnP Hooks](#available-pnp-hooks)
35
+ - [TypeScript Support](#typescript-support)
36
+ - [Architecture](#architecture)
37
+ - [Best Practices](#best-practices)
38
+ - [Troubleshooting](#troubleshooting)
39
+ - [Compatibility](#compatibility)
40
+ - [License](#license)
41
+ - [Contributing](#contributing)
42
+ - [Support](#support)
43
+
44
+ ---
45
+
46
+ ## Overview
47
+
48
+ **SPFx React Toolkit** is a comprehensive React runtime and hooks library for SharePoint Framework (SPFx) development. It provides a single `SPFxProvider` component that wraps your application and enables access to 33+ strongly-typed, production-ready hooks for seamless integration with SPFx context, properties, HTTP clients, permissions, storage, performance tracking, and more.
49
+
50
+ Built on [Jotai](https://jotai.org/) atomic state management, this toolkit delivers per-instance state isolation, automatic synchronization, and an ergonomic React Hooks API that works across all SPFx component types: **WebParts**, **Application Customizers**, **Field Customizers**, and **ListView Command Sets**.
51
+
52
+ ### Why SPFx React Toolkit?
53
+
54
+ - **💪 Type-Safe** - Full TypeScript support with zero `any` usage
55
+ - **⚡ Optimized** - Jotai atomic state with per-instance scoping
56
+ - **🔄 Auto-Sync** - Bidirectional synchronization
57
+ - **🎨 Universal** - Works with all SPFx component types
58
+ - **📦 Modular** - Tree-shakeable, minimal bundle impact
59
+
60
+ ---
61
+
62
+ ## Features
63
+
64
+ - ✅ **Automatic Context Detection** - Detects WebPart, ApplicationCustomizer, CommandSet, or FieldCustomizer
65
+ - ✅ **33+ React Hooks** - Comprehensive API surface for all SPFx capabilities
66
+ - ✅ **Type-Safe** - Full TypeScript inference with strict typing
67
+ - ✅ **Instance Isolation** - State scoped per SPFx instance (multi-instance support)
68
+ - ✅ **Bidirectional Sync** - Properties automatically sync between UI and SPFx
69
+ - ✅ **PnPjs Integration** - Optional hooks for PnPjs v4 with type-safe filters
70
+ - ✅ **Performance Tracking** - Built-in hooks for performance measurement and logging
71
+ - ✅ **Cross-Platform** - Teams, SharePoint, and Local Workbench support
72
+
73
+ ---
74
+
75
+ ## Installation
76
+
77
+ ```bash
78
+ npm install @apvee/spfx-react-toolkit
79
+ ```
80
+
81
+ **Auto-Install (npm 7+):**
82
+ The following peer dependencies are automatically installed:
83
+ - **Jotai** v2+ - Atomic state management (lightweight ~3KB)
84
+ - **PnPjs** v4 - SharePoint API operations
85
+
86
+ ### Peer Dependencies
87
+
88
+ All peer dependencies (`jotai`, `@pnp/sp`, `@pnp/core`, `@pnp/queryable`) are installed automatically with npm 7+. However:
89
+
90
+ - ✅ **Jotai** (~3KB) - Always included, core dependency for state management
91
+ - ✅ **PnP hooks not used?** - Tree-shaking removes unused PnP code (0 KB overhead)
92
+ - ✅ **PnP hooks used?** - Only imported parts included (~30-50 KB compressed)
93
+ - ✅ **No webpack errors** - All dependencies resolved
94
+ - ✅ **No duplicate installations** - npm reuses existing compatible versions
95
+
96
+ **All hooks available from single import:**
97
+
98
+ ```typescript
99
+ import {
100
+ useSPFxProperties, // Core hooks
101
+ useSPFxContext,
102
+ useSPFxPnP, // PnP hooks
103
+ useSPFxPnPList
104
+ } from '@apvee/spfx-react-toolkit';
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Quick Start
110
+
111
+ ### WebPart Setup
112
+
113
+ In your WebPart's `render()` method, wrap your component with `SPFxWebPartProvider`:
114
+
115
+ ```typescript
116
+ import * as React from 'react';
117
+ import * as ReactDom from 'react-dom';
118
+ import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
119
+ import { SPFxWebPartProvider } from 'spfx-react-toolkit';
120
+ import MyComponent from './components/MyComponent';
121
+
122
+ export interface IMyWebPartProps {
123
+ title: string;
124
+ description: string;
125
+ }
126
+
127
+ export default class MyWebPart extends BaseClientSideWebPart<IMyWebPartProps> {
128
+ public render(): void {
129
+ const element = React.createElement(
130
+ SPFxWebPartProvider,
131
+ { instance: this }, // Type-safe, no casting needed
132
+ React.createElement(MyComponent)
133
+ );
134
+
135
+ ReactDom.render(element, this.domElement);
136
+ }
137
+
138
+ protected onDispose(): void {
139
+ ReactDom.unmountComponentAtNode(this.domElement);
140
+ }
141
+ }
142
+ ```
143
+
144
+ ### ApplicationCustomizer Setup
145
+
146
+ For Application Customizers, use `SPFxApplicationCustomizerProvider`:
147
+
148
+ ```typescript
149
+ import * as React from 'react';
150
+ import * as ReactDom from 'react-dom';
151
+ import { BaseApplicationCustomizer, PlaceholderName } from '@microsoft/sp-application-base';
152
+ import { SPFxApplicationCustomizerProvider } from 'spfx-react-toolkit';
153
+ import MyHeaderComponent from './components/MyHeaderComponent';
154
+
155
+ export default class MyApplicationCustomizer extends BaseApplicationCustomizer<IMyProps> {
156
+ public onInit(): Promise<void> {
157
+ // Get placeholder
158
+ const placeholder = this.context.placeholderProvider.tryCreateContent(
159
+ PlaceholderName.Top
160
+ );
161
+
162
+ if (placeholder) {
163
+ const element = React.createElement(
164
+ SPFxApplicationCustomizerProvider,
165
+ { instance: this },
166
+ React.createElement(MyHeaderComponent)
167
+ );
168
+
169
+ ReactDom.render(element, placeholder.domElement);
170
+ }
171
+
172
+ return Promise.resolve();
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### FieldCustomizer Setup
178
+
179
+ For Field Customizers, use `SPFxFieldCustomizerProvider`:
180
+
181
+ ```typescript
182
+ import * as React from 'react';
183
+ import * as ReactDom from 'react-dom';
184
+ import { BaseFieldCustomizer, IFieldCustomizerCellEventParameters } from '@microsoft/sp-listview-extensibility';
185
+ import { SPFxFieldCustomizerProvider } from 'spfx-react-toolkit';
186
+ import MyFieldRenderer from './components/MyFieldRenderer';
187
+
188
+ export default class MyFieldCustomizer extends BaseFieldCustomizer<IMyProps> {
189
+ public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
190
+ const element = React.createElement(
191
+ SPFxFieldCustomizerProvider,
192
+ { instance: this },
193
+ React.createElement(MyFieldRenderer, {
194
+ value: event.fieldValue,
195
+ listItem: event.listItem
196
+ })
197
+ );
198
+
199
+ ReactDom.render(element, event.domElement);
200
+ }
201
+
202
+ public onDisposeCell(event: IFieldCustomizerCellEventParameters): void {
203
+ ReactDom.unmountComponentAtNode(event.domElement);
204
+ super.onDisposeCell(event);
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### ListView CommandSet Setup
210
+
211
+ For ListView Command Sets, use `SPFxListViewCommandSetProvider`:
212
+
213
+ ```typescript
214
+ import * as React from 'react';
215
+ import * as ReactDom from 'react-dom';
216
+ import { BaseListViewCommandSet, IListViewCommandSetExecuteEventParameters } from '@microsoft/sp-listview-extensibility';
217
+ import { Dialog } from '@microsoft/sp-dialog';
218
+ import { SPFxListViewCommandSetProvider } from 'spfx-react-toolkit';
219
+ import MyDialogComponent from './components/MyDialogComponent';
220
+
221
+ export default class MyCommandSet extends BaseListViewCommandSet<IMyProps> {
222
+ public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
223
+ switch (event.itemId) {
224
+ case 'COMMAND_1':
225
+ this._showDialog(event.selectedRows);
226
+ break;
227
+ }
228
+ }
229
+
230
+ private _showDialog(selectedItems: any[]): void {
231
+ const dialogElement = document.createElement('div');
232
+ document.body.appendChild(dialogElement);
233
+
234
+ const element = React.createElement(
235
+ SPFxListViewCommandSetProvider,
236
+ { instance: this },
237
+ React.createElement(MyDialogComponent, {
238
+ items: selectedItems,
239
+ onDismiss: () => {
240
+ ReactDom.unmountComponentAtNode(dialogElement);
241
+ document.body.removeChild(dialogElement);
242
+ }
243
+ })
244
+ );
245
+
246
+ ReactDom.render(element, dialogElement);
247
+ }
248
+ }
249
+ ```
250
+
251
+ ### Using Hooks in Components
252
+
253
+ Once wrapped with a Provider, access SPFx capabilities via hooks:
254
+
255
+ ```typescript
256
+ import * as React from 'react';
257
+ import {
258
+ useSPFxProperties,
259
+ useSPFxDisplayMode,
260
+ useSPFxThemeInfo,
261
+ useSPFxUserInfo,
262
+ useSPFxSiteInfo,
263
+ } from 'spfx-react-toolkit';
264
+
265
+ interface IMyWebPartProps {
266
+ title: string;
267
+ description: string;
268
+ }
269
+
270
+ const MyComponent: React.FC = () => {
271
+ // Access and update properties
272
+ const { properties, setProperties } = useSPFxProperties<IMyWebPartProps>();
273
+
274
+ // Check display mode
275
+ const { isEdit } = useSPFxDisplayMode();
276
+
277
+ // Get theme colors
278
+ const theme = useSPFxThemeInfo();
279
+
280
+ // Get user information
281
+ const { displayName, email } = useSPFxUserInfo();
282
+
283
+ // Get site information
284
+ const { title: siteTitle, webUrl } = useSPFxSiteInfo();
285
+
286
+ return (
287
+ <div style={{
288
+ backgroundColor: theme?.semanticColors?.bodyBackground,
289
+ color: theme?.semanticColors?.bodyText,
290
+ padding: '20px'
291
+ }}>
292
+ <h1>{properties?.title || 'Default Title'}</h1>
293
+ <p>{properties?.description}</p>
294
+ <p>Welcome, {displayName} ({email})</p>
295
+ <p>Site: {siteTitle} - {webUrl}</p>
296
+
297
+ {isEdit && (
298
+ <button onClick={() => setProperties({ title: 'Updated Title' })}>
299
+ Update Title
300
+ </button>
301
+ )}
302
+ </div>
303
+ );
304
+ };
305
+
306
+ export default MyComponent;
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Core API
312
+
313
+ ### Provider Components
314
+
315
+ The toolkit provides **4 type-safe Provider components**, one for each SPFx component type. Each Provider automatically detects the component kind, initializes instance-scoped state, and enables all hooks.
316
+
317
+ #### `SPFxWebPartProvider<TProps>`
318
+
319
+ Type-safe provider for **WebParts**.
320
+
321
+ **Props:**
322
+ - `instance: BaseClientSideWebPart<TProps>` - WebPart instance
323
+ - `children?: React.ReactNode` - Child components
324
+
325
+ **Example:**
326
+ ```typescript
327
+ import { SPFxWebPartProvider } from 'spfx-react-toolkit';
328
+
329
+ export default class MyWebPart extends BaseClientSideWebPart<IMyWebPartProps> {
330
+ public render(): void {
331
+ const element = React.createElement(
332
+ SPFxWebPartProvider,
333
+ { instance: this },
334
+ React.createElement(MyComponent)
335
+ );
336
+ ReactDom.render(element, this.domElement);
337
+ }
338
+ }
339
+ ```
340
+
341
+ #### `SPFxApplicationCustomizerProvider<TProps>`
342
+
343
+ Type-safe provider for **Application Customizers**.
344
+
345
+ **Props:**
346
+ - `instance: BaseApplicationCustomizer<TProps>` - Application Customizer instance
347
+ - `children?: React.ReactNode` - Child components
348
+
349
+ **Example:**
350
+ ```typescript
351
+ import { SPFxApplicationCustomizerProvider } from 'spfx-react-toolkit';
352
+
353
+ export default class MyApplicationCustomizer extends BaseApplicationCustomizer<IMyProps> {
354
+ public onInit(): Promise<void> {
355
+ const placeholder = this.context.placeholderProvider.tryCreateContent(
356
+ PlaceholderName.Top
357
+ );
358
+
359
+ if (placeholder) {
360
+ const element = React.createElement(
361
+ SPFxApplicationCustomizerProvider,
362
+ { instance: this },
363
+ React.createElement(MyHeaderComponent)
364
+ );
365
+ ReactDom.render(element, placeholder.domElement);
366
+ }
367
+
368
+ return Promise.resolve();
369
+ }
370
+ }
371
+ ```
372
+
373
+ #### `SPFxFieldCustomizerProvider<TProps>`
374
+
375
+ Type-safe provider for **Field Customizers**.
376
+
377
+ **Props:**
378
+ - `instance: BaseFieldCustomizer<TProps>` - Field Customizer instance
379
+ - `children?: React.ReactNode` - Child components
380
+
381
+ **Example:**
382
+ ```typescript
383
+ import { SPFxFieldCustomizerProvider } from 'spfx-react-toolkit';
384
+
385
+ export default class MyFieldCustomizer extends BaseFieldCustomizer<IMyProps> {
386
+ public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
387
+ const element = React.createElement(
388
+ SPFxFieldCustomizerProvider,
389
+ { instance: this },
390
+ React.createElement(MyFieldRenderer, { value: event.fieldValue })
391
+ );
392
+ ReactDom.render(element, event.domElement);
393
+ }
394
+ }
395
+ ```
396
+
397
+ #### `SPFxListViewCommandSetProvider<TProps>`
398
+
399
+ Type-safe provider for **ListView Command Sets**.
400
+
401
+ **Props:**
402
+ - `instance: BaseListViewCommandSet<TProps>` - ListView Command Set instance
403
+ - `children?: React.ReactNode` - Child components
404
+
405
+ **Example:**
406
+ ```typescript
407
+ import { SPFxListViewCommandSetProvider } from 'spfx-react-toolkit';
408
+
409
+ export default class MyCommandSet extends BaseListViewCommandSet<IMyProps> {
410
+ public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
411
+ const element = React.createElement(
412
+ SPFxListViewCommandSetProvider,
413
+ { instance: this },
414
+ React.createElement(MyDialogComponent)
415
+ );
416
+ // Render to dialog container
417
+ }
418
+ }
419
+ ```
420
+
421
+ ### TypeScript Types
422
+
423
+ All Provider props and hook return types are fully typed and exported:
424
+
425
+ ```typescript
426
+ import type {
427
+ // Provider Props
428
+ SPFxWebPartProviderProps,
429
+ SPFxApplicationCustomizerProviderProps,
430
+ SPFxFieldCustomizerProviderProps,
431
+ SPFxListViewCommandSetProviderProps,
432
+
433
+ // Core Context Types
434
+ HostKind, // 'WebPart' | 'AppCustomizer' | 'FieldCustomizer' | 'CommandSet' | 'ACE'
435
+ SPFxComponent, // Union of all SPFx component types
436
+ SPFxContextType, // Union of all SPFx context types
437
+ SPFxContextValue, // Context value: { instanceId, spfxContext, kind }
438
+ ContainerSize, // { width: number, height: number }
439
+
440
+ // Hook Return Types
441
+ SPFxPropertiesInfo, // useSPFxProperties
442
+ SPFxDisplayModeInfo, // useSPFxDisplayMode
443
+ SPFxInstanceInfo, // useSPFxInstanceInfo
444
+ SPFxEnvironmentInfo, // useSPFxEnvironmentInfo
445
+ SPFxPageTypeInfo, // useSPFxPageType
446
+ SPFxUserInfo, // useSPFxUserInfo
447
+ SPFxSiteInfo, // useSPFxSiteInfo
448
+ SPFxLocaleInfo, // useSPFxLocaleInfo
449
+ SPFxListInfo, // useSPFxListInfo
450
+ SPFxHubSiteInfo, // useSPFxHubSiteInfo
451
+ SPFxThemeInfo, // useSPFxThemeInfo
452
+ SPFxFluent9ThemeInfo, // useSPFxFluent9ThemeInfo
453
+ SPFxContainerInfo, // useSPFxContainerInfo
454
+ SPFxStorageHook, // useSPFxLocalStorage / useSPFxSessionStorage
455
+ SPFxPerformanceInfo, // useSPFxPerformance
456
+ SPFxPerfResult, // Performance measurement result
457
+ SPFxLoggerInfo, // useSPFxLogger
458
+ LogEntry, // Log entry structure
459
+ LogLevel, // Log levels
460
+ SPFxCorrelationInfo, // useSPFxCorrelationInfo
461
+ SPFxPermissionsInfo, // useSPFxPermissions
462
+ SPFxTeamsInfo, // useSPFxTeams
463
+ TeamsTheme, // Teams theme type
464
+ SPFxOneDriveAppDataResult, // useSPFxOneDriveAppData
465
+
466
+ // PnP Types (if using PnPjs integration)
467
+ PnPContextInfo, // useSPFxPnPContext
468
+ SPFxPnPInfo, // useSPFxPnP
469
+ SPFxPnPListInfo, // useSPFxPnPList
470
+ SPFxPnPSearchInfo, // useSPFxPnPSearch
471
+ } from 'spfx-react-toolkit';
472
+ ```
473
+
474
+ ---
475
+
476
+ ## Hooks API
477
+
478
+ The toolkit provides **33 specialized hooks** organized by functionality. All hooks are type-safe, memoized, and automatically access the instance-scoped state.
479
+
480
+ ### Context & Configuration
481
+
482
+ #### `useSPFxPageContext()`
483
+
484
+ Access the full SharePoint PageContext object containing site, web, user, list, and Teams information.
485
+
486
+ **Returns:** `PageContext`
487
+
488
+ **Example:**
489
+ ```typescript
490
+ const pageContext = useSPFxPageContext();
491
+
492
+ console.log(pageContext.web.title);
493
+ console.log(pageContext.web.absoluteUrl);
494
+ console.log(pageContext.user.displayName);
495
+ ```
496
+
497
+ ---
498
+
499
+ #### `useSPFxProperties<T>()`
500
+
501
+ Access and manage SPFx properties with type-safe partial updates and automatic bidirectional synchronization with the Property Pane.
502
+
503
+ **Returns:** `SPFxPropertiesInfo<T>`
504
+ - `properties: T | undefined` - Current properties object
505
+ - `setProperties: (updates: Partial<T>) => void` - Partial merge update
506
+ - `updateProperties: (updater: (current: T | undefined) => T) => void` - Updater function pattern
507
+
508
+ **Features:**
509
+ - ✅ Type-safe with generics
510
+ - ✅ Partial updates (shallow merge)
511
+ - ✅ Updater function pattern (like React setState)
512
+ - ✅ Automatic bidirectional sync with SPFx
513
+ - ✅ Property Pane refresh for WebParts
514
+
515
+ **Example:**
516
+ ```typescript
517
+ interface IMyWebPartProps {
518
+ title: string;
519
+ description: string;
520
+ listId?: string;
521
+ }
522
+
523
+ function MyComponent() {
524
+ const { properties, setProperties, updateProperties } =
525
+ useSPFxProperties<IMyWebPartProps>();
526
+
527
+ return (
528
+ <div>
529
+ <h1>{properties?.title ?? 'Default Title'}</h1>
530
+ <p>{properties?.description}</p>
531
+
532
+ {/* Partial update */}
533
+ <button onClick={() => setProperties({ title: 'New Title' })}>
534
+ Update Title
535
+ </button>
536
+
537
+ {/* Updater function */}
538
+ <button onClick={() => updateProperties(prev => ({
539
+ ...prev,
540
+ title: (prev?.title ?? '') + ' Updated'
541
+ }))}>
542
+ Append to Title
543
+ </button>
544
+ </div>
545
+ );
546
+ }
547
+ ```
548
+
549
+ ---
550
+
551
+ #### `useSPFxDisplayMode()`
552
+
553
+ Access display mode (Read/Edit) for conditional rendering. Display mode is readonly and controlled by SharePoint.
554
+
555
+ **Returns:** `SPFxDisplayModeInfo`
556
+ - `mode: DisplayMode` - Current display mode (Read/Edit)
557
+ - `isEdit: boolean` - True if in Edit mode
558
+ - `isRead: boolean` - True if in Read mode
559
+
560
+ **Example:**
561
+ ```typescript
562
+ function MyComponent() {
563
+ const { isEdit, isRead } = useSPFxDisplayMode();
564
+
565
+ return (
566
+ <div>
567
+ <p>Mode: {isEdit ? 'Editing' : 'Reading'}</p>
568
+ {isEdit && <EditControls />}
569
+ {isRead && <ReadOnlyView />}
570
+ </div>
571
+ );
572
+ }
573
+ ```
574
+
575
+ ---
576
+
577
+ #### `useSPFxInstanceInfo()`
578
+
579
+ Get unique instance ID and component kind for debugging, logging, and conditional logic.
580
+
581
+ **Returns:** `SPFxInstanceInfo`
582
+ - `id: string` - Unique identifier for this SPFx instance
583
+ - `kind: HostKind` - Component type: `'WebPart' | 'AppCustomizer' | 'FieldCustomizer' | 'CommandSet' | 'ACE'`
584
+
585
+ **Example:**
586
+ ```typescript
587
+ function MyComponent() {
588
+ const { id, kind } = useSPFxInstanceInfo();
589
+
590
+ console.log(`Instance ID: ${id}`);
591
+ console.log(`Component Type: ${kind}`);
592
+
593
+ if (kind === 'WebPart') {
594
+ return <WebPartView />;
595
+ }
596
+
597
+ return <ExtensionView />;
598
+ }
599
+ ```
600
+
601
+ ---
602
+
603
+ #### `useSPFxServiceScope()`
604
+
605
+ Access SPFx ServiceScope for advanced service consumption and dependency injection.
606
+
607
+ **Returns:** `ServiceScope | undefined`
608
+
609
+ **Example:**
610
+ ```typescript
611
+ import { ServiceKey, ServiceScope } from '@microsoft/sp-core-library';
612
+
613
+ const MyServiceKey = ServiceKey.create<IMyService>('my-service', MyService);
614
+
615
+ function MyComponent() {
616
+ const serviceScope = useSPFxServiceScope();
617
+
618
+ const myService = serviceScope?.consume(MyServiceKey);
619
+
620
+ // Use service...
621
+ }
622
+ ```
623
+
624
+ ---
625
+
626
+ ### User & Site Information
627
+
628
+ #### `useSPFxUserInfo()`
629
+
630
+ Access current user information including display name, email, login name, and guest status.
631
+
632
+ **Returns:** `SPFxUserInfo`
633
+ - `displayName: string` - User display name
634
+ - `email: string | undefined` - User email address
635
+ - `loginName: string` - User login name (e.g., "domain\\user" or email)
636
+ - `isExternal: boolean` - Whether user is an external guest
637
+
638
+ **Example:**
639
+ ```typescript
640
+ function MyComponent() {
641
+ const { displayName, email, isExternal } = useSPFxUserInfo();
642
+
643
+ return (
644
+ <div>
645
+ <h2>Welcome, {displayName}!</h2>
646
+ {email && <p>Email: {email}</p>}
647
+ {isExternal && <Badge>Guest User</Badge>}
648
+ </div>
649
+ );
650
+ }
651
+ ```
652
+
653
+ ---
654
+
655
+ #### `useSPFxSiteInfo()`
656
+
657
+ Access comprehensive site collection and web information with flat, predictable property naming.
658
+
659
+ **Returns:** `SPFxSiteInfo`
660
+
661
+ **Web Properties (primary context - 90% use case):**
662
+ - `webId: string` - Web ID (GUID)
663
+ - `webUrl: string` - Web absolute URL
664
+ - `webServerRelativeUrl: string` - Web server-relative URL
665
+ - `title: string` - Web display name (most commonly used)
666
+ - `languageId: number` - Web language (LCID)
667
+ - `logoUrl?: string` - Site logo URL (for branding)
668
+
669
+ **Site Collection Properties (parent context - specialized):**
670
+ - `siteId: string` - Site collection ID (GUID)
671
+ - `siteUrl: string` - Site collection absolute URL
672
+ - `siteServerRelativeUrl: string` - Site collection server-relative URL
673
+ - `siteClassification?: string` - Enterprise classification (e.g., "Confidential", "Public")
674
+ - `siteGroup?: SPFxGroupInfo` - Microsoft 365 Group info (if group-connected)
675
+
676
+ **Example:**
677
+ ```typescript
678
+ function SiteHeader() {
679
+ const {
680
+ title, // Web title (most common)
681
+ webUrl, // Web URL
682
+ logoUrl, // Site logo
683
+ siteClassification, // Enterprise classification
684
+ siteGroup // M365 Group info
685
+ } = useSPFxSiteInfo();
686
+
687
+ return (
688
+ <header>
689
+ {logoUrl && <img src={logoUrl} alt="Site logo" />}
690
+ <h1>{title}</h1>
691
+ <a href={webUrl}>Visit Site</a>
692
+
693
+ {siteClassification && (
694
+ <Label>Classification: {siteClassification}</Label>
695
+ )}
696
+
697
+ {siteGroup && (
698
+ <Badge>
699
+ {siteGroup.isPublic ? 'Public Team' : 'Private Team'}
700
+ </Badge>
701
+ )}
702
+ </header>
703
+ );
704
+ }
705
+ ```
706
+
707
+ ---
708
+
709
+ #### `useSPFxLocaleInfo()`
710
+
711
+ Access locale and regional settings for internationalization (i18n) with direct Intl API compatibility.
712
+
713
+ **Returns:** `SPFxLocaleInfo`
714
+ - `locale: string` - Current content locale (e.g., "en-US", "it-IT")
715
+ - `uiLocale: string` - Current UI language locale
716
+ - `timeZone?: SPFxTimeZone` - Time zone information (preview API)
717
+ - `isRtl: boolean` - Whether language is right-to-left
718
+
719
+ **Example:**
720
+ ```typescript
721
+ function DateDisplay() {
722
+ const { locale, isRtl, timeZone } = useSPFxLocaleInfo();
723
+
724
+ const formatDate = (date: Date) => {
725
+ return new Intl.DateTimeFormat(locale, {
726
+ dateStyle: 'full',
727
+ timeStyle: 'long'
728
+ }).format(date);
729
+ };
730
+
731
+ return (
732
+ <div dir={isRtl ? 'rtl' : 'ltr'}>
733
+ <p>{formatDate(new Date())}</p>
734
+ {timeZone && (
735
+ <p>Time Zone: {timeZone.description} (UTC {timeZone.offset/60})</p>
736
+ )}
737
+ </div>
738
+ );
739
+ }
740
+ ```
741
+
742
+ ---
743
+
744
+ #### `useSPFxListInfo()`
745
+
746
+ Access list information when component is rendered in a list context (Field Customizers, list-scoped components).
747
+
748
+ **Returns:** `SPFxListInfo | undefined`
749
+ - `id: string` - List ID (GUID)
750
+ - `title: string` - List title
751
+ - `serverRelativeUrl: string` - List server-relative URL
752
+ - `baseTemplate?: number` - List template type (e.g., 100 for Generic List, 101 for Document Library)
753
+ - `isDocumentLibrary?: boolean` - Whether list is a document library
754
+
755
+ **Example:**
756
+ ```typescript
757
+ function FieldRenderer() {
758
+ const list = useSPFxListInfo();
759
+
760
+ if (!list) {
761
+ return <div>Not in list context</div>;
762
+ }
763
+
764
+ return (
765
+ <div>
766
+ <h3>{list.title}</h3>
767
+ <p>List ID: {list.id}</p>
768
+ {list.isDocumentLibrary && <Icon iconName="DocumentLibrary" />}
769
+ </div>
770
+ );
771
+ }
772
+ ```
773
+
774
+ ---
775
+
776
+ #### `useSPFxHubSiteInfo()`
777
+
778
+ Access Hub Site association information with automatic hub URL fetching via REST API.
779
+
780
+ **Returns:** `SPFxHubSiteInfo`
781
+ - `isHubSite: boolean` - Whether site is associated with a hub
782
+ - `hubSiteId?: string` - Hub site ID (GUID)
783
+ - `hubSiteUrl?: string` - Hub site URL (fetched asynchronously)
784
+ - `isLoading: boolean` - Loading state for hub URL fetch
785
+ - `error?: Error` - Error during hub URL fetch
786
+
787
+ **Example:**
788
+ ```typescript
789
+ function HubNavigation() {
790
+ const { isHubSite, hubSiteUrl, isLoading } = useSPFxHubSiteInfo();
791
+
792
+ if (!isHubSite) return null;
793
+
794
+ if (isLoading) {
795
+ return <Spinner label="Loading hub info..." />;
796
+ }
797
+
798
+ return (
799
+ <nav>
800
+ <a href={hubSiteUrl}>← Back to Hub</a>
801
+ </nav>
802
+ );
803
+ }
804
+ ```
805
+
806
+ ---
807
+
808
+ ### UI & Layout
809
+
810
+ #### `useSPFxThemeInfo()`
811
+
812
+ Access current SPFx theme (Fluent UI 8) with automatic updates when user switches themes.
813
+
814
+ **Returns:** `IReadonlyTheme | undefined`
815
+
816
+ **Example:**
817
+ ```typescript
818
+ function MyComponent() {
819
+ const theme = useSPFxThemeInfo();
820
+
821
+ return (
822
+ <div style={{
823
+ backgroundColor: theme?.semanticColors?.bodyBackground,
824
+ color: theme?.semanticColors?.bodyText,
825
+ padding: '20px'
826
+ }}>
827
+ Themed Content
828
+ </div>
829
+ );
830
+ }
831
+ ```
832
+
833
+ ---
834
+
835
+ #### `useSPFxFluent9ThemeInfo()`
836
+
837
+ Access Fluent UI 9 theme with automatic Teams/SharePoint detection and theme conversion.
838
+
839
+ **Returns:** `SPFxFluent9ThemeInfo`
840
+ - `theme: Theme` - Fluent UI 9 theme object (ready for FluentProvider)
841
+ - `isTeams: boolean` - Whether running in Microsoft Teams
842
+ - `teamsTheme?: string` - Teams theme name ('default', 'dark', 'contrast')
843
+
844
+ **Priority order:**
845
+ 1. Teams native themes (if in Teams)
846
+ 2. SPFx theme converted to Fluent UI 9
847
+ 3. Default webLightTheme
848
+
849
+ **Example:**
850
+ ```typescript
851
+ import { FluentProvider } from '@fluentui/react-components';
852
+
853
+ function MyWebPart() {
854
+ const { theme, isTeams, teamsTheme } = useSPFxFluent9ThemeInfo();
855
+
856
+ return (
857
+ <FluentProvider theme={theme}>
858
+ <div>
859
+ <p>Running in: {isTeams ? 'Teams' : 'SharePoint'}</p>
860
+ {isTeams && <p>Teams theme: {teamsTheme}</p>}
861
+ <Button appearance="primary">Themed Button</Button>
862
+ </div>
863
+ </FluentProvider>
864
+ );
865
+ }
866
+ ```
867
+
868
+ ---
869
+
870
+ #### `useSPFxContainerSize()`
871
+
872
+ Get reactive container dimensions with Fluent UI 9 aligned breakpoints. Auto-updates on resize.
873
+
874
+ **Returns:** `SPFxContainerSizeInfo`
875
+ - `size: SPFxContainerSize` - Category: 'small' | 'medium' | 'large' | 'xLarge' | 'xxLarge' | 'xxxLarge'
876
+ - `isSmall: boolean` - 320-479px (mobile portrait)
877
+ - `isMedium: boolean` - 480-639px (mobile landscape)
878
+ - `isLarge: boolean` - 640-1023px (tablets)
879
+ - `isXLarge: boolean` - 1024-1365px (laptop, desktop)
880
+ - `isXXLarge: boolean` - 1366-1919px (wide desktop)
881
+ - `isXXXLarge: boolean` - ≥1920px (4K, ultra-wide)
882
+ - `width: number` - Actual width in pixels
883
+ - `height: number` - Actual height in pixels
884
+
885
+ **Example:**
886
+ ```typescript
887
+ function ResponsiveWebPart() {
888
+ const { size, isSmall, isXXXLarge, width } = useSPFxContainerSize();
889
+
890
+ if (isSmall) {
891
+ return <CompactMobileView />;
892
+ }
893
+
894
+ if (size === 'medium' || size === 'large') {
895
+ return <TabletView />;
896
+ }
897
+
898
+ if (isXXXLarge) {
899
+ return <UltraWideView columns={6} />; // 4K/ultra-wide
900
+ }
901
+
902
+ return <DesktopView columns={size === 'xxLarge' ? 4 : 3} />;
903
+ }
904
+ ```
905
+
906
+ ---
907
+
908
+ #### `useSPFxContainerInfo()`
909
+
910
+ Access container DOM element and size tracking.
911
+
912
+ **Returns:** `SPFxContainerInfo`
913
+ - `element: HTMLElement | undefined` - Container DOM element
914
+ - `size: ContainerSize | undefined` - `{ width: number, height: number }`
915
+
916
+ **Example:**
917
+ ```typescript
918
+ function MyComponent() {
919
+ const { element, size } = useSPFxContainerInfo();
920
+
921
+ return (
922
+ <div>
923
+ {size && <p>Container: {size.width}px × {size.height}px</p>}
924
+ </div>
925
+ );
926
+ }
927
+ ```
928
+
929
+ ---
930
+
931
+ ### Storage
932
+
933
+ #### `useSPFxLocalStorage<T>(key, defaultValue)`
934
+
935
+ Instance-scoped localStorage for persistent data across sessions. Automatically scoped per SPFx instance.
936
+
937
+ **Parameters:**
938
+ - `key: string` - Storage key (auto-prefixed with instance ID)
939
+ - `defaultValue: T` - Default value if not in storage
940
+
941
+ **Returns:** `SPFxStorageHook<T>`
942
+ - `value: T` - Current value
943
+ - `setValue: (value: T | ((prev: T) => T)) => void` - Set new value
944
+ - `remove: () => void` - Remove value (reset to default)
945
+
946
+ **Example:**
947
+ ```typescript
948
+ function PreferencesPanel() {
949
+ const { value: viewMode, setValue: setViewMode } =
950
+ useSPFxLocalStorage('view-mode', 'grid');
951
+
952
+ return (
953
+ <div>
954
+ <p>View: {viewMode}</p>
955
+ <button onClick={() => setViewMode('list')}>List View</button>
956
+ <button onClick={() => setViewMode('grid')}>Grid View</button>
957
+ </div>
958
+ );
959
+ }
960
+ ```
961
+
962
+ ---
963
+
964
+ #### `useSPFxSessionStorage<T>(key, defaultValue)`
965
+
966
+ Instance-scoped sessionStorage for temporary data (current tab/session only). Automatically scoped per SPFx instance.
967
+
968
+ **Parameters:**
969
+ - `key: string` - Storage key (auto-prefixed with instance ID)
970
+ - `defaultValue: T` - Default value if not in storage
971
+
972
+ **Returns:** `SPFxStorageHook<T>`
973
+ - `value: T` - Current value
974
+ - `setValue: (value: T | ((prev: T) => T)) => void` - Set new value
975
+ - `remove: () => void` - Remove value (reset to default)
976
+
977
+ **Example:**
978
+ ```typescript
979
+ function WizardComponent() {
980
+ const { value: step, setValue: setStep } =
981
+ useSPFxSessionStorage('wizard-step', 1);
982
+
983
+ return (
984
+ <div>
985
+ <p>Step: {step} of 5</p>
986
+ <button onClick={() => setStep(s => s + 1)}>Next</button>
987
+ <button onClick={() => setStep(s => s - 1)}>Back</button>
988
+ </div>
989
+ );
990
+ }
991
+ ```
992
+
993
+ ---
994
+
995
+ ### HTTP Clients
996
+
997
+ #### `useSPFxSPHttpClient()`
998
+
999
+ Access SharePoint REST API client (SPHttpClient).
1000
+
1001
+ **Returns:** `SPFxSPHttpClientInfo`
1002
+ - `client: SPHttpClient | undefined` - SPHttpClient instance
1003
+ - `invoke: (fn) => Promise<T>` - Execute with error handling
1004
+ - `baseUrl: string` - Base URL for REST API calls
1005
+
1006
+ **Example:**
1007
+ ```typescript
1008
+ function ListsViewer() {
1009
+ const { invoke, baseUrl } = useSPFxSPHttpClient();
1010
+ const [lists, setLists] = useState([]);
1011
+
1012
+ const fetchLists = async () => {
1013
+ const data = await invoke(async (client) => {
1014
+ const response = await client.get(
1015
+ `${baseUrl}/_api/web/lists`,
1016
+ SPHttpClient.configurations.v1
1017
+ );
1018
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1019
+ return (await response.json()).value;
1020
+ });
1021
+ setLists(data);
1022
+ };
1023
+
1024
+ return (
1025
+ <div>
1026
+ <button onClick={fetchLists}>Load Lists</button>
1027
+ <ul>
1028
+ {lists.map(list => <li key={list.Id}>{list.Title}</li>)}
1029
+ </ul>
1030
+ </div>
1031
+ );
1032
+ }
1033
+ ```
1034
+
1035
+ ---
1036
+
1037
+ #### `useSPFxMSGraphClient()`
1038
+
1039
+ Access Microsoft Graph API client.
1040
+
1041
+ **Returns:** `SPFxMSGraphClientInfo`
1042
+ - `client: MSGraphClientV3 | undefined` - MS Graph client instance
1043
+ - `invoke: (fn) => Promise<T>` - Execute with error handling
1044
+
1045
+ **Required API Permissions:** Configure in `package-solution.json`
1046
+
1047
+ **Example:**
1048
+ ```typescript
1049
+ function UserProfile() {
1050
+ const { invoke } = useSPFxMSGraphClient();
1051
+ const [profile, setProfile] = useState(null);
1052
+
1053
+ const fetchProfile = async () => {
1054
+ const data = await invoke(async (client) => {
1055
+ return await client.api('/me').get();
1056
+ });
1057
+ setProfile(data);
1058
+ };
1059
+
1060
+ useEffect(() => { fetchProfile(); }, []);
1061
+
1062
+ return profile && (
1063
+ <div>
1064
+ <h3>{profile.displayName}</h3>
1065
+ <p>{profile.mail}</p>
1066
+ </div>
1067
+ );
1068
+ }
1069
+ ```
1070
+
1071
+ ---
1072
+
1073
+ #### `useSPFxAadHttpClient()`
1074
+
1075
+ Access Azure AD secured API client (AadHttpClient).
1076
+
1077
+ **Returns:** `SPFxAadHttpClientInfo`
1078
+ - `client: AadHttpClient | undefined` - AAD HTTP client instance
1079
+ - `invoke: (fn) => Promise<T>` - Execute with error handling
1080
+
1081
+ **Example:**
1082
+ ```typescript
1083
+ function CustomApiCall() {
1084
+ const { invoke } = useSPFxAadHttpClient();
1085
+ const [data, setData] = useState(null);
1086
+
1087
+ const callApi = async () => {
1088
+ const result = await invoke(async (client) => {
1089
+ const response = await client.get(
1090
+ 'https://api.contoso.com/data',
1091
+ AadHttpClient.configurations.v1
1092
+ );
1093
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1094
+ return await response.json();
1095
+ });
1096
+ setData(result);
1097
+ };
1098
+
1099
+ return (
1100
+ <div>
1101
+ <button onClick={callApi}>Call API</button>
1102
+ {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
1103
+ </div>
1104
+ );
1105
+ }
1106
+ ```
1107
+
1108
+ ---
1109
+
1110
+ #### `useSPFxOneDriveAppData<T>(filename, folder?, autoFetch?)`
1111
+
1112
+ Manage JSON files in user's OneDrive appRoot folder with unified read/write operations.
1113
+
1114
+ **Parameters:**
1115
+ - `filename: string` - JSON filename
1116
+ - `folder?: string` - Folder namespace (optional, for isolation)
1117
+ - `autoFetch?: boolean` - Auto-fetch on mount (default: true)
1118
+
1119
+ **Returns:** `SPFxOneDriveAppDataResult<T>`
1120
+ - `data: T | undefined` - Current data
1121
+ - `isLoading: boolean` - Loading state
1122
+ - `isWriting: boolean` - Writing state
1123
+ - `error?: Error` - Error during operations
1124
+ - `write: (data: T) => Promise<void>` - Write data to file
1125
+ - `load: () => Promise<void>` - Manually load data
1126
+ - `isReady: boolean` - Client ready for operations
1127
+
1128
+ **Example:**
1129
+ ```typescript
1130
+ interface UserSettings {
1131
+ theme: string;
1132
+ language: string;
1133
+ }
1134
+
1135
+ function SettingsPanel() {
1136
+ const { data, write, isLoading, isWriting } =
1137
+ useSPFxOneDriveAppData<UserSettings>('settings.json');
1138
+
1139
+ if (isLoading) return <Spinner />;
1140
+
1141
+ const handleSave = async () => {
1142
+ await write({ theme: 'dark', language: 'en' });
1143
+ };
1144
+
1145
+ return (
1146
+ <div>
1147
+ <p>Theme: {data?.theme}</p>
1148
+ <button onClick={handleSave} disabled={isWriting}>
1149
+ Save Settings
1150
+ </button>
1151
+ </div>
1152
+ );
1153
+ }
1154
+ ```
1155
+
1156
+ ---
1157
+
1158
+ #### `useSPFxUserPhoto(options?)`
1159
+
1160
+ Load user profile photos from Microsoft Graph API. Supports current user or specific users by ID/email.
1161
+
1162
+ **Parameters:**
1163
+ - `options?: UserPhotoOptions` - Optional `{ userId?, email?, size?, autoFetch? }`
1164
+
1165
+ **Requires MS Graph Permissions:**
1166
+ - **User.Read**: For current user's photo
1167
+ - **User.ReadBasic.All**: For other users' photos
1168
+
1169
+ **Returns:** `SPFxUserPhotoInfo`
1170
+ - `photoUrl: string | undefined` - Photo URL or undefined
1171
+ - `isLoading: boolean` - Loading state
1172
+ - `error?: Error` - Error state
1173
+ - `reload: () => Promise<void>` - Manually reload photo
1174
+
1175
+ **Example:**
1176
+ ```typescript
1177
+ // Current user
1178
+ const { photoUrl, isLoading } = useSPFxUserPhoto();
1179
+
1180
+ // Specific user by email
1181
+ const { photoUrl } = useSPFxUserPhoto({
1182
+ email: 'user@contoso.com',
1183
+ size: '96x96' // Options: 48x48, 64x64, 96x96, 120x120, 240x240, 360x360, 432x432, 504x504, 648x648
1184
+ });
1185
+
1186
+ // Lazy loading
1187
+ const { photoUrl, reload, isLoading } = useSPFxUserPhoto({
1188
+ email: 'user@contoso.com',
1189
+ autoFetch: false
1190
+ });
1191
+
1192
+ return (
1193
+ <div>
1194
+ {photoUrl ? (
1195
+ <img src={photoUrl} alt="Avatar" />
1196
+ ) : (
1197
+ <button onClick={reload} disabled={isLoading}>
1198
+ {isLoading ? 'Loading...' : 'Load Photo'}
1199
+ </button>
1200
+ )}
1201
+ </div>
1202
+ );
1203
+ ```
1204
+
1205
+ ---
1206
+
1207
+ #### `useSPFxTenantProperty<T>(key)`
1208
+
1209
+ Manage tenant-wide properties using SharePoint StorageEntity API with smart serialization.
1210
+
1211
+ **Parameters:**
1212
+ - `key: string` - Property key
1213
+
1214
+ **Returns:** `SPFxTenantPropertyInfo<T>`
1215
+ - `data: T | undefined` - Current property value
1216
+ - `description: string | undefined` - Property description
1217
+ - `isLoading: boolean` - Loading state
1218
+ - `error?: Error` - Error state
1219
+ - `write: (value: T, description?: string) => Promise<void>` - Write property
1220
+ - `remove: () => Promise<void>` - Remove property
1221
+ - `canWrite: boolean` - Whether user can write (requires Site Collection Admin on tenant app catalog)
1222
+
1223
+ **Requirements:**
1224
+ - Tenant app catalog must be provisioned
1225
+ - **Read**: Any authenticated user
1226
+ - **Write/Remove**: Must be Site Collection Administrator of tenant app catalog
1227
+
1228
+ **Smart Serialization:**
1229
+ - Primitives (string, number, boolean, null, bigint) → stored as string
1230
+ - Date objects → stored as ISO 8601 string
1231
+ - Objects/arrays → stored as JSON string
1232
+
1233
+ **Example:**
1234
+ ```typescript
1235
+ // String property
1236
+ const { data, write, canWrite, isLoading } = useSPFxTenantProperty<string>('appVersion');
1237
+
1238
+ if (isLoading) return <Spinner />;
1239
+
1240
+ const handleUpdate = async () => {
1241
+ if (!canWrite) {
1242
+ alert('Insufficient permissions');
1243
+ return;
1244
+ }
1245
+ await write('2.0.1', 'Current application version');
1246
+ };
1247
+
1248
+ return (
1249
+ <div>
1250
+ <p>Version: {data ?? 'Not Set'}</p>
1251
+ {canWrite && <button onClick={handleUpdate}>Update</button>}
1252
+ </div>
1253
+ );
1254
+
1255
+ // Number property
1256
+ const { data: maxSize } = useSPFxTenantProperty<number>('maxUploadSize');
1257
+ await write(10485760, 'Max file size in bytes');
1258
+
1259
+ // Boolean property
1260
+ const { data: maintenance } = useSPFxTenantProperty<boolean>('maintenanceMode');
1261
+ if (maintenance) {
1262
+ return <MessageBar>System under maintenance</MessageBar>;
1263
+ }
1264
+
1265
+ // Complex object
1266
+ interface FeatureFlags {
1267
+ enableChat: boolean;
1268
+ enableAnalytics: boolean;
1269
+ maxUsers: number;
1270
+ }
1271
+
1272
+ const { data, write } = useSPFxTenantProperty<FeatureFlags>('featureFlags');
1273
+
1274
+ await write({
1275
+ enableChat: true,
1276
+ enableAnalytics: false,
1277
+ maxUsers: 1000
1278
+ }, 'Global feature flags');
1279
+
1280
+ if (data?.enableChat) {
1281
+ return <ChatPanel />;
1282
+ }
1283
+ ```
1284
+
1285
+ ---
1286
+
1287
+ ### Performance & Diagnostics
1288
+
1289
+ #### `useSPFxPerformance()`
1290
+
1291
+ Performance measurement API with automatic SPFx context integration for monitoring and profiling.
1292
+
1293
+ **Returns:** `SPFxPerformanceInfo`
1294
+ - `mark: (name: string) => void` - Create performance mark
1295
+ - `measure: (name, startMark, endMark?) => SPFxPerfResult` - Measure duration between marks
1296
+ - `time: <T>(name, fn) => Promise<SPFxPerfResult<T>>` - Time async operations
1297
+
1298
+ **Example:**
1299
+ ```typescript
1300
+ function DataLoader() {
1301
+ const { time } = useSPFxPerformance();
1302
+ const [data, setData] = useState(null);
1303
+
1304
+ const fetchData = async () => {
1305
+ const result = await time('fetch-data', async () => {
1306
+ const response = await fetch('/api/data');
1307
+ return response.json();
1308
+ });
1309
+
1310
+ console.log(`Fetch took ${result.durationMs}ms`);
1311
+ setData(result.result);
1312
+ };
1313
+
1314
+ return <button onClick={fetchData}>Load Data</button>;
1315
+ }
1316
+ ```
1317
+
1318
+ ---
1319
+
1320
+ #### `useSPFxLogger(handler?)`
1321
+
1322
+ Structured logging with automatic SPFx context (instance ID, user, site, correlation ID).
1323
+
1324
+ **Parameters:**
1325
+ - `handler?: (entry: LogEntry) => void` - Optional custom log handler (e.g., Application Insights)
1326
+
1327
+ **Returns:** `SPFxLoggerInfo`
1328
+ - `debug: (message, extra?) => void` - Log debug message
1329
+ - `info: (message, extra?) => void` - Log info message
1330
+ - `warn: (message, extra?) => void` - Log warning message
1331
+ - `error: (message, extra?) => void` - Log error message
1332
+
1333
+ **Example:**
1334
+ ```typescript
1335
+ function MyComponent() {
1336
+ const logger = useSPFxLogger();
1337
+
1338
+ const handleClick = () => {
1339
+ logger.info('Button clicked', { buttonId: 'save', timestamp: Date.now() });
1340
+ };
1341
+
1342
+ const handleError = (error: Error) => {
1343
+ logger.error('Operation failed', {
1344
+ errorMessage: error.message,
1345
+ stack: error.stack
1346
+ });
1347
+ };
1348
+
1349
+ return <button onClick={handleClick}>Save</button>;
1350
+ }
1351
+ ```
1352
+
1353
+ ---
1354
+
1355
+ #### `useSPFxCorrelationInfo()`
1356
+
1357
+ Access correlation ID and tenant ID for distributed tracing and diagnostics.
1358
+
1359
+ **Returns:** `SPFxCorrelationInfo`
1360
+ - `correlationId?: string` - Correlation ID for tracking requests
1361
+ - `tenantId?: string` - Azure AD tenant ID
1362
+
1363
+ **Example:**
1364
+ ```typescript
1365
+ function DiagnosticsPanel() {
1366
+ const { correlationId, tenantId } = useSPFxCorrelationInfo();
1367
+
1368
+ const logError = (error: Error) => {
1369
+ console.error('Error occurred', {
1370
+ message: error.message,
1371
+ correlationId,
1372
+ tenantId,
1373
+ timestamp: new Date().toISOString()
1374
+ });
1375
+ };
1376
+
1377
+ return (
1378
+ <div>
1379
+ <p>Tenant: {tenantId}</p>
1380
+ <p>Correlation: {correlationId}</p>
1381
+ </div>
1382
+ );
1383
+ }
1384
+ ```
1385
+
1386
+ ---
1387
+
1388
+ ### Permissions & Security
1389
+
1390
+ #### `useSPFxPermissions()`
1391
+
1392
+ Check SharePoint permissions at site, web, and list levels with SPPermission enum helpers.
1393
+
1394
+ **Returns:** `SPFxPermissionsInfo`
1395
+ - `sitePermissions?: SPPermission` - Site collection permissions
1396
+ - `webPermissions?: SPPermission` - Web permissions
1397
+ - `listPermissions?: SPPermission` - List permissions (if in list context)
1398
+ - `hasWebPermission: (permission) => boolean` - Check web permission
1399
+ - `hasSitePermission: (permission) => boolean` - Check site permission
1400
+ - `hasListPermission: (permission) => boolean` - Check list permission
1401
+
1402
+ **Common Permissions:**
1403
+ - `SPPermission.manageWeb`
1404
+ - `SPPermission.addListItems`
1405
+ - `SPPermission.editListItems`
1406
+ - `SPPermission.deleteListItems`
1407
+ - `SPPermission.managePermissions`
1408
+
1409
+ **Example:**
1410
+ ```typescript
1411
+ import { SPPermission } from '@microsoft/sp-page-context';
1412
+
1413
+ function AdminPanel() {
1414
+ const { hasWebPermission, hasListPermission } = useSPFxPermissions();
1415
+
1416
+ const canManage = hasWebPermission(SPPermission.manageWeb);
1417
+ const canAddItems = hasListPermission(SPPermission.addListItems);
1418
+
1419
+ return (
1420
+ <div>
1421
+ {canManage && <button>Manage Settings</button>}
1422
+ {canAddItems && <button>Add Item</button>}
1423
+ {!canManage && <p>Insufficient permissions</p>}
1424
+ </div>
1425
+ );
1426
+ }
1427
+ ```
1428
+
1429
+ ---
1430
+
1431
+ #### `useSPFxCrossSitePermissions(siteUrl?, options?)`
1432
+
1433
+ Retrieve permissions for a different site/web/list (cross-site permission check).
1434
+
1435
+ **Parameters:**
1436
+ - `siteUrl?: string` - Target site URL (no fetch if undefined/empty - lazy loading)
1437
+ - `options?: SPFxCrossSitePermissionsOptions` - Optional `{ webUrl?, listId? }`
1438
+
1439
+ **Returns:** `SPFxCrossSitePermissionsInfo`
1440
+ - `sitePermissions?: SPPermission` - Site permissions
1441
+ - `webPermissions?: SPPermission` - Web permissions
1442
+ - `listPermissions?: SPPermission` - List permissions (if listId provided)
1443
+ - `hasWebPermission: (permission) => boolean` - Check web permission
1444
+ - `hasSitePermission: (permission) => boolean` - Check site permission
1445
+ - `hasListPermission: (permission) => boolean` - Check list permission
1446
+ - `isLoading: boolean` - Loading state
1447
+ - `error?: Error` - Error state
1448
+
1449
+ **Example:**
1450
+ ```typescript
1451
+ import { SPPermission } from '@microsoft/sp-page-context';
1452
+
1453
+ function CrossSiteCheck() {
1454
+ const [targetUrl, setTargetUrl] = useState<string | undefined>();
1455
+
1456
+ const { hasWebPermission, isLoading, error } = useSPFxCrossSitePermissions(
1457
+ targetUrl,
1458
+ { webUrl: 'https://contoso.sharepoint.com/sites/target/subweb' }
1459
+ );
1460
+
1461
+ // Trigger fetch
1462
+ const checkPermissions = () => {
1463
+ setTargetUrl('https://contoso.sharepoint.com/sites/target');
1464
+ };
1465
+
1466
+ if (isLoading) return <Spinner />;
1467
+ if (error) return <MessageBar>{error.message}</MessageBar>;
1468
+
1469
+ const canAdd = hasWebPermission(SPPermission.addListItems);
1470
+
1471
+ return (
1472
+ <div>
1473
+ <button onClick={checkPermissions}>Check Permissions</button>
1474
+ {targetUrl && <p>Can add items: {canAdd ? 'Yes' : 'No'}</p>}
1475
+ </div>
1476
+ );
1477
+ }
1478
+ ```
1479
+
1480
+ ---
1481
+
1482
+ ### Environment & Platform
1483
+
1484
+ #### `useSPFxEnvironmentInfo()`
1485
+
1486
+ Detect execution environment (Local, SharePoint, Teams, Office, Outlook).
1487
+
1488
+ **Returns:** `SPFxEnvironmentInfo`
1489
+ - `type: SPFxEnvironmentType` - 'Local' | 'SharePoint' | 'SharePointOnPrem' | 'Teams' | 'Office' | 'Outlook'
1490
+ - `isLocal: boolean` - Running in local workbench
1491
+ - `isWorkbench: boolean` - Running in any workbench
1492
+ - `isSharePoint: boolean` - SharePoint Online
1493
+ - `isSharePointOnPrem: boolean` - SharePoint On-Premises
1494
+ - `isTeams: boolean` - Microsoft Teams
1495
+ - `isOffice: boolean` - Office application
1496
+ - `isOutlook: boolean` - Outlook
1497
+
1498
+ **Example:**
1499
+ ```typescript
1500
+ function AdaptiveUI() {
1501
+ const { type, isTeams, isLocal } = useSPFxEnvironmentInfo();
1502
+
1503
+ if (isLocal) {
1504
+ return <DevModeBanner />;
1505
+ }
1506
+
1507
+ if (isTeams) {
1508
+ return <TeamsOptimizedUI />;
1509
+ }
1510
+
1511
+ return <SharePointUI />;
1512
+ }
1513
+ ```
1514
+
1515
+ ---
1516
+
1517
+ #### `useSPFxPageType()`
1518
+
1519
+ Detect SharePoint page type (modern site page, classic, list page, etc.).
1520
+
1521
+ **Returns:** `SPFxPageTypeInfo`
1522
+ - `pageType: SPFxPageType` - 'sitePage' | 'webPartPage' | 'listPage' | 'listFormPage' | 'profilePage' | 'searchPage' | 'unknown'
1523
+ - `isModernPage: boolean` - True for modern site pages
1524
+ - `isSitePage: boolean` - Site page (modern)
1525
+ - `isListPage: boolean` - List view page
1526
+ - `isListFormPage: boolean` - List form page
1527
+ - `isWebPartPage: boolean` - Classic web part page
1528
+
1529
+ **Example:**
1530
+ ```typescript
1531
+ function FeatureGate() {
1532
+ const { isModernPage, isSitePage } = useSPFxPageType();
1533
+
1534
+ if (!isModernPage) {
1535
+ return <div>This feature requires a modern page</div>;
1536
+ }
1537
+
1538
+ return isSitePage ? <ModernFeature /> : <ClassicFallback />;
1539
+ }
1540
+ ```
1541
+
1542
+ ---
1543
+
1544
+ #### `useSPFxTeams()`
1545
+
1546
+ Access Microsoft Teams context with automatic SDK initialization (v1 and v2 compatible).
1547
+
1548
+ **Returns:** `SPFxTeamsInfo`
1549
+ - `supported: boolean` - Whether Teams context is available
1550
+ - `context?: unknown` - Teams context object (team, channel, user info)
1551
+ - `theme?: TeamsTheme` - 'default' | 'dark' | 'highContrast'
1552
+
1553
+ **Example:**
1554
+ ```typescript
1555
+ function TeamsIntegration() {
1556
+ const { supported, context, theme } = useSPFxTeams();
1557
+
1558
+ if (!supported) {
1559
+ return <div>Not running in Teams</div>;
1560
+ }
1561
+
1562
+ const teamsContext = context as {
1563
+ team?: { displayName: string };
1564
+ channel?: { displayName: string };
1565
+ };
1566
+
1567
+ return (
1568
+ <div className={`teams-theme-${theme}`}>
1569
+ <h3>Team: {teamsContext.team?.displayName}</h3>
1570
+ <p>Channel: {teamsContext.channel?.displayName}</p>
1571
+ </div>
1572
+ );
1573
+ }
1574
+ ```
1575
+
1576
+ ---
1577
+
1578
+ ## PnPjs Integration (Optional)
1579
+
1580
+ This toolkit provides optional hooks for working with **PnPjs v4** for SharePoint REST API operations.
1581
+
1582
+ ### <a id="pnpjs-installation"></a>Installation
1583
+
1584
+ All dependencies (Jotai + PnPjs) are **peer dependencies** and installed automatically:
1585
+
1586
+ ```bash
1587
+ npm install @apvee/spfx-react-toolkit
1588
+ # Automatically installs (npm 7+):
1589
+ # - jotai ^2.0.0
1590
+ # - @pnp/sp, @pnp/core, @pnp/queryable ^4.0.0
1591
+ ```
1592
+
1593
+ **Import Pattern:**
1594
+
1595
+ All hooks (core and PnP) are available from the main entry point:
1596
+
1597
+ ```typescript
1598
+ import {
1599
+ // Core hooks
1600
+ useSPFxProperties,
1601
+ useSPFxContext,
1602
+ useSPFxSPHttpClient,
1603
+
1604
+ // PnP hooks
1605
+ useSPFxPnP,
1606
+ useSPFxPnPList,
1607
+ useSPFxPnPSearch,
1608
+ useSPFxPnPContext
1609
+ } from '@apvee/spfx-react-toolkit';
1610
+ ```
1611
+
1612
+ **Bundle Size Optimization:**
1613
+
1614
+ - **Don't use PnP hooks?** Tree-shaking removes all PnP code from your bundle (0 KB overhead)
1615
+ - **Use PnP hooks?** Only imported hooks and their dependencies are bundled (~30-50 KB compressed)
1616
+ - **SPFx bundler optimization:** Webpack automatically excludes unused code
1617
+
1618
+ **When to use PnP hooks:**
1619
+ - ✅ You prefer PnPjs fluent API over native SPHttpClient
1620
+ - ✅ You need advanced features like batching, caching, selective queries
1621
+ - ✅ You want cleaner, more maintainable code for SharePoint operations
1622
+
1623
+ See [PNPJS_SETUP.md](./PNPJS_SETUP.md) for complete installation and troubleshooting guide.
1624
+
1625
+ ### Available PnP Hooks
1626
+
1627
+ #### `useSPFxPnPContext(siteUrl?, options?)`
1628
+
1629
+ Factory hook for creating configured PnPjs SPFI instances with cache, batching, and cross-site support.
1630
+
1631
+ **Import:**
1632
+ ```typescript
1633
+ import { useSPFxPnPContext } from '@apvee/spfx-react-toolkit';
1634
+ ```
1635
+
1636
+ **Parameters:
1637
+ - `siteUrl?: string` - Target site URL (default: current site)
1638
+ - `options?: PnPContextOptions` - Configuration for cache, batching, etc.
1639
+
1640
+ **Returns:** `PnPContextInfo`
1641
+ - `sp: SPFI | undefined` - Configured SPFI instance
1642
+ - `isInitialized: boolean` - Whether sp instance is ready
1643
+ - `error?: Error` - Initialization error
1644
+ - `siteUrl: string` - Effective site URL
1645
+
1646
+ **Example:**
1647
+ ```typescript
1648
+ // Current site
1649
+ const { sp, isInitialized, error } = useSPFxPnPContext();
1650
+
1651
+ // Cross-site with caching
1652
+ const hrContext = useSPFxPnPContext('/sites/hr', {
1653
+ cache: {
1654
+ enabled: true,
1655
+ storage: 'session',
1656
+ timeout: 600000 // 10 minutes
1657
+ }
1658
+ });
1659
+ ```
1660
+
1661
+ ---
1662
+
1663
+ #### `useSPFxPnP(pnpContext?)`
1664
+
1665
+ General-purpose wrapper for any PnP operation with state management and batching.
1666
+
1667
+ **Import:**
1668
+ ```typescript
1669
+ import { useSPFxPnP } from '@apvee/spfx-react-toolkit';
1670
+ ```
1671
+
1672
+ **Parameters:
1673
+ - `pnpContext?: PnPContextInfo` - Optional context from `useSPFxPnPContext` (default: current site)
1674
+
1675
+ **Returns:** `SPFxPnPInfo`
1676
+ - `sp: SPFI | undefined` - SPFI instance for direct access
1677
+ - `invoke: <T>(fn) => Promise<T>` - Execute single operation with state management
1678
+ - `batch: <T>(fn) => Promise<T>` - Execute batch operations
1679
+ - `isLoading: boolean` - Loading state (tracks `invoke`/`batch` calls only)
1680
+ - `error?: Error` - Error from operations
1681
+ - `clearError: () => void` - Clear error state
1682
+ - `isInitialized: boolean` - SP instance ready
1683
+ - `siteUrl: string` - Effective site URL
1684
+
1685
+ **Selective Imports Required:**
1686
+ ```typescript
1687
+ // Import only what you need
1688
+ import '@pnp/sp/lists';
1689
+ import '@pnp/sp/items';
1690
+ import '@pnp/sp/files';
1691
+ import '@pnp/sp/search';
1692
+ ```
1693
+
1694
+ **Example:**
1695
+ ```typescript
1696
+ import '@pnp/sp/lists';
1697
+ import '@pnp/sp/items';
1698
+
1699
+ function ListsViewer() {
1700
+ const { invoke, batch, isLoading, error } = useSPFxPnP();
1701
+
1702
+ // Single operation
1703
+ const loadLists = async () => {
1704
+ const lists = await invoke(sp => sp.web.lists());
1705
+ return lists;
1706
+ };
1707
+
1708
+ // Batch operation (single HTTP request)
1709
+ const loadDashboard = async () => {
1710
+ const [user, lists, tasks] = await batch(async (batchedSP) => {
1711
+ const user = batchedSP.web.currentUser();
1712
+ const lists = batchedSP.web.lists();
1713
+ const tasks = batchedSP.web.lists.getByTitle('Tasks').items.top(10)();
1714
+
1715
+ return Promise.all([user, lists, tasks]);
1716
+ });
1717
+
1718
+ return { user, lists, tasks };
1719
+ };
1720
+
1721
+ if (isLoading) return <Spinner />;
1722
+ if (error) return <MessageBar>{error.message}</MessageBar>;
1723
+
1724
+ return <button onClick={loadDashboard}>Load Dashboard</button>;
1725
+ }
1726
+ ```
1727
+
1728
+ ---
1729
+
1730
+ #### `useSPFxPnPList<T>(listTitle, options?, pnpContext?)`
1731
+
1732
+ Specialized hook for SharePoint list operations with **type-safe fluent filter API** and CRUD operations.
1733
+
1734
+ **Parameters:**
1735
+ - `listTitle: string` - List title
1736
+ - `options?: ListQueryOptions` - Query options (filter, select, orderBy, top, etc.)
1737
+ - `pnpContext?: PnPContextInfo` - Optional context for cross-site operations
1738
+
1739
+ **Returns:** `SPFxPnPListInfo<T>`
1740
+ - `items: T[]` - Current items
1741
+ - `loading: boolean` - Loading state
1742
+ - `error?: Error` - Error state
1743
+ - `hasMore: boolean` - More items available
1744
+ - `loadMore: () => Promise<void>` - Load next page
1745
+ - `loadingMore: boolean` - Loading more state
1746
+ - `refetch: () => Promise<void>` - Reload items
1747
+ - `create: (data: Partial<T>) => Promise<number>` - Create single item
1748
+ - `createBatch: (items: Partial<T>[]) => Promise<number[]>` - Batch create
1749
+ - `update: (id: number, data: Partial<T>) => Promise<void>` - Update item
1750
+ - `updateBatch: (updates: Array<{id: number, data: Partial<T>}>) => Promise<void>` - Batch update
1751
+ - `remove: (id: number) => Promise<void>` - Delete item
1752
+ - `removeBatch: (ids: number[]) => Promise<void>` - Batch delete
1753
+
1754
+ **Type-Safe Fluent Filter (PnPjs v4):**
1755
+ ```typescript
1756
+ interface Task {
1757
+ Id: number;
1758
+ Title: string;
1759
+ Status: string;
1760
+ Priority: number;
1761
+ DueDate: string;
1762
+ }
1763
+
1764
+ const { items } = useSPFxPnPList<Task>('Tasks', {
1765
+ // Type-safe fluent filter (recommended)
1766
+ filter: f => f.text("Status").equals("Active")
1767
+ .and()
1768
+ .number("Priority").greaterThan(3),
1769
+ select: ['Id', 'Title', 'Status', 'Priority'],
1770
+ orderBy: 'Priority desc',
1771
+ top: 50
1772
+ });
1773
+ ```
1774
+
1775
+ **CRUD Operations:**
1776
+ ```typescript
1777
+ const { items, create, update, remove, createBatch } = useSPFxPnPList<Task>('Tasks');
1778
+
1779
+ // Create
1780
+ const newId = await create({ Title: 'New Task', Status: 'Active' });
1781
+
1782
+ // Batch create
1783
+ const ids = await createBatch([
1784
+ { Title: 'Task 1', Status: 'Active' },
1785
+ { Title: 'Task 2', Status: 'Active' }
1786
+ ]);
1787
+
1788
+ // Update
1789
+ await update(newId, { Status: 'Completed' });
1790
+
1791
+ // Delete
1792
+ await remove(newId);
1793
+ ```
1794
+
1795
+ **Pagination:**
1796
+ ```typescript
1797
+ const { items, hasMore, loadMore, loadingMore } = useSPFxPnPList<Task>('Tasks', {
1798
+ top: 50,
1799
+ orderBy: 'Created desc'
1800
+ });
1801
+
1802
+ return (
1803
+ <>
1804
+ {items.map(item => <TaskCard key={item.Id} task={item} />)}
1805
+ {hasMore && (
1806
+ <button onClick={loadMore} disabled={loadingMore}>
1807
+ {loadingMore ? 'Loading...' : 'Load More'}
1808
+ </button>
1809
+ )}
1810
+ </>
1811
+ );
1812
+ ```
1813
+
1814
+ ---
1815
+
1816
+ #### `useSPFxPnPSearch(query, options?, pnpContext?)`
1817
+
1818
+ Specialized hook for SharePoint Search API with managed properties and refiners.
1819
+
1820
+ See [PNPJS_SETUP.md](./PNPJS_SETUP.md) for complete documentation.
1821
+
1822
+ ---
1823
+
1824
+ **For more examples and detailed documentation, see [PNPJS_SETUP.md](./PNPJS_SETUP.md).**
1825
+
1826
+ ---
1827
+
1828
+ ## TypeScript Support
1829
+
1830
+ All hooks and providers are fully typed with comprehensive TypeScript support. Import types as needed:
1831
+
1832
+ ```typescript
1833
+ import type {
1834
+ // Core Provider Props
1835
+ SPFxWebPartProviderProps,
1836
+ SPFxApplicationCustomizerProviderProps,
1837
+ SPFxFieldCustomizerProviderProps,
1838
+ SPFxListViewCommandSetProviderProps,
1839
+
1840
+ // Core Context Types
1841
+ HostKind,
1842
+ SPFxComponent,
1843
+ SPFxContextType,
1844
+ SPFxContextValue,
1845
+ ContainerSize,
1846
+
1847
+ // Hook Return Types
1848
+ SPFxPropertiesInfo,
1849
+ SPFxDisplayModeInfo,
1850
+ SPFxInstanceInfo,
1851
+ SPFxEnvironmentInfo,
1852
+ SPFxPageTypeInfo,
1853
+ SPFxUserInfo,
1854
+ SPFxSiteInfo,
1855
+ SPFxGroupInfo,
1856
+ SPFxLocaleInfo,
1857
+ SPFxListInfo,
1858
+ SPFxHubSiteInfo,
1859
+ SPFxThemeInfo,
1860
+ SPFxFluent9ThemeInfo,
1861
+ SPFxContainerInfo,
1862
+ SPFxContainerSizeInfo,
1863
+ SPFxStorageHook,
1864
+ SPFxPerformanceInfo,
1865
+ SPFxPerfResult,
1866
+ SPFxLoggerInfo,
1867
+ LogEntry,
1868
+ LogLevel,
1869
+ SPFxCorrelationInfo,
1870
+ SPFxPermissionsInfo,
1871
+ SPFxTeamsInfo,
1872
+ TeamsTheme,
1873
+
1874
+ // HTTP Clients
1875
+ SPFxSPHttpClientInfo,
1876
+ SPFxMSGraphClientInfo,
1877
+ SPFxAadHttpClientInfo,
1878
+ SPFxOneDriveAppDataResult,
1879
+
1880
+ // PnP Types (if using PnPjs)
1881
+ PnPContextInfo,
1882
+ SPFxPnPInfo,
1883
+ SPFxPnPListInfo,
1884
+ SPFxPnPSearchInfo,
1885
+ } from 'spfx-react-toolkit';
1886
+ ```
1887
+
1888
+ **Type Inference:** All hooks provide full type inference when using TypeScript. Use generics where applicable (e.g., `useSPFxProperties<IMyProps>()`) for enhanced type safety.
1889
+
1890
+ ---
1891
+
1892
+ ## Architecture
1893
+
1894
+ ### State Management
1895
+
1896
+ The toolkit uses **Jotai** for atomic state management with per-instance scoping:
1897
+
1898
+ - **Atomic Design**: Each piece of state (properties, displayMode, theme, etc.) is an independent atom
1899
+ - **Instance Scoping**: `atomFamily` creates separate atom instances per SPFx component ID
1900
+ - **Multi-Instance Support**: Multiple WebParts on the same page work independently
1901
+ - **Minimal Bundle**: Jotai adds only ~3KB to bundle size
1902
+ - **React-Native**: Built for React, works with Concurrent Mode
1903
+
1904
+ ### Provider Responsibilities
1905
+
1906
+ The `SPFxProvider` (and its type-specific variants) handle:
1907
+
1908
+ 1. **Component Detection**: Automatically detects WebPart, Extension, or Command Set
1909
+ 2. **Instance Scoping**: Initializes Jotai atoms scoped to unique instance ID
1910
+ 3. **Property Sync**: Subscribes to Property Pane changes (WebParts) and syncs to atoms
1911
+ 4. **Bidirectional Updates**: Syncs hook-based property updates back to SPFx
1912
+ 5. **Container Tracking**: Monitors container size with ResizeObserver
1913
+ 6. **Theme Subscription**: Listens for theme changes and updates atoms
1914
+ 7. **Cleanup**: Proper disposal on unmount
1915
+
1916
+ ### Hook Pattern
1917
+
1918
+ All hooks follow a consistent design:
1919
+
1920
+ 1. **Access Context**: Get instance metadata via `useSPFxContext()`
1921
+ 2. **Read Atoms**: Access instance-scoped atoms via `atomFamily(instanceId)`
1922
+ 3. **Return Interface**: Provide read-only or controlled interfaces (no direct atom exposure)
1923
+ 4. **Type Safety**: Full TypeScript inference with zero `any` usage
1924
+
1925
+ ### Why Jotai?
1926
+
1927
+ - ✅ **Atomic**: Independent state units prevent unnecessary re-renders
1928
+ - ✅ **Scoped**: `atomFamily` enables perfect isolation between instances
1929
+ - ✅ **Minimal**: Small bundle size (~3KB)
1930
+ - ✅ **Modern**: Built for React, supports Concurrent Mode
1931
+ - ✅ **TypeScript-First**: Excellent type inference
1932
+
1933
+ ---
1934
+
1935
+ ## Best Practices
1936
+
1937
+ 1. **Always Use Provider**: Wrap your entire component tree with the appropriate Provider
1938
+ 2. **Type Your Properties**: Use generics like `useSPFxProperties<IMyProps>()` for type safety
1939
+ 3. **Handle Undefined**: Some hooks return optional data (list info, hub info, Teams context)
1940
+ 4. **Storage Keys**: Use descriptive keys for localStorage/sessionStorage
1941
+ 5. **Performance Monitoring**: Leverage `useSPFxPerformance` for critical operations
1942
+ 6. **Structured Logging**: Use `useSPFxLogger` with correlation IDs for better diagnostics
1943
+ 7. **Error Handling**: Always wrap HTTP client calls in try-catch blocks
1944
+ 8. **Memoization**: Use `useMemo`/`useCallback` for expensive computations based on hook data
1945
+ 9. **Responsive Design**: Use `useSPFxContainerSize` for adaptive layouts
1946
+ 10. **Permission Checks**: Gate features with `useSPFxPermissions` for better UX
1947
+
1948
+ ---
1949
+
1950
+ ## Troubleshooting
1951
+
1952
+ ### Provider Errors
1953
+
1954
+ **Error: "useSPFxContext must be used within SPFxProvider"**
1955
+ - Ensure your component tree is wrapped with the appropriate Provider
1956
+ - Verify hooks are called inside functional components, not outside
1957
+ - Check that the Provider is mounted before hook calls
1958
+
1959
+ ### Property Sync Issues
1960
+
1961
+ - **Properties not updating?** Use `setProperties` from `useSPFxProperties`, not direct SPFx property mutation
1962
+ - **Property Pane not reflecting changes?** Ensure SPFx instance properties are mutable
1963
+ - **Sync delays?** Property sync is intentional - hooks update immediately, Property Pane follows
1964
+
1965
+ ### Teams Context Not Available
1966
+
1967
+ - Teams context loads asynchronously - always check `supported` flag before using
1968
+ - In local workbench, Teams context won't be available
1969
+ - Requires SPFx to be running inside Teams environment
1970
+
1971
+ ### Storage Not Persisting
1972
+
1973
+ - Check browser settings - localStorage/sessionStorage may be disabled
1974
+ - Storage keys are automatically scoped per instance - different instances have isolated storage
1975
+ - Session storage clears when tab closes (by design)
1976
+
1977
+ ### Type Errors
1978
+
1979
+ - Import types from the library: `import type { ... } from 'spfx-react-toolkit'`
1980
+ - Use type assertions when accessing SPFx context-specific properties
1981
+ - Check that generics are properly specified (e.g., `useSPFxProperties<IMyProps>()`)
1982
+
1983
+ ---
1984
+
1985
+ ## Compatibility
1986
+
1987
+ - **SPFx Version**: >=1.18.0
1988
+ - **Node.js**: Node.js version aligned with your SPFx version (e.g., Node 18.x for SPFx 1.21.1 - see [SPFx compatibility table](https://learn.microsoft.com/sharepoint/dev/spfx/compatibility))
1989
+ - **React**: 17.x (SPFx standard)
1990
+ - **TypeScript**: ~5.3.3
1991
+ - **Jotai**: ^2.0.0
1992
+ - **Browsers**: Modern browsers (Chrome, Edge, Firefox, Safari)
1993
+ - **SharePoint**: SharePoint Online
1994
+ - **Microsoft 365**: Teams, Office, Outlook (with SPFx support)
1995
+
1996
+ ---
1997
+
1998
+ ## License
1999
+
2000
+ MIT License
2001
+
2002
+ ---
2003
+
2004
+ ## Links
2005
+
2006
+ - **GitHub Repository**: [https://github.com/apvee/spfx-react-toolkit](https://github.com/apvee/spfx-react-toolkit)
2007
+ - **Issues**: [https://github.com/apvee/spfx-react-toolkit/issues](https://github.com/apvee/spfx-react-toolkit/issues)
2008
+ - **NPM Package**: [@apvee/spfx-react-toolkit](https://www.npmjs.com/package/@apvee/spfx-react-toolkit)
2009
+
2010
+ ---
2011
+
2012
+ Made with ❤️ by [Apvee Solutions](https://github.com/apvee)