@seamapi/react 5.0.0 → 5.1.1

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 (125) hide show
  1. package/README.md +111 -2
  2. package/dist/ecosystem.js +45 -0
  3. package/dist/ecosystem.js.map +1 -0
  4. package/dist/elements.js +12872 -11737
  5. package/dist/elements.js.map +1 -1
  6. package/dist/index.css +469 -0
  7. package/dist/index.css.map +1 -1
  8. package/dist/index.min.css +1 -1
  9. package/dist/index.min.css.map +1 -1
  10. package/lib/seam/SeamProvider.d.ts +10 -3
  11. package/lib/seam/SeamProvider.js +5 -3
  12. package/lib/seam/SeamProvider.js.map +1 -1
  13. package/lib/seam/components/CompatibleApps/CompatibleApps.d.ts +26 -0
  14. package/lib/seam/components/CompatibleApps/CompatibleApps.js +126 -0
  15. package/lib/seam/components/CompatibleApps/CompatibleApps.js.map +1 -0
  16. package/lib/seam/components/SupportedDevices/DeviceModelRow.d.ts +8 -0
  17. package/lib/seam/components/SupportedDevices/DeviceModelRow.js +31 -0
  18. package/lib/seam/components/SupportedDevices/DeviceModelRow.js.map +1 -0
  19. package/lib/seam/components/SupportedDevices/FilterSelect.d.ts +12 -0
  20. package/lib/seam/components/SupportedDevices/FilterSelect.js +9 -0
  21. package/lib/seam/components/SupportedDevices/FilterSelect.js.map +1 -0
  22. package/lib/seam/components/SupportedDevices/ManufacturerSection.d.ts +10 -0
  23. package/lib/seam/components/SupportedDevices/ManufacturerSection.js +35 -0
  24. package/lib/seam/components/SupportedDevices/ManufacturerSection.js.map +1 -0
  25. package/lib/seam/components/SupportedDevices/SupportedDevices.d.ts +24 -0
  26. package/lib/seam/components/SupportedDevices/SupportedDevices.js +151 -0
  27. package/lib/seam/components/SupportedDevices/SupportedDevices.js.map +1 -0
  28. package/lib/seam/components/SupportedDevices/device-models.d.ts +47 -0
  29. package/lib/seam/components/SupportedDevices/device-models.js +217 -0
  30. package/lib/seam/components/SupportedDevices/device-models.js.map +1 -0
  31. package/lib/seam/components/index.d.ts +2 -0
  32. package/lib/seam/components/index.js +2 -0
  33. package/lib/seam/components/index.js.map +1 -1
  34. package/lib/seam/ecosystem/EcosystemProvider.d.ts +37 -0
  35. package/lib/seam/ecosystem/EcosystemProvider.js +56 -0
  36. package/lib/seam/ecosystem/EcosystemProvider.js.map +1 -0
  37. package/lib/seam/ecosystem/client.d.ts +28 -0
  38. package/lib/seam/ecosystem/client.js +74 -0
  39. package/lib/seam/ecosystem/client.js.map +1 -0
  40. package/lib/seam/ecosystem/index.d.ts +9 -0
  41. package/lib/seam/ecosystem/index.js +8 -0
  42. package/lib/seam/ecosystem/index.js.map +1 -0
  43. package/lib/seam/ecosystem/types.d.ts +312 -0
  44. package/lib/seam/ecosystem/types.js +10 -0
  45. package/lib/seam/ecosystem/types.js.map +1 -0
  46. package/lib/seam/ecosystem/use-compatible-partners.d.ts +21 -0
  47. package/lib/seam/ecosystem/use-compatible-partners.js +36 -0
  48. package/lib/seam/ecosystem/use-compatible-partners.js.map +1 -0
  49. package/lib/seam/ecosystem/use-ecosystem-device-model-summaries.d.ts +11 -0
  50. package/lib/seam/ecosystem/use-ecosystem-device-model-summaries.js +53 -0
  51. package/lib/seam/ecosystem/use-ecosystem-device-model-summaries.js.map +1 -0
  52. package/lib/seam/ecosystem/use-ecosystem-device-models.d.ts +20 -0
  53. package/lib/seam/ecosystem/use-ecosystem-device-models.js +51 -0
  54. package/lib/seam/ecosystem/use-ecosystem-device-models.js.map +1 -0
  55. package/lib/seam/ecosystem/use-ecosystem-manufacturers.d.ts +11 -0
  56. package/lib/seam/ecosystem/use-ecosystem-manufacturers.js +49 -0
  57. package/lib/seam/ecosystem/use-ecosystem-manufacturers.js.map +1 -0
  58. package/lib/seam/ecosystem/use-ecosystem-query-result.d.ts +7 -0
  59. package/lib/seam/ecosystem/use-ecosystem-query-result.js +7 -0
  60. package/lib/seam/ecosystem/use-ecosystem-query-result.js.map +1 -0
  61. package/lib/seam/ecosystem/use-embed-key-resolution.d.ts +20 -0
  62. package/lib/seam/ecosystem/use-embed-key-resolution.js +51 -0
  63. package/lib/seam/ecosystem/use-embed-key-resolution.js.map +1 -0
  64. package/lib/seam/index.d.ts +1 -0
  65. package/lib/seam/index.js +1 -0
  66. package/lib/seam/index.js.map +1 -1
  67. package/lib/seam/seam-components-class-name.d.ts +1 -0
  68. package/lib/seam/seam-components-class-name.js +2 -0
  69. package/lib/seam/seam-components-class-name.js.map +1 -0
  70. package/lib/ui/ecosystem/SearchField.d.ts +9 -0
  71. package/lib/ui/ecosystem/SearchField.js +8 -0
  72. package/lib/ui/ecosystem/SearchField.js.map +1 -0
  73. package/lib/ui/ecosystem/StateMessage.d.ts +8 -0
  74. package/lib/ui/ecosystem/StateMessage.js +5 -0
  75. package/lib/ui/ecosystem/StateMessage.js.map +1 -0
  76. package/lib/ui/ecosystem/errors.d.ts +5 -0
  77. package/lib/ui/ecosystem/errors.js +25 -0
  78. package/lib/ui/ecosystem/errors.js.map +1 -0
  79. package/lib/ui/ecosystem/storybook.d.ts +15 -0
  80. package/lib/ui/ecosystem/storybook.js +16 -0
  81. package/lib/ui/ecosystem/storybook.js.map +1 -0
  82. package/lib/ui/ecosystem/theme.d.ts +6 -0
  83. package/lib/ui/ecosystem/theme.js +11 -0
  84. package/lib/ui/ecosystem/theme.js.map +1 -0
  85. package/lib/version.d.ts +1 -1
  86. package/lib/version.js +1 -1
  87. package/package.json +11 -3
  88. package/src/ecosystem.d.ts +2 -0
  89. package/src/ecosystem.ts +12 -0
  90. package/src/elements.ts +4 -0
  91. package/src/lib/ecosystem-element.tsx +154 -0
  92. package/src/lib/element.tsx +6 -0
  93. package/src/lib/seam/SeamProvider.tsx +18 -2
  94. package/src/lib/seam/components/CompatibleApps/CompatibleApps.element.ts +19 -0
  95. package/src/lib/seam/components/CompatibleApps/CompatibleApps.tsx +344 -0
  96. package/src/lib/seam/components/SupportedDevices/DeviceModelRow.tsx +108 -0
  97. package/src/lib/seam/components/SupportedDevices/FilterSelect.tsx +43 -0
  98. package/src/lib/seam/components/SupportedDevices/ManufacturerSection.tsx +114 -0
  99. package/src/lib/seam/components/SupportedDevices/SupportedDevices.element.ts +17 -0
  100. package/src/lib/seam/components/SupportedDevices/SupportedDevices.tsx +382 -0
  101. package/src/lib/seam/components/SupportedDevices/device-models.ts +326 -0
  102. package/src/lib/seam/components/ecosystem-elements.ts +2 -0
  103. package/src/lib/seam/components/index.ts +2 -0
  104. package/src/lib/seam/ecosystem/EcosystemProvider.tsx +133 -0
  105. package/src/lib/seam/ecosystem/client.ts +116 -0
  106. package/src/lib/seam/ecosystem/index.ts +19 -0
  107. package/src/lib/seam/ecosystem/types.ts +402 -0
  108. package/src/lib/seam/ecosystem/use-compatible-partners.ts +80 -0
  109. package/src/lib/seam/ecosystem/use-ecosystem-device-model-summaries.ts +89 -0
  110. package/src/lib/seam/ecosystem/use-ecosystem-device-models.ts +96 -0
  111. package/src/lib/seam/ecosystem/use-ecosystem-manufacturers.ts +86 -0
  112. package/src/lib/seam/ecosystem/use-ecosystem-query-result.ts +19 -0
  113. package/src/lib/seam/ecosystem/use-embed-key-resolution.ts +80 -0
  114. package/src/lib/seam/index.ts +1 -0
  115. package/src/lib/seam/seam-components-class-name.ts +1 -0
  116. package/src/lib/ui/ecosystem/SearchField.tsx +32 -0
  117. package/src/lib/ui/ecosystem/StateMessage.tsx +29 -0
  118. package/src/lib/ui/ecosystem/errors.ts +30 -0
  119. package/src/lib/ui/ecosystem/storybook.tsx +40 -0
  120. package/src/lib/ui/ecosystem/theme.ts +14 -0
  121. package/src/lib/version.ts +1 -1
  122. package/src/styles/_compatible-apps.scss +102 -0
  123. package/src/styles/_ecosystem.scss +295 -0
  124. package/src/styles/_main.scss +6 -0
  125. package/src/styles/_supported-devices.scss +153 -0
