@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
@@ -0,0 +1,683 @@
1
+ // useSPFxTenantProperty.ts
2
+ // Hook to manage tenant-wide properties using SharePoint StorageEntity API
3
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
+ return new (P || (P = Promise))(function (resolve, reject) {
6
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10
+ });
11
+ };
12
+ var __generator = (this && this.__generator) || function (thisArg, body) {
13
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
14
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
15
+ function verb(n) { return function (v) { return step([n, v]); }; }
16
+ function step(op) {
17
+ if (f) throw new TypeError("Generator is already executing.");
18
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
19
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
20
+ if (y = 0, t) op = [op[0] & 2, t.value];
21
+ switch (op[0]) {
22
+ case 0: case 1: t = op; break;
23
+ case 4: _.label++; return { value: op[1], done: false };
24
+ case 5: _.label++; y = op[1]; op = [0]; continue;
25
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
26
+ default:
27
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
28
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
29
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
30
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
31
+ if (t[2]) _.ops.pop();
32
+ _.trys.pop(); continue;
33
+ }
34
+ op = body.call(thisArg, _);
35
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
36
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
37
+ }
38
+ };
39
+ import { useState, useCallback, useEffect, useRef } from 'react';
40
+ import { useSPFxSPHttpClient } from './useSPFxSPHttpClient';
41
+ import { useSPFxPageContext } from './useSPFxPageContext';
42
+ import { SPHttpClient } from '@microsoft/sp-http';
43
+ /**
44
+ * Hook to manage tenant-wide properties using SharePoint StorageEntity API
45
+ *
46
+ * Provides read/write operations for tenant-scoped properties stored in the
47
+ * SharePoint tenant app catalog. Properties are accessible across all sites
48
+ * in the tenant and support metadata (description only).
49
+ *
50
+ * Features:
51
+ * - Tenant-wide centralized storage (not site-specific)
52
+ * - Smart serialization for primitives, Date, and complex objects
53
+ * - Permission checking (canWrite flag)
54
+ * - Optional metadata (description only - SharePoint limitation)
55
+ * - Type-safe with TypeScript generics
56
+ * - Memory leak safe with mounted state tracking
57
+ * - Automatic app catalog URL discovery
58
+ * - Remove operation for cleanup
59
+ *
60
+ * Requirements:
61
+ * - Tenant app catalog must be provisioned
62
+ * - Read: Any authenticated user
63
+ * - Write/Remove: Site Collection Administrator role on tenant app catalog site
64
+ *
65
+ * Permission Notes:
66
+ * - Being a Tenant Admin is NOT sufficient for write operations
67
+ * - You must be explicitly added as Site Collection Administrator to the tenant app catalog
68
+ * - Navigate to the app catalog site → Site Settings → Site Collection Administrators
69
+ * - Add your user account if write operations fail with permission errors
70
+ *
71
+ * Storage Format:
72
+ * - Primitives (string, number, boolean, null) → stored as string
73
+ * - Date → stored as ISO 8601 string
74
+ * - Objects/arrays → stored as JSON string
75
+ *
76
+ * @param key - Unique property key (e.g., 'appVersion', 'apiEndpoint', 'featureFlags')
77
+ * @param autoFetch - Whether to automatically load property on mount. Default: true
78
+ *
79
+ * @returns Object with data, metadata, loading states, error states, and CRUD functions
80
+ *
81
+ * @example Basic usage - string property
82
+ * ```tsx
83
+ * function VersionDisplay() {
84
+ * const { data, isLoading, error, write, canWrite } =
85
+ * useSPFxTenantProperty<string>('appVersion');
86
+ *
87
+ * if (isLoading) return <Spinner label="Loading version..." />;
88
+ * if (error) return <MessageBar messageBarType={MessageBarType.error}>
89
+ * Failed to load: {error.message}
90
+ * </MessageBar>;
91
+ *
92
+ * const handleUpdate = async () => {
93
+ * if (!canWrite) {
94
+ * alert('Insufficient permissions');
95
+ * return;
96
+ * }
97
+ *
98
+ * try {
99
+ * await write('2.0.1', 'Current application version');
100
+ * console.log('Version updated!');
101
+ * } catch (err) {
102
+ * console.error('Update failed:', err);
103
+ * }
104
+ * };
105
+ *
106
+ * return (
107
+ * <div>
108
+ * <Text>Current Version: {data ?? 'Not Set'}</Text>
109
+ * {canWrite && <PrimaryButton onClick={handleUpdate}>Update Version</PrimaryButton>}
110
+ * </div>
111
+ * );
112
+ * }
113
+ * ```
114
+ *
115
+ * @example Number property
116
+ * ```tsx
117
+ * const { data, write } = useSPFxTenantProperty<number>('maxUploadSize');
118
+ *
119
+ * // Stored as "10485760" (10MB in bytes)
120
+ * await write(10485760, 'Maximum file upload size in bytes');
121
+ *
122
+ * // Read returns: 10485760 (number)
123
+ * console.log(typeof data); // "number"
124
+ * ```
125
+ *
126
+ * @example Boolean flag
127
+ * ```tsx
128
+ * const { data: maintenanceMode, write } = useSPFxTenantProperty<boolean>('maintenanceMode');
129
+ *
130
+ * // Stored as "true" or "false"
131
+ * await write(true, 'Maintenance mode enabled');
132
+ *
133
+ * if (maintenanceMode) {
134
+ * return <MessageBar>System is under maintenance</MessageBar>;
135
+ * }
136
+ * ```
137
+ *
138
+ * @example Date property
139
+ * ```tsx
140
+ * const { data, write } = useSPFxTenantProperty<string>('lastDeployment');
141
+ *
142
+ * // Write date as ISO string
143
+ * await write(new Date().toISOString(), 'Last deployment timestamp');
144
+ *
145
+ * // Read and convert back to Date
146
+ * const lastDeploy = data ? new Date(data) : undefined;
147
+ * ```
148
+ *
149
+ * @example Complex object with JSON
150
+ * ```tsx
151
+ * interface FeatureFlags {
152
+ * enableChat: boolean;
153
+ * enableAnalytics: boolean;
154
+ * maxUsers: number;
155
+ * }
156
+ *
157
+ * const { data, write, isLoading } = useSPFxTenantProperty<FeatureFlags>('featureFlags');
158
+ *
159
+ * // Stored as JSON string
160
+ * await write({
161
+ * enableChat: true,
162
+ * enableAnalytics: false,
163
+ * maxUsers: 1000
164
+ * }, 'Global feature flags configuration');
165
+ *
166
+ * // Read returns parsed object
167
+ * if (data?.enableChat) {
168
+ * return <ChatPanel />;
169
+ * }
170
+ * ```
171
+ *
172
+ * @example Permission-aware UI
173
+ * ```tsx
174
+ * function TenantConfigPanel() {
175
+ * const { data, canWrite, write, isWriting, error, writeError } =
176
+ * useSPFxTenantProperty<string>('apiEndpoint');
177
+ *
178
+ * const [editValue, setEditValue] = React.useState(data ?? '');
179
+ *
180
+ * React.useEffect(() => {
181
+ * setEditValue(data ?? '');
182
+ * }, [data]);
183
+ *
184
+ * if (!canWrite) {
185
+ * return (
186
+ * <MessageBar messageBarType={MessageBarType.info}>
187
+ * You don't have permission to edit tenant properties.
188
+ * Contact your SharePoint administrator.
189
+ * </MessageBar>
190
+ * );
191
+ * }
192
+ *
193
+ * const handleSave = async () => {
194
+ * try {
195
+ * await write(editValue, 'Production API endpoint URL');
196
+ * } catch (err) {
197
+ * console.error('Save failed:', err);
198
+ * }
199
+ * };
200
+ *
201
+ * return (
202
+ * <Stack tokens={{ childrenGap: 10 }}>
203
+ * <TextField
204
+ * label="API Endpoint"
205
+ * value={editValue}
206
+ * onChange={(_, val) => setEditValue(val ?? '')}
207
+ * disabled={isWriting}
208
+ * />
209
+ * <PrimaryButton
210
+ * onClick={handleSave}
211
+ * disabled={isWriting || editValue === data}
212
+ * >
213
+ * {isWriting ? 'Saving...' : 'Save'}
214
+ * </PrimaryButton>
215
+ * {writeError && (
216
+ * <MessageBar messageBarType={MessageBarType.error}>
217
+ * {writeError.message}
218
+ * </MessageBar>
219
+ * )}
220
+ * </Stack>
221
+ * );
222
+ * }
223
+ * ```
224
+ *
225
+ * @example With metadata viewing
226
+ * ```tsx
227
+ * function PropertyViewer() {
228
+ * const { data, description, isLoading } =
229
+ * useSPFxTenantProperty<string>('appConfig');
230
+ *
231
+ * if (isLoading) return <Spinner />;
232
+ *
233
+ * return (
234
+ * <Stack tokens={{ childrenGap: 5 }}>
235
+ * <Text variant="large">Value: {data}</Text>
236
+ * {description && <Text variant="small">Description: {description}</Text>}
237
+ * </Stack>
238
+ * );
239
+ * }
240
+ * ```
241
+ *
242
+ * @example Lazy loading with manual trigger
243
+ * ```tsx
244
+ * const { data, load, isLoading, write } = useSPFxTenantProperty<Config>(
245
+ * 'appConfig',
246
+ * false // Don't auto-fetch
247
+ * );
248
+ *
249
+ * return (
250
+ * <div>
251
+ * <button onClick={load} disabled={isLoading}>
252
+ * {isLoading ? 'Loading...' : 'Load Config'}
253
+ * </button>
254
+ * {data && <ConfigDisplay config={data} />}
255
+ * </div>
256
+ * );
257
+ * ```
258
+ *
259
+ * @example Property removal
260
+ * ```tsx
261
+ * function PropertyManager() {
262
+ * const { data, remove, canWrite } = useSPFxTenantProperty<string>('deprecatedSetting');
263
+ *
264
+ * const handleDelete = async () => {
265
+ * if (!confirm('Delete this property? This cannot be undone.')) return;
266
+ *
267
+ * try {
268
+ * await remove();
269
+ * console.log('Property deleted');
270
+ * } catch (err) {
271
+ * console.error('Delete failed:', err);
272
+ * }
273
+ * };
274
+ *
275
+ * if (!data) return <Text>Property not found</Text>;
276
+ *
277
+ * return (
278
+ * <Stack tokens={{ childrenGap: 10 }}>
279
+ * <Text>Value: {data}</Text>
280
+ * {canWrite && (
281
+ * <DefaultButton onClick={handleDelete} text="Delete Property" />
282
+ * )}
283
+ * </Stack>
284
+ * );
285
+ * }
286
+ * ```
287
+ *
288
+ * @example Multi-property dashboard
289
+ * ```tsx
290
+ * function TenantDashboard() {
291
+ * const version = useSPFxTenantProperty<string>('appVersion');
292
+ * const maintenance = useSPFxTenantProperty<boolean>('maintenanceMode');
293
+ * const lastUpdate = useSPFxTenantProperty<string>('lastUpdate');
294
+ * const config = useSPFxTenantProperty<AppConfig>('appConfig');
295
+ *
296
+ * const isLoading = version.isLoading || maintenance.isLoading ||
297
+ * lastUpdate.isLoading || config.isLoading;
298
+ *
299
+ * if (isLoading) return <Spinner label="Loading dashboard..." />;
300
+ *
301
+ * return (
302
+ * <Stack tokens={{ childrenGap: 20 }}>
303
+ * <Text variant="xxLarge">Tenant Configuration</Text>
304
+ *
305
+ * <Stack tokens={{ childrenGap: 10 }}>
306
+ * <Label>App Version: {version.data ?? 'Not Set'}</Label>
307
+ * <Label>Maintenance Mode: {maintenance.data ? 'ON' : 'OFF'}</Label>
308
+ * <Label>Last Update: {lastUpdate.data ? new Date(lastUpdate.data).toLocaleString() : 'Never'}</Label>
309
+ * </Stack>
310
+ *
311
+ * {config.data && (
312
+ * <Stack tokens={{ childrenGap: 5 }}>
313
+ * <Text variant="large">Configuration</Text>
314
+ * <pre>{JSON.stringify(config.data, null, 2)}</pre>
315
+ * </Stack>
316
+ * )}
317
+ * </Stack>
318
+ * );
319
+ * }
320
+ * ```
321
+ */
322
+ export function useSPFxTenantProperty(key, autoFetch) {
323
+ var _this = this;
324
+ if (autoFetch === void 0) { autoFetch = true; }
325
+ var spHttpClient = useSPFxSPHttpClient().client;
326
+ var pageContext = useSPFxPageContext();
327
+ // State management
328
+ var _a = useState(undefined), data = _a[0], setData = _a[1];
329
+ var _b = useState(undefined), description = _b[0], setDescription = _b[1];
330
+ var _c = useState(false), isLoading = _c[0], setIsLoading = _c[1];
331
+ var _d = useState(undefined), error = _d[0], setError = _d[1];
332
+ var _e = useState(false), isWriting = _e[0], setIsWriting = _e[1];
333
+ var _f = useState(undefined), writeError = _f[0], setWriteError = _f[1];
334
+ var _g = useState(false), canWrite = _g[0], setCanWrite = _g[1];
335
+ var _h = useState(undefined), appCatalogUrl = _h[0], setAppCatalogUrl = _h[1];
336
+ // Track component mounted state to prevent memory leaks
337
+ var isMounted = useRef(true);
338
+ useEffect(function () {
339
+ isMounted.current = true;
340
+ return function () {
341
+ isMounted.current = false;
342
+ };
343
+ }, []);
344
+ /**
345
+ * Check if value is a primitive type (including null, Date)
346
+ */
347
+ var isPrimitive = useCallback(function (val) {
348
+ if (val === null)
349
+ return true;
350
+ var type = typeof val;
351
+ // ES5-compatible array check
352
+ var primitiveTypes = ['string', 'number', 'boolean', 'bigint'];
353
+ if (primitiveTypes.indexOf(type) !== -1)
354
+ return true;
355
+ // Date is special: treat as primitive and convert to ISO string
356
+ if (val instanceof Date)
357
+ return true;
358
+ return false;
359
+ }, []);
360
+ /**
361
+ * Serialize content for storage
362
+ * - Primitives → String(content)
363
+ * - Date → ISO string
364
+ * - Objects/arrays → JSON.stringify()
365
+ */
366
+ var serializeValue = useCallback(function (content) {
367
+ if (isPrimitive(content)) {
368
+ return content instanceof Date ? content.toISOString() : String(content);
369
+ }
370
+ return JSON.stringify(content);
371
+ }, [isPrimitive]);
372
+ /**
373
+ * Deserialize value from storage
374
+ * - Try JSON.parse first
375
+ * - If fails, return raw string (will be cast to T by TypeScript)
376
+ */
377
+ var deserializeValue = useCallback(function (rawValue) {
378
+ try {
379
+ return JSON.parse(rawValue);
380
+ }
381
+ catch (_a) {
382
+ // Not valid JSON, assume it's a primitive value
383
+ return rawValue;
384
+ }
385
+ }, []);
386
+ /**
387
+ * Discover tenant app catalog URL
388
+ */
389
+ var discoverAppCatalogUrl = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
390
+ var response, data_1, err_1;
391
+ return __generator(this, function (_a) {
392
+ switch (_a.label) {
393
+ case 0:
394
+ if (!spHttpClient || !pageContext) {
395
+ throw new Error('SPHttpClient or PageContext not available');
396
+ }
397
+ // Check cache first
398
+ if (appCatalogUrl) {
399
+ return [2 /*return*/, appCatalogUrl];
400
+ }
401
+ _a.label = 1;
402
+ case 1:
403
+ _a.trys.push([1, 4, , 5]);
404
+ return [4 /*yield*/, spHttpClient.get("".concat(pageContext.web.absoluteUrl, "/_api/SP_TenantSettings_Current"), SPHttpClient.configurations.v1)];
405
+ case 2:
406
+ response = _a.sent();
407
+ if (!response.ok) {
408
+ throw new Error("Failed to discover app catalog: ".concat(response.statusText));
409
+ }
410
+ return [4 /*yield*/, response.json()];
411
+ case 3:
412
+ data_1 = _a.sent();
413
+ if (!data_1.CorporateCatalogUrl) {
414
+ throw new Error('Tenant app catalog is not provisioned. Please provision the app catalog first.');
415
+ }
416
+ if (isMounted.current) {
417
+ setAppCatalogUrl(data_1.CorporateCatalogUrl);
418
+ }
419
+ return [2 /*return*/, data_1.CorporateCatalogUrl];
420
+ case 4:
421
+ err_1 = _a.sent();
422
+ throw new Error("App catalog discovery failed: ".concat(err_1 instanceof Error ? err_1.message : String(err_1)));
423
+ case 5: return [2 /*return*/];
424
+ }
425
+ });
426
+ }); }, [spHttpClient, pageContext, appCatalogUrl]);
427
+ /**
428
+ * Check write permissions on tenant app catalog
429
+ * User must be Site Collection Administrator of the tenant app catalog
430
+ */
431
+ var checkWritePermission = useCallback(function (catalogUrl) { return __awaiter(_this, void 0, void 0, function () {
432
+ var response, user, _a;
433
+ return __generator(this, function (_b) {
434
+ switch (_b.label) {
435
+ case 0:
436
+ if (!spHttpClient)
437
+ return [2 /*return*/, false];
438
+ _b.label = 1;
439
+ case 1:
440
+ _b.trys.push([1, 4, , 5]);
441
+ return [4 /*yield*/, spHttpClient.get("".concat(catalogUrl, "/_api/web/currentuser?$select=IsSiteAdmin"), SPHttpClient.configurations.v1)];
442
+ case 2:
443
+ response = _b.sent();
444
+ if (!response.ok)
445
+ return [2 /*return*/, false];
446
+ return [4 /*yield*/, response.json()];
447
+ case 3:
448
+ user = _b.sent();
449
+ return [2 /*return*/, user.IsSiteAdmin === true];
450
+ case 4:
451
+ _a = _b.sent();
452
+ return [2 /*return*/, false];
453
+ case 5: return [2 /*return*/];
454
+ }
455
+ });
456
+ }); }, [spHttpClient]);
457
+ /**
458
+ * Load property from tenant app catalog
459
+ */
460
+ var load = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
461
+ var catalogUrl, response, entity, err_2, error_1;
462
+ return __generator(this, function (_a) {
463
+ switch (_a.label) {
464
+ case 0:
465
+ if (!spHttpClient || !pageContext) {
466
+ console.warn('SPHttpClient or PageContext not available yet. Skipping load.');
467
+ return [2 /*return*/];
468
+ }
469
+ if (!key) {
470
+ console.warn('key is required. Skipping load.');
471
+ return [2 /*return*/];
472
+ }
473
+ setIsLoading(true);
474
+ setError(undefined);
475
+ _a.label = 1;
476
+ case 1:
477
+ _a.trys.push([1, 5, 6, 7]);
478
+ return [4 /*yield*/, discoverAppCatalogUrl()];
479
+ case 2:
480
+ catalogUrl = _a.sent();
481
+ // Check write permissions (async, non-blocking)
482
+ checkWritePermission(catalogUrl).then(function (hasPermission) {
483
+ if (isMounted.current) {
484
+ setCanWrite(hasPermission);
485
+ }
486
+ }).catch(function () {
487
+ if (isMounted.current) {
488
+ setCanWrite(false);
489
+ }
490
+ });
491
+ return [4 /*yield*/, spHttpClient.get("".concat(catalogUrl, "/_api/web/GetStorageEntity('").concat(encodeURIComponent(key), "')"), SPHttpClient.configurations.v1)];
492
+ case 3:
493
+ response = _a.sent();
494
+ if (!response.ok) {
495
+ throw new Error("Failed to read property: ".concat(response.statusText));
496
+ }
497
+ return [4 /*yield*/, response.json()];
498
+ case 4:
499
+ entity = _a.sent();
500
+ if (isMounted.current) {
501
+ if (entity.Value) {
502
+ setData(deserializeValue(entity.Value));
503
+ setDescription(entity.Description);
504
+ }
505
+ else {
506
+ // Property doesn't exist or has no value
507
+ setData(undefined);
508
+ setDescription(undefined);
509
+ }
510
+ }
511
+ return [3 /*break*/, 7];
512
+ case 5:
513
+ err_2 = _a.sent();
514
+ if (isMounted.current) {
515
+ error_1 = err_2 instanceof Error ? err_2 : new Error(String(err_2));
516
+ setError(error_1);
517
+ console.error('Failed to load tenant property:', error_1);
518
+ }
519
+ return [3 /*break*/, 7];
520
+ case 6:
521
+ if (isMounted.current) {
522
+ setIsLoading(false);
523
+ }
524
+ return [7 /*endfinally*/];
525
+ case 7: return [2 /*return*/];
526
+ }
527
+ });
528
+ }); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl, checkWritePermission, deserializeValue]);
529
+ /**
530
+ * Write property to tenant app catalog
531
+ */
532
+ var write = useCallback(function (content, desc) { return __awaiter(_this, void 0, void 0, function () {
533
+ var catalogUrl, serializedValue, body, response, errorText, err_3, error_2;
534
+ return __generator(this, function (_a) {
535
+ switch (_a.label) {
536
+ case 0:
537
+ if (!spHttpClient || !pageContext) {
538
+ throw new Error('SPHttpClient or PageContext not available. Cannot write property.');
539
+ }
540
+ if (!key) {
541
+ throw new Error('key is required. Cannot write property.');
542
+ }
543
+ setIsWriting(true);
544
+ setWriteError(undefined);
545
+ _a.label = 1;
546
+ case 1:
547
+ _a.trys.push([1, 6, 7, 8]);
548
+ return [4 /*yield*/, discoverAppCatalogUrl()];
549
+ case 2:
550
+ catalogUrl = _a.sent();
551
+ serializedValue = serializeValue(content);
552
+ body = {
553
+ key: key,
554
+ value: serializedValue,
555
+ description: desc !== null && desc !== void 0 ? desc : ''
556
+ // comment is NOT supported by SharePoint SetStorageEntity API
557
+ };
558
+ return [4 /*yield*/, spHttpClient.post("".concat(catalogUrl, "/_api/web/SetStorageEntity"), SPHttpClient.configurations.v1, {
559
+ headers: {
560
+ 'Content-Type': 'application/json;odata=verbose',
561
+ 'Accept': 'application/json;odata=verbose'
562
+ },
563
+ body: JSON.stringify(body)
564
+ })];
565
+ case 3:
566
+ response = _a.sent();
567
+ if (!!response.ok) return [3 /*break*/, 5];
568
+ return [4 /*yield*/, response.text()];
569
+ case 4:
570
+ errorText = _a.sent();
571
+ throw new Error("Failed to write property: ".concat(response.statusText, ". ").concat(errorText));
572
+ case 5:
573
+ if (isMounted.current) {
574
+ // Update local state to reflect successful write
575
+ setData(content);
576
+ setDescription(desc);
577
+ // Clear read error if write succeeds
578
+ setError(undefined);
579
+ }
580
+ return [3 /*break*/, 8];
581
+ case 6:
582
+ err_3 = _a.sent();
583
+ if (isMounted.current) {
584
+ error_2 = err_3 instanceof Error ? err_3 : new Error(String(err_3));
585
+ setWriteError(error_2);
586
+ console.error('Failed to write tenant property:', error_2);
587
+ }
588
+ // Re-throw to allow caller to handle
589
+ throw err_3;
590
+ case 7:
591
+ if (isMounted.current) {
592
+ setIsWriting(false);
593
+ }
594
+ return [7 /*endfinally*/];
595
+ case 8: return [2 /*return*/];
596
+ }
597
+ });
598
+ }); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl, serializeValue]);
599
+ /**
600
+ * Remove property from tenant app catalog
601
+ */
602
+ var remove = useCallback(function () { return __awaiter(_this, void 0, void 0, function () {
603
+ var catalogUrl, response, errorText, err_4, error_3;
604
+ return __generator(this, function (_a) {
605
+ switch (_a.label) {
606
+ case 0:
607
+ if (!spHttpClient || !pageContext) {
608
+ throw new Error('SPHttpClient or PageContext not available. Cannot remove property.');
609
+ }
610
+ if (!key) {
611
+ throw new Error('key is required. Cannot remove property.');
612
+ }
613
+ setIsWriting(true);
614
+ setWriteError(undefined);
615
+ _a.label = 1;
616
+ case 1:
617
+ _a.trys.push([1, 6, 7, 8]);
618
+ return [4 /*yield*/, discoverAppCatalogUrl()];
619
+ case 2:
620
+ catalogUrl = _a.sent();
621
+ return [4 /*yield*/, spHttpClient.post("".concat(catalogUrl, "/_api/web/RemoveStorageEntity('").concat(encodeURIComponent(key), "')"), SPHttpClient.configurations.v1, {
622
+ headers: {
623
+ 'Accept': 'application/json;odata=verbose'
624
+ }
625
+ })];
626
+ case 3:
627
+ response = _a.sent();
628
+ if (!!response.ok) return [3 /*break*/, 5];
629
+ return [4 /*yield*/, response.text()];
630
+ case 4:
631
+ errorText = _a.sent();
632
+ throw new Error("Failed to remove property: ".concat(response.statusText, ". ").concat(errorText));
633
+ case 5:
634
+ if (isMounted.current) {
635
+ // Clear local state
636
+ setData(undefined);
637
+ setDescription(undefined);
638
+ setError(undefined);
639
+ }
640
+ return [3 /*break*/, 8];
641
+ case 6:
642
+ err_4 = _a.sent();
643
+ if (isMounted.current) {
644
+ error_3 = err_4 instanceof Error ? err_4 : new Error(String(err_4));
645
+ setWriteError(error_3);
646
+ console.error('Failed to remove tenant property:', error_3);
647
+ }
648
+ // Re-throw to allow caller to handle
649
+ throw err_4;
650
+ case 7:
651
+ if (isMounted.current) {
652
+ setIsWriting(false);
653
+ }
654
+ return [7 /*endfinally*/];
655
+ case 8: return [2 /*return*/];
656
+ }
657
+ });
658
+ }); }, [spHttpClient, pageContext, key, discoverAppCatalogUrl]);
659
+ // Auto-fetch on mount if enabled
660
+ useEffect(function () {
661
+ if (autoFetch && spHttpClient && pageContext && key) {
662
+ load().catch(function () {
663
+ // Error already handled in load() function
664
+ });
665
+ }
666
+ }, [autoFetch, spHttpClient, pageContext, key, load]);
667
+ // Computed state: ready when data loaded successfully
668
+ var isReady = !isLoading && !error && data !== undefined;
669
+ return {
670
+ data: data,
671
+ description: description,
672
+ isLoading: isLoading,
673
+ error: error,
674
+ isWriting: isWriting,
675
+ writeError: writeError,
676
+ canWrite: canWrite,
677
+ load: load,
678
+ write: write,
679
+ remove: remove,
680
+ isReady: isReady,
681
+ };
682
+ }
683
+ //# sourceMappingURL=useSPFxTenantProperty.js.map