@@ -0,0 +1,51 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { fetchEcosystem, } from '../../../lib/seam/ecosystem/client.js';
3
+ import { useEcosystemContext } from '../../../lib/seam/ecosystem/EcosystemProvider.js';
4
+ import { retryEcosystemQuery, } from '../../../lib/seam/ecosystem/use-ecosystem-query-result.js';
5
+ /**
6
+ * Validates the provider's embed key against the page origin with
7
+ * Ecosystem `GET /v1/embed/resolve` and returns the key's public record
8
+ * and, for partner keys, what the partner's widgets must not show.
9
+ * Disabled when there is no embed key.
10
+ */
11
+ export function useEmbedKeyResolution({ capability = '', } = {}) {
12
+ const { embedKey, endpoint } = useEcosystemContext();
13
+ const params = { capability: capability.trim() };
14
+ const { data, ...rest } = useQuery({
15
+ enabled: embedKey != null,
16
+ queryKey: [
17
+ 'ecosystem',
18
+ endpoint,
19
+ embedKey ?? null,
20
+ 'embed',
21
+ 'resolve',
22
+ params,
23
+ ],
24
+ queryFn: async ({ signal }) => await fetchEcosystem('embed/resolve', {
25
+ endpoint,
26
+ embedKey,
27
+ params,
28
+ signal,
29
+ }),
30
+ retry: retryEcosystemQuery,
31
+ });
32
+ return { ...rest, embedResolution: data };
33
+ }
34
+ /**
35
+ * The manufacturer an embed key is scoped to, if any.
36
+ * Prefers the `manufacturer_context` Ecosystem returns for manufacturer keys (DEVDB-439),
37
+ * and falls back to the key's own `manufacturer_slug`.
38
+ */
39
+ export const getEmbedKeyManufacturer = (resolution) => {
40
+ if (resolution == null)
41
+ return null;
42
+ const contextSlug = resolution.manufacturer_context?.manufacturer_slug;
43
+ if (contextSlug != null && contextSlug.length > 0)
44
+ return contextSlug;
45
+ const { owner_kind: ownerKind, manufacturer_slug: slug } = resolution.embed_key;
46
+ if (ownerKind === 'manufacturer' && slug != null && slug.length > 0) {
47
+ return slug;
48
+ }
49
+ return null;
50
+ };
51
+ //# sourceMappingURL=use-embed-key-resolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-embed-key-resolution.js","sourceRoot":"","sources":["../../../src/lib/seam/ecosystem/use-embed-key-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,OAAO,EAEL,cAAc,GACf,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAE7E,OAAO,EACL,mBAAmB,GAEpB,MAAM,kDAAkD,CAAA;AASzD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,UAAU,GAAG,EAAE,MACgB,EAAE;IAIjC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,mBAAmB,EAAE,CAAA;IAEpD,MAAM,MAAM,GAAG,EAAE,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,CAAA;IAEhD,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAGhC;QACA,OAAO,EAAE,QAAQ,IAAI,IAAI;QACzB,QAAQ,EAAE;YACR,WAAW;YACX,QAAQ;YACR,QAAQ,IAAI,IAAI;YAChB,OAAO;YACP,SAAS;YACT,MAAM;SACP;QACD,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAC5B,MAAM,cAAc,CAAkB,eAAe,EAAE;YACrD,QAAQ;YACR,QAAQ;YACR,MAAM;YACN,MAAM;SACP,CAAC;QACJ,KAAK,EAAE,mBAAmB;KAC3B,CAAC,CAAA;IAEF,OAAO,EAAE,GAAG,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,CAAA;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,UAA8C,EAC/B,EAAE;IACjB,IAAI,UAAU,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IACnC,MAAM,WAAW,GAAG,UAAU,CAAC,oBAAoB,EAAE,iBAAiB,CAAA;IACtE,IAAI,WAAW,IAAI,IAAI,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,WAAW,CAAA;IACrE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE,IAAI,EAAE,GACtD,UAAU,CAAC,SAAS,CAAA;IACtB,IAAI,SAAS,KAAK,cAAc,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpE,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA"}
@@ -10,5 +10,6 @@ export * from './connected-accounts/use-connected-account.js';
10
10
  export * from './devices/use-device.js';
11
11
  export * from './devices/use-device-providers.js';
12
12
  export * from './devices/use-devices.js';
13
+ export * from './ecosystem/index.js';
13
14
  export * from './SeamProvider.js';
14
15
  export * from './use-seam-query-result.js';
package/lib/seam/index.js CHANGED
@@ -10,6 +10,7 @@ export * from './connected-accounts/use-connected-account.js';
10
10
  export * from './devices/use-device.js';
11
11
  export * from './devices/use-device-providers.js';
12
12
  export * from './devices/use-devices.js';
13
+ export * from './ecosystem/index.js';
13
14
  export * from './SeamProvider.js';
14
15
  export * from './use-seam-query-result.js';
15
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/seam/index.ts"],"names":[],"mappings":"AAAA,cAAc,mCAAmC,CAAA;AACjD,cAAc,oCAAoC,CAAA;AAClD,cAAc,0CAA0C,CAAA;AACxD,cAAc,0CAA0C,CAAA;AACxD,cAAc,iDAAiD,CAAA;AAC/D,cAAc,0CAA0C,CAAA;AACxD,cAAc,yCAAyC,CAAA;AACvD,cAAc,kDAAkD,CAAA;AAChE,cAAc,+CAA+C,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/seam/index.ts"],"names":[],"mappings":"AAAA,cAAc,mCAAmC,CAAA;AACjD,cAAc,oCAAoC,CAAA;AAClD,cAAc,0CAA0C,CAAA;AACxD,cAAc,0CAA0C,CAAA;AACxD,cAAc,iDAAiD,CAAA;AAC/D,cAAc,0CAA0C,CAAA;AACxD,cAAc,yCAAyC,CAAA;AACvD,cAAc,kDAAkD,CAAA;AAChE,cAAc,+CAA+C,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AACxC,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,4BAA4B,CAAA"}
@@ -0,0 +1 @@
1
+ export declare const seamComponentsClassName = "seam-components";
@@ -0,0 +1,2 @@
1
+ export const seamComponentsClassName = 'seam-components';
2
+ //# sourceMappingURL=seam-components-class-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seam-components-class-name.js","sourceRoot":"","sources":["../../src/lib/seam/seam-components-class-name.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,iBAAiB,CAAA"}
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ interface SearchFieldProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export declare function SearchField({ value, onChange, label, disabled, }: SearchFieldProps): JSX.Element;
9
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SearchIcon } from '../../../lib/icons/Search.js';
3
+ export function SearchField({ value, onChange, label, disabled = false, }) {
4
+ return (_jsxs("label", { className: 'seam-ecosystem-search', children: [_jsx(SearchIcon, { "aria-hidden": true, className: 'seam-ecosystem-search-icon' }), _jsx("input", { type: 'search', className: 'seam-ecosystem-search-input', placeholder: label, "aria-label": label, value: value, disabled: disabled, onChange: (event) => {
5
+ onChange(event.target.value);
6
+ } })] }));
7
+ }
8
+ //# sourceMappingURL=SearchField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchField.js","sourceRoot":"","sources":["../../../src/lib/ui/ecosystem/SearchField.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAShD,MAAM,UAAU,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,QAAQ,GAAG,KAAK,GACC;IACjB,OAAO,CACL,iBAAO,SAAS,EAAC,uBAAuB,aACtC,KAAC,UAAU,yBAAa,SAAS,EAAC,4BAA4B,GAAG,EACjE,gBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,6BAA6B,EACvC,WAAW,EAAE,KAAK,gBACN,KAAK,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;oBAClB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBAC9B,CAAC,GACD,IACI,CACT,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ interface StateMessageProps extends PropsWithChildren {
3
+ actionLabel?: string;
4
+ onAction?: () => void;
5
+ role?: 'status' | 'alert';
6
+ }
7
+ export declare function StateMessage({ children, actionLabel, onAction, role, }: StateMessageProps): JSX.Element;
8
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function StateMessage({ children, actionLabel, onAction, role = 'status', }) {
3
+ return (_jsxs("div", { className: 'seam-ecosystem-message', role: role, children: [_jsx("p", { children: children }), actionLabel != null && onAction != null && (_jsx("button", { type: 'button', className: 'seam-ecosystem-button', onClick: onAction, children: actionLabel }))] }));
4
+ }
5
+ //# sourceMappingURL=StateMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StateMessage.js","sourceRoot":"","sources":["../../../src/lib/ui/ecosystem/StateMessage.tsx"],"names":[],"mappings":";AAQA,MAAM,UAAU,YAAY,CAAC,EAC3B,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,IAAI,GAAG,QAAQ,GACG;IAClB,OAAO,CACL,eAAK,SAAS,EAAC,wBAAwB,EAAC,IAAI,EAAE,IAAI,aAChD,sBAAI,QAAQ,GAAK,EAChB,WAAW,IAAI,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI,CAC1C,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,uBAAuB,EACjC,OAAO,EAAE,QAAQ,YAEhB,WAAW,GACL,CACV,IACG,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** A message for an Ecosystem error, and whether retrying could help. */
2
+ export declare const getEcosystemErrorMessage: (error: unknown, fallback: string) => {
3
+ message: string;
4
+ canRetry: boolean;
5
+ };
@@ -0,0 +1,25 @@
1
+ import { EcosystemApiError } from '../../../lib/seam/ecosystem/client.js';
2
+ /** A message for an Ecosystem error, and whether retrying could help. */
3
+ export const getEcosystemErrorMessage = (error, fallback) => {
4
+ if (error instanceof EcosystemApiError) {
5
+ if (error.type === 'embed_key_required') {
6
+ return { message: t.embedKeyRequired, canRetry: false };
7
+ }
8
+ if (error.type === 'origin_not_allowed') {
9
+ return { message: t.originNotAllowed, canRetry: false };
10
+ }
11
+ if (error.status === 401) {
12
+ return { message: t.invalidEmbedKey, canRetry: false };
13
+ }
14
+ if (error.status === 403) {
15
+ return { message: t.originNotAllowed, canRetry: false };
16
+ }
17
+ }
18
+ return { message: fallback, canRetry: true };
19
+ };
20
+ const t = {
21
+ embedKeyRequired: 'An embed key is required to show this list on this website.',
22
+ invalidEmbedKey: 'The embed key is invalid or has been revoked.',
23
+ originNotAllowed: 'The embed key is not allowed on this website.',
24
+ };
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/lib/ui/ecosystem/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAEhE,yEAAyE;AACzE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,KAAc,EACd,QAAgB,EACwB,EAAE;IAC1C,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YACxC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QACzD,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YACxC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QACzD,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QACxD,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QACzD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AAC9C,CAAC,CAAA;AAED,MAAM,CAAC,GAAG;IACR,gBAAgB,EACd,6DAA6D;IAC/D,eAAe,EAAE,+CAA+C;IAChE,gBAAgB,EAAE,+CAA+C;CAClE,CAAA"}
@@ -0,0 +1,15 @@
1
+ import type { Decorator } from '@storybook/react';
2
+ export declare const storybookEmbedKeys: {
3
+ readonly seam: "eco_live_storybook";
4
+ readonly partner: "eco_live_storybook_partner";
5
+ readonly manufacturer: "eco_live_storybook_schlage";
6
+ readonly wrongOrigin: "eco_live_storybook_wrong_origin";
7
+ readonly invalid: "eco_live_invalid";
8
+ };
9
+ export declare const storybookEcosystemEndpoint = "/api/ecosystem/v1/";
10
+ export interface EcosystemStoryArgs {
11
+ embedKey?: string | undefined;
12
+ }
13
+ /** Wraps a story in an EcosystemProvider pointed at the fake Ecosystem API. */
14
+ export declare const withFakeEcosystem: Decorator<EcosystemStoryArgs>;
15
+ export declare const withDarkBackground: Decorator;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { EcosystemProvider } from '../../../lib/seam/ecosystem/EcosystemProvider.js';
3
+ // Embed keys accepted by the fake Ecosystem API served at /api/ecosystem
4
+ // (.storybook/fake-ecosystem.js). The Storybook never uses a real key or production data.
5
+ export const storybookEmbedKeys = {
6
+ seam: 'eco_live_storybook',
7
+ partner: 'eco_live_storybook_partner',
8
+ manufacturer: 'eco_live_storybook_schlage',
9
+ wrongOrigin: 'eco_live_storybook_wrong_origin',
10
+ invalid: 'eco_live_invalid',
11
+ };
12
+ export const storybookEcosystemEndpoint = '/api/ecosystem/v1/';
13
+ /** Wraps a story in an EcosystemProvider pointed at the fake Ecosystem API. */
14
+ export const withFakeEcosystem = (Story, context) => (_jsx(EcosystemProvider, { embedKey: context.args.embedKey, ecosystemEndpoint: storybookEcosystemEndpoint, disableCssInjection: true, disableFontInjection: true, children: _jsx(Story, {}) }));
15
+ export const withDarkBackground = (Story) => (_jsx("div", { style: { background: '#0b0d10', padding: 24, borderRadius: 12 }, children: _jsx(Story, {}) }));
16
+ //# sourceMappingURL=storybook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook.js","sourceRoot":"","sources":["../../../src/lib/ui/ecosystem/storybook.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAA;AAE3E,yEAAyE;AACzE,0FAA0F;AAC1F,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,4BAA4B;IACrC,YAAY,EAAE,4BAA4B;IAC1C,WAAW,EAAE,iCAAiC;IAC9C,OAAO,EAAE,kBAAkB;CACnB,CAAA;AAEV,MAAM,CAAC,MAAM,0BAA0B,GAAG,oBAAoB,CAAA;AAM9D,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAkC,CAC9D,KAAK,EACL,OAAO,EACP,EAAE,CAAC,CACH,KAAC,iBAAiB,IAChB,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAC/B,iBAAiB,EAAE,0BAA0B,EAC7C,mBAAmB,QACnB,oBAAoB,kBAEpB,KAAC,KAAK,KAAG,GACS,CACrB,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAc,CAAC,KAAK,EAAE,EAAE,CAAC,CACtD,cAAK,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,YAClE,KAAC,KAAK,KAAG,GACL,CACP,CAAA"}
@@ -0,0 +1,6 @@
1
+ export type EcosystemTheme = 'light' | 'dark' | 'auto';
2
+ /**
3
+ * The class name for an Ecosystem component theme.
4
+ * `auto` follows the visitor's `prefers-color-scheme`.
5
+ */
6
+ export declare const getEcosystemThemeClassName: (theme: string | undefined) => string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The class name for an Ecosystem component theme.
3
+ * `auto` follows the visitor's `prefers-color-scheme`.
4
+ */
5
+ export const getEcosystemThemeClassName = (theme) => {
6
+ if (theme === 'dark' || theme === 'auto') {
7
+ return `seam-ecosystem-theme-${theme}`;
8
+ }
9
+ return 'seam-ecosystem-theme-light';
10
+ };
11
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../../src/lib/ui/ecosystem/theme.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,KAAyB,EACjB,EAAE;IACV,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACzC,OAAO,wBAAwB,KAAK,EAAE,CAAA;IACxC,CAAC;IACD,OAAO,4BAA4B,CAAA;AACrC,CAAC,CAAA"}
package/lib/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const seamapiReactVersion = "5.0.0";
1
+ declare const seamapiReactVersion = "5.1.1";
2
2
  export default seamapiReactVersion;
package/lib/version.js CHANGED
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '5.0.0';
1
+ const seamapiReactVersion = '5.1.1';
2
2
  export default seamapiReactVersion;
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/react",
3
- "version": "5.0.0",
3
+ "version": "5.1.1",
4
4
  "description": "Seam Components.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -18,6 +18,10 @@
18
18
  "import": "./dist/elements.js",
19
19
  "types": "./src/elements.d.ts"
20
20
  },
21
+ "./ecosystem.js": {
22
+ "import": "./dist/ecosystem.js",
23
+ "types": "./src/ecosystem.d.ts"
24
+ },
21
25
  "./index.css": {
22
26
  "import": "./dist/index.css"
23
27
  }
@@ -26,7 +30,8 @@
26
30
  "sideEffects": [
27
31
  "**/*.css",
28
32
  "./elements.js",
29
- "./dist/elements.js"
33
+ "./dist/elements.js",
34
+ "./dist/ecosystem.js"
30
35
  ],
31
36
  "keywords": [
32
37
  "node"
@@ -64,7 +69,10 @@
64
69
  "postbuild": "concurrently --raw --group 'node ./index.js' 'node ./hooks.js'",
65
70
  "build:entrypoints": "npm run build:js",
66
71
  "prebuild:entrypoints": "npm run build:css",
67
- "postbuild:entrypoints": "vite build",
72
+ "postbuild:entrypoints": "npm run build:elements && npm run build:ecosystem",
73
+ "build:elements": "vite build",
74
+ "build:ecosystem": "vite build --mode ecosystem",
75
+ "size": "tsx ./size.ts",
68
76
  "build:js": "tsc --project tsconfig.build.json",
69
77
  "postbuild:js": "tsc-alias --project tsconfig.build.json",
70
78
  "build:css": "sass --load-path=node_modules src/index.scss:dist/index.css",
@@ -0,0 +1,2 @@
1
+ export const elementNames: string[]
2
+ export default elementNames
@@ -0,0 +1,12 @@
1
+ import { defineEcosystemElements } from 'lib/ecosystem-element.js'
2
+ import * as components from 'lib/seam/components/ecosystem-elements.js'
3
+
4
+ /**
5
+ * The lightweight Seam Ecosystem web components bundle:
6
+ * only <seam-compatible-apps> and <seam-supported-devices>,
7
+ * without the Seam API client used by the other Seam Components.
8
+ * These elements are also defined by elements.js.
9
+ */
10
+ export const elementNames: string[] = defineEcosystemElements(components)
11
+
12
+ export default elementNames
package/src/elements.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import { defineEcosystemElements } from 'lib/ecosystem-element.js'
1
2
  import { defineCustomElement, type ElementDefinition } from 'lib/element.js'
3
+ import * as ecosystemComponents from 'lib/seam/components/ecosystem-elements.js'
2
4
  import * as components from 'lib/seam/components/elements.js'
3
5
 
4
6
  const elementDefinitions = components as unknown as Record<
@@ -30,3 +32,5 @@ for (const key of Object.keys(elementDefinitions)) {
30
32
  elementNames.push(name)
31
33
  defineCustomElement({ name, Component, props })
32
34
  }
35
+
36
+ elementNames.push(...defineEcosystemElements(ecosystemComponents))
@@ -0,0 +1,154 @@
1
+ import r2wc from '@rxfork/r2wc-react-to-web-component'
2
+ import type { ComponentType } from 'react'
3
+
4
+ import {
5
+ EcosystemProvider,
6
+ type EcosystemProviderProps,
7
+ } from 'lib/seam/ecosystem/EcosystemProvider.js'
8
+
9
+ declare global {
10
+ // eslint-disable-next-line no-var
11
+ var disableSeamCssInjection: boolean | undefined
12
+ // eslint-disable-next-line no-var
13
+ var disableSeamFontInjection: boolean | undefined
14
+ // eslint-disable-next-line no-var
15
+ var unminifiySeamCss: boolean | undefined
16
+ }
17
+
18
+ type PropType = 'string' | 'number' | 'boolean' | 'array' | 'object'
19
+
20
+ export type EcosystemElementProps<T> = Record<
21
+ Exclude<keyof T, 'className'>,
22
+ PropType
23
+ >
24
+
25
+ export interface EcosystemElementDefinition {
26
+ name: string
27
+ Component: ComponentType<any>
28
+ props?: Record<string, PropType>
29
+ }
30
+
31
+ type ProviderProps = Omit<EcosystemProviderProps, 'children'>
32
+
33
+ const providerProps: Record<keyof ProviderProps, PropType> = {
34
+ embedKey: 'string',
35
+ ecosystemEndpoint: 'string',
36
+ queryClient: 'object',
37
+ disableCssInjection: 'boolean',
38
+ disableFontInjection: 'boolean',
39
+ unminifiyCss: 'boolean',
40
+ }
41
+
42
+ const commonProps: Record<string, PropType> = {
43
+ className: 'string',
44
+ }
45
+
46
+ /**
47
+ * Defines the custom elements for the Seam Ecosystem components.
48
+ * Each element is wrapped in an `<EcosystemProvider />`, not a `<SeamProvider />`,
49
+ * so it needs no publishable key and does not load the Seam API client.
50
+ * Elements that are already defined are skipped,
51
+ * so `elements.js` and `ecosystem.js` may both be loaded on one page.
52
+ */
53
+ export const defineEcosystemElements = (
54
+ definitions: Record<string, Partial<EcosystemElementDefinition>>
55
+ ): string[] => {
56
+ const names: string[] = []
57
+ for (const [key, definition] of Object.entries(definitions)) {
58
+ const { name, Component, props } = definition
59
+ if (name == null) throw new Error(`Missing element name for ${key}`)
60
+ if (Component == null) {
61
+ throw new Error(`Missing element Component for ${key}`)
62
+ }
63
+ names.push(name)
64
+ defineEcosystemElement({ name, Component, props })
65
+ }
66
+ return names
67
+ }
68
+
69
+ export const defineEcosystemElement = ({
70
+ name,
71
+ Component,
72
+ props = {},
73
+ }: EcosystemElementDefinition): void => {
74
+ const registry = globalThis.customElements
75
+ if (registry == null || registry.get(name) != null) return
76
+
77
+ const allProps = { ...props, ...providerProps, ...commonProps }
78
+
79
+ // UPSTREAM: r2wc parses boolean attributes as false when present without a value,
80
+ // e.g., <seam-compatible-apps hide-search>, so they are parsed here instead.
81
+ const booleanProps = Object.entries(allProps)
82
+ .filter(([, type]) => type === 'boolean')
83
+ .map(([prop]) => prop)
84
+
85
+ const element = r2wc(withEcosystemProvider(Component, booleanProps), {
86
+ props: Object.fromEntries(
87
+ Object.entries(allProps).map(([prop, type]) => [
88
+ prop,
89
+ type === 'boolean' ? 'string' : type,
90
+ ])
91
+ ),
92
+ })
93
+
94
+ registry.define(name, element)
95
+ }
96
+
97
+ /**
98
+ * Parses a boolean attribute or property:
99
+ * present without a value or `true` is true; absent or `false` is false.
100
+ */
101
+ export const parseBooleanProp = (value: unknown): boolean | undefined => {
102
+ if (value == null) return undefined
103
+ if (typeof value === 'boolean') return value
104
+ if (typeof value !== 'string') return Boolean(value)
105
+ return !/^(false|0|no|off)$/i.test(value.trim())
106
+ }
107
+
108
+ function withEcosystemProvider(
109
+ Component: ComponentType<any>,
110
+ booleanProps: string[]
111
+ ): (props: Record<string, unknown>) => JSX.Element {
112
+ const name = Component.displayName ?? Component.name ?? 'Component'
113
+
114
+ function WithEcosystemProvider({
115
+ container: _container,
116
+ ...rawProps
117
+ }: Record<string, unknown>): JSX.Element {
118
+ const props: Record<string, unknown> = { ...rawProps }
119
+ for (const prop of booleanProps) {
120
+ props[prop] = parseBooleanProp(props[prop])
121
+ }
122
+
123
+ const {
124
+ embedKey,
125
+ ecosystemEndpoint,
126
+ queryClient,
127
+ disableCssInjection,
128
+ disableFontInjection,
129
+ unminifiyCss,
130
+ ...componentProps
131
+ } = props as ProviderProps & Record<string, unknown>
132
+
133
+ return (
134
+ <EcosystemProvider
135
+ embedKey={embedKey}
136
+ ecosystemEndpoint={ecosystemEndpoint}
137
+ queryClient={queryClient}
138
+ disableCssInjection={
139
+ disableCssInjection ?? globalThis.disableSeamCssInjection
140
+ }
141
+ disableFontInjection={
142
+ disableFontInjection ?? globalThis.disableSeamFontInjection
143
+ }
144
+ unminifiyCss={unminifiyCss ?? globalThis.unminifiySeamCss}
145
+ >
146
+ <Component {...componentProps} />
147
+ </EcosystemProvider>
148
+ )
149
+ }
150
+
151
+ WithEcosystemProvider.displayName = `WithEcosystemProvider(${name})`
152
+
153
+ return WithEcosystemProvider
154
+ }
@@ -64,6 +64,8 @@ const providerProps: R2wcProps<ProviderProps> = {
64
64
  disableFontInjection: 'boolean',
65
65
  unminifiyCss: 'boolean',
66
66
  onSessionUpdate: 'object',
67
+ embedKey: 'string',
68
+ ecosystemEndpoint: 'string',
67
69
  }
68
70
 
69
71
  export const defineCustomElement = ({
@@ -96,6 +98,8 @@ function withProvider<P extends JSX.IntrinsicAttributes>(
96
98
  disableFontInjection,
97
99
  unminifiyCss,
98
100
  onSessionUpdate,
101
+ embedKey,
102
+ ecosystemEndpoint,
99
103
  container: _container,
100
104
  ...props
101
105
  }: ProviderProps & { container: Container } & P): JSX.Element | null {
@@ -114,6 +118,8 @@ function withProvider<P extends JSX.IntrinsicAttributes>(
114
118
  }
115
119
  unminifiyCss={unminifiyCss ?? globalThis.unminifiySeamCss}
116
120
  onSessionUpdate={onSessionUpdate}
121
+ embedKey={embedKey}
122
+ ecosystemEndpoint={ecosystemEndpoint}
117
123
  >
118
124
  <Component {...(props as P)} />
119
125
  </SeamProvider>
@@ -8,6 +8,8 @@ import {
8
8
  import type { QueryClient } from '@tanstack/react-query'
9
9
  import { createContext, type PropsWithChildren, useMemo } from 'react'
10
10
 
11
+ import { EcosystemContextProvider } from 'lib/seam/ecosystem/EcosystemProvider.js'
12
+ import { seamComponentsClassName } from 'lib/seam/seam-components-class-name.js'
11
13
  import { useSeamFont } from 'lib/seam/use-seam-font.js'
12
14
  import { useSeamStyles } from 'lib/seam/use-seam-styles.js'
13
15
  import {
@@ -58,9 +60,16 @@ interface SeamProviderBaseProps extends PropsWithChildren {
58
60
  queryClient?: QueryClient | undefined
59
61
  telemetryClient?: TelemetryClient | undefined
60
62
  onSessionUpdate?: (client: SeamHttp) => void
63
+ /**
64
+ * Seam Ecosystem embed key (`eco_live_…`) used by the Ecosystem components,
65
+ * e.g., `CompatibleApps` and `SupportedDevices`.
66
+ */
67
+ embedKey?: string | undefined
68
+ /** Seam Ecosystem API endpoint. Defaults to `https://ecosystem.seam.co/v1/`. */
69
+ ecosystemEndpoint?: string | undefined
61
70
  }
62
71
 
63
- export const seamComponentsClassName = 'seam-components'
72
+ export { seamComponentsClassName }
64
73
 
65
74
  export function SeamProvider({
66
75
  children,
@@ -70,6 +79,8 @@ export function SeamProvider({
70
79
  unminifiyCss = false,
71
80
  telemetryClient,
72
81
  queryClient,
82
+ embedKey,
83
+ ecosystemEndpoint,
73
84
  ...props
74
85
  }: SeamProviderProps): JSX.Element {
75
86
  useSeamStyles({ disabled: disableCssInjection, unminified: unminifiyCss })
@@ -95,7 +106,12 @@ export function SeamProvider({
95
106
  {...props}
96
107
  >
97
108
  <Provider value={value}>
98
- <Telemetry>{children}</Telemetry>
109
+ <EcosystemContextProvider
110
+ embedKey={embedKey}
111
+ ecosystemEndpoint={ecosystemEndpoint}
112
+ >
113
+ <Telemetry>{children}</Telemetry>
114
+ </EcosystemContextProvider>
99
115
  </Provider>
100
116
  </SeamQueryProvider>
101
117
  </TelemetryProvider>
@@ -0,0 +1,19 @@
1
+ import type { EcosystemElementProps } from 'lib/ecosystem-element.js'
2
+
3
+ import type { CompatibleAppsProps } from './CompatibleApps.js'
4
+
5
+ export const name = 'seam-compatible-apps'
6
+
7
+ export const props: EcosystemElementProps<CompatibleAppsProps> = {
8
+ deviceType: 'string',
9
+ manufacturer: 'string',
10
+ provider: 'string',
11
+ capability: 'string',
12
+ industry: 'string',
13
+ maxVisible: 'number',
14
+ hideSearch: 'boolean',
15
+ hideTitle: 'boolean',
16
+ theme: 'string',
17
+ }
18
+
19
+ export { CompatibleApps as Component } from './CompatibleApps.js'