@neurodyn/react-model-viewer 0.0.2 → 0.0.4

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.
package/dist/index.d.ts CHANGED
@@ -3,15 +3,33 @@ import { JSX } from 'react/jsx-runtime';
3
3
  import type { default as React_2 } from 'react';
4
4
  import * as React_3 from 'react';
5
5
 
6
+ /** What the host needs to open its own AR experience for the shown model. */
7
+ export declare interface ArLaunchTarget {
8
+ /** Material collection currently shown, which may differ from the entry url. */
9
+ hid?: string;
10
+ tinuuid: string;
11
+ }
12
+
6
13
  declare type DialogProps = React_3.ComponentProps<typeof DialogPrimitive.Root>
7
14
 
8
- export declare function ModelViewer({ tinuuid, apiKey, hid, layout, background, className, ...props }: ModelViewerProps & React_2.ComponentProps<'div'>): JSX.Element;
15
+ /**
16
+ * Lets the host open its own AR experience instead of the native one.
17
+ *
18
+ * Returning `true` means the host took the launch over: the viewer then skips
19
+ * both the native handoff and its own `ar_launched`, so one tap stays one
20
+ * event. Anything else falls through to the usual behaviour.
21
+ */
22
+ export declare type LaunchArOverride = (target: ArLaunchTarget) => boolean | Promise<boolean>;
23
+
24
+ export declare function ModelViewer({ tinuuid, apiKey, appClipBundleId, arExperience, hid, apiBaseUrl, legacyApiBaseUrl, mode, initialWidgetSessionToken, handoffUrl, launchAr, sizeType, layout, background, viewerSurface, className, ...props }: ModelViewerProps & React_2.ComponentProps<'div'>): JSX.Element;
25
+
26
+ export declare type ModelViewerArExperience = 'advanced' | 'standard';
9
27
 
10
28
  declare type ModelViewerBackground = 'default' | 'transparent';
11
29
 
12
- export declare function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }: ModelViewerDialogProps): JSX.Element;
30
+ export declare function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, legacyApiBaseUrl, initialWidgetSessionToken, handoffUrl, hid, sizeType, ...props }: ModelViewerDialogProps): JSX.Element;
13
31
 
14
- declare interface ModelViewerDialogProps extends Pick<ModelViewerRootProps, 'tinuuid' | 'apiKey' | 'hid'>, Omit<DialogProps, 'children'> {
32
+ declare interface ModelViewerDialogProps extends Pick<ModelViewerRootProps, 'apiBaseUrl' | 'apiKey' | 'handoffUrl' | 'hid' | 'initialWidgetSessionToken' | 'legacyApiBaseUrl' | 'sizeType' | 'tinuuid'>, Omit<DialogProps, 'children'> {
15
33
  }
16
34
 
17
35
  declare type ModelViewerLayout = 'default' | 'model';
@@ -22,16 +40,42 @@ export declare interface ModelViewerProps extends Omit<ModelViewerRootProps, 'sh
22
40
  declare interface ModelViewerRootProps {
23
41
  /** The unique identifier of a Vizbl object. */
24
42
  tinuuid: string;
25
- /** Public API key. */
26
- apiKey: string;
43
+ /** Public API key. Without it, the viewer uses the legacy `/obj/Fetch` API. */
44
+ apiKey?: string | null;
45
+ /** App Clip bundle used by automatic Advanced AR handoffs. */
46
+ appClipBundleId?: string;
47
+ /** Standard or Advanced launch behavior for the shared QR backend mode. */
48
+ arExperience?: ModelViewerArExperience;
27
49
  /** The identifier of a material variant. */
28
50
  hid?: string | null;
51
+ /** Node Public API base URL. */
52
+ apiBaseUrl?: string;
53
+ /** Legacy Go API base URL used only when `apiKey` is missing. */
54
+ legacyApiBaseUrl?: string;
55
+ /** Widget integration mode recorded by the backend session. */
56
+ mode?: ModelViewerWidgetMode;
57
+ /** Existing token used to preserve the same widget session across a handoff. */
58
+ initialWidgetSessionToken?: string;
59
+ /** Hosted viewer URL encoded into the desktop-to-mobile QR handoff. */
60
+ handoffUrl?: string;
61
+ /** Lets the host open its own AR experience instead of the native handoff. */
62
+ launchAr?: LaunchArOverride;
63
+ /** Display unit for model dimensions. */
64
+ sizeType?: SizeType;
29
65
  /** Controls which viewer UI elements are shown. */
30
66
  layout?: ModelViewerLayout;
31
67
  /** Controls the viewer container background. */
32
68
  background?: ModelViewerBackground;
33
69
  /** Enables dialog-only close controls. */
34
70
  showDialogCloseButton?: boolean;
71
+ /** Controls open-event semantics for inline, dialog, and hosted-page viewers. */
72
+ viewerSurface?: ModelViewerSurface;
35
73
  }
36
74
 
75
+ declare type ModelViewerSurface = 'dialog' | 'inline' | 'page';
76
+
77
+ export declare type ModelViewerWidgetMode = 'advanced-ar-view' | 'ar-view' | 'embedded-3d-viewer' | 'ar-viewer' | 'qr-code';
78
+
79
+ declare type SizeType = 'cm' | 'inch';
80
+
37
81
  export { }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { createContext, memo, use as external_react_use, useCallback, useEffect, useMemo as external_react_useMemo, useState } from "react";
2
+ import { createContext, memo, use as external_react_use, useCallback, useEffect, useMemo as external_react_useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import react_shadow from "react-shadow";
5
5
  import { QueryClient, QueryClientProvider, useQuery as react_query_useQuery } from "@tanstack/react-query";
@@ -919,6 +919,10 @@ button, input:where([type="button"], [type="reset"], [type="submit"]) {
919
919
  height: auto;
920
920
  }
921
921
 
922
+ .h-dvh {
923
+ height: 100dvh;
924
+ }
925
+
922
926
  .h-full {
923
927
  height: 100%;
924
928
  }
@@ -927,10 +931,6 @@ button, input:where([type="button"], [type="reset"], [type="submit"]) {
927
931
  height: 1px;
928
932
  }
929
933
 
930
- .h-svh {
931
- height: 100svh;
932
- }
933
-
934
934
  .max-h-\\(--radix-select-content-available-height\\) {
935
935
  max-height: var(--radix-select-content-available-height);
936
936
  }
@@ -5724,18 +5724,28 @@ function ThemeProvider({ children, defaultTheme = 'system', storageKey = 'v-them
5724
5724
  }
5725
5725
  var fontsinline = __webpack_require__("../ui/src/styles/fonts.css?inline");
5726
5726
  const PROPERTY_BLOCK_RE = /@property\s+(--[^{\s]+)\s*\{([^}]*)\}/g;
5727
- const INITIAL_VALUE_RE = /initial-value:([^;\n]+);/i;
5727
+ const INITIAL_VALUE_RE = /initial-value:([^;}]+)/i;
5728
+ const NON_INHERITING_RE = /inherits:\s*false(?:\s*;|\s*$)/i;
5728
5729
  const ROOT_TO_HOST_SELECTOR_RE = /:root\s*,\s*:host|:root/g;
5729
5730
  function createShadowRootCssText(cssText) {
5730
5731
  const cssForHost = cssText.replace(ROOT_TO_HOST_SELECTOR_RE, ':host');
5731
5732
  const propertyBlocks = Array.from(cssText.matchAll(PROPERTY_BLOCK_RE));
5732
5733
  const propertyDefaults = propertyBlocks.map(([, name, body])=>{
5733
- const match = body.match(INITIAL_VALUE_RE);
5734
- if (!match) return null;
5735
- const initialValue = match[1].trim();
5736
- return ` ${name}: ${initialValue};`;
5734
+ if (!NON_INHERITING_RE.test(body)) return null;
5735
+ const initialValue = body.match(INITIAL_VALUE_RE)?.[1].trim() ?? 'initial';
5736
+ return ` ${name}: ${initialValue};`;
5737
5737
  }).filter((value)=>null !== value).join('\n');
5738
- return propertyDefaults ? `:host {\n${propertyDefaults}\n}\n\n${cssForHost}` : cssForHost;
5738
+ if (!propertyDefaults) return cssForHost;
5739
+ return `${cssForHost}\n\n@layer base {
5740
+ :host,
5741
+ *,
5742
+ ::before,
5743
+ ::after,
5744
+ ::backdrop,
5745
+ ::file-selector-button {
5746
+ ${propertyDefaults}
5747
+ }
5748
+ }`;
5739
5749
  }
5740
5750
  var styleinline = __webpack_require__("./src/style.css?inline");
5741
5751
  const shadowRootCssText = createShadowRootCssText(styleinline.A);
@@ -6475,7 +6485,7 @@ const runtime = createApiClientRuntime({
6475
6485
  configureFunctionName: 'configureNodeApiClient',
6476
6486
  defaultBaseURL: "https://api.vizbl.com"
6477
6487
  });
6478
- const configureNodeApiClientRuntime = runtime.configureClientRuntime;
6488
+ runtime.configureClientRuntime;
6479
6489
  runtime.configureAccessTokenResolverRuntime;
6480
6490
  runtime.configureHeadersResolverRuntime;
6481
6491
  runtime.configureUnauthorizedHandlerRuntime;
@@ -6485,12 +6495,30 @@ runtime.resetClientForTestsRuntime;
6485
6495
  const requestNodeRuntime = runtime.requestRuntime;
6486
6496
  runtime.requestWithoutUnauthorizedRecovery;
6487
6497
  runtime.addResponseInterceptorRuntime;
6488
- function configureNodeApiClient(config) {
6489
- configureNodeApiClientRuntime(config);
6490
- }
6491
6498
  async function node_client_node_client(config) {
6492
6499
  return requestNodeRuntime(config);
6493
6500
  }
6501
+ async function publicApiControllerActivateCoreAR(params, config = {}) {
6502
+ const { client: request = node_client_node_client, ...requestConfig } = config;
6503
+ const res = await request({
6504
+ method: "POST",
6505
+ url: "/public/widget/activate-core",
6506
+ params,
6507
+ ...requestConfig
6508
+ });
6509
+ return res.data;
6510
+ }
6511
+ async function publicApiControllerIssueWidgetToken(data, config = {}) {
6512
+ const { client: request = node_client_node_client, ...requestConfig } = config;
6513
+ const requestData = data;
6514
+ const res = await request({
6515
+ method: "POST",
6516
+ url: "/public/widget/token",
6517
+ data: requestData,
6518
+ ...requestConfig
6519
+ });
6520
+ return res.data;
6521
+ }
6494
6522
  async function publicApiControllerReadObjectByTinuuid({ tinuuid }, params, config = {}) {
6495
6523
  const { client: request = node_client_node_client, ...requestConfig } = config;
6496
6524
  const res = await request({
@@ -6501,33 +6529,227 @@ async function publicApiControllerReadObjectByTinuuid({ tinuuid }, params, confi
6501
6529
  });
6502
6530
  return res.data;
6503
6531
  }
6532
+ const WIDGET_SESSION_TOKEN_HEADER = 'x-widget-session-token';
6533
+ const DEFAULT_REFRESH_THRESHOLD_MS = 30000;
6534
+ const ALLOWED_PRODUCT_TYPES = new Set([
6535
+ 'core-ar',
6536
+ 'core-ar-shopify'
6537
+ ]);
6538
+ class ModelViewerWidgetSession {
6539
+ apiBaseUrl;
6540
+ apiKey;
6541
+ issueWidgetToken;
6542
+ mode;
6543
+ now;
6544
+ refreshThresholdMs;
6545
+ refreshPromise = null;
6546
+ session;
6547
+ constructor(activationData, options){
6548
+ this.apiBaseUrl = options.apiBaseUrl;
6549
+ this.apiKey = options.apiKey;
6550
+ this.issueWidgetToken = options.issueWidgetToken ?? publicApiControllerIssueWidgetToken;
6551
+ this.mode = options.mode;
6552
+ this.now = options.now ?? Date.now;
6553
+ this.refreshThresholdMs = options.refreshThresholdMs ?? DEFAULT_REFRESH_THRESHOLD_MS;
6554
+ this.session = this.toSnapshot(activationData);
6555
+ }
6556
+ get token() {
6557
+ return this.session.token;
6558
+ }
6559
+ get sid() {
6560
+ return this.session.sid;
6561
+ }
6562
+ get snapshot() {
6563
+ return {
6564
+ ...this.session
6565
+ };
6566
+ }
6567
+ async getTokenForRequest() {
6568
+ if (this.session.expiresAt - this.now() > this.refreshThresholdMs) return this.session.token;
6569
+ return (await this.refresh()).token;
6570
+ }
6571
+ async getRequestConfig(signal) {
6572
+ return {
6573
+ baseURL: this.apiBaseUrl,
6574
+ headers: {
6575
+ [WIDGET_SESSION_TOKEN_HEADER]: await this.getTokenForRequest()
6576
+ },
6577
+ signal
6578
+ };
6579
+ }
6580
+ refresh() {
6581
+ if (this.refreshPromise) return this.refreshPromise;
6582
+ this.refreshPromise = this.issueWidgetToken({
6583
+ mode: this.mode,
6584
+ token: this.session.token
6585
+ }, {
6586
+ baseURL: this.apiBaseUrl,
6587
+ headers: {
6588
+ 'x-api-key': this.apiKey
6589
+ }
6590
+ }).then((data)=>this.toSnapshot(parseWidgetSession(data))).then((session)=>{
6591
+ this.session = session;
6592
+ return this.snapshot;
6593
+ }).finally(()=>{
6594
+ this.refreshPromise = null;
6595
+ });
6596
+ return this.refreshPromise;
6597
+ }
6598
+ toSnapshot(data) {
6599
+ return {
6600
+ ...data,
6601
+ expiresAt: this.now() + 1000 * data.expiresIn
6602
+ };
6603
+ }
6604
+ }
6605
+ async function createModelViewerWidgetSession({ apiBaseUrl, apiKey, initialWidgetSessionToken, mode, signal }) {
6606
+ const requestConfig = {
6607
+ baseURL: apiBaseUrl,
6608
+ headers: {
6609
+ 'x-api-key': apiKey
6610
+ },
6611
+ signal
6612
+ };
6613
+ if (initialWidgetSessionToken) try {
6614
+ const refreshed = await publicApiControllerIssueWidgetToken({
6615
+ mode,
6616
+ token: initialWidgetSessionToken
6617
+ }, requestConfig);
6618
+ return new ModelViewerWidgetSession(parseWidgetSession(refreshed), {
6619
+ apiBaseUrl,
6620
+ apiKey,
6621
+ mode
6622
+ });
6623
+ } catch (error) {
6624
+ if (signal?.aborted) throw error;
6625
+ }
6626
+ const activation = await publicApiControllerActivateCoreAR({
6627
+ mode
6628
+ }, requestConfig);
6629
+ if ('string' != typeof activation.productType || !ALLOWED_PRODUCT_TYPES.has(activation.productType)) throw new TypeError('The API key is not enabled for Core AR');
6630
+ return new ModelViewerWidgetSession(parseWidgetSession(activation.widgetSession), {
6631
+ apiBaseUrl,
6632
+ apiKey,
6633
+ mode
6634
+ });
6635
+ }
6636
+ async function fetchModelViewerObject(tinuuid, variantId, session, signal) {
6637
+ return publicApiControllerReadObjectByTinuuid({
6638
+ tinuuid
6639
+ }, {
6640
+ productType: 'core-ar',
6641
+ variantId: variantId ?? void 0
6642
+ }, await session.getRequestConfig(signal));
6643
+ }
6644
+ async function sendModelViewerWidgetEvent(session, event, options = {}) {
6645
+ if (!session) return;
6646
+ try {
6647
+ const config = await session.getRequestConfig();
6648
+ const url = new URL('/public/widget/events', config.baseURL ?? window.location.origin);
6649
+ const response = await fetch(url, {
6650
+ body: JSON.stringify({
6651
+ event,
6652
+ objectId: options.objectId ?? void 0,
6653
+ timestamp: new Date().toISOString(),
6654
+ variantId: options.variantId ?? void 0
6655
+ }),
6656
+ headers: {
6657
+ 'Content-Type': 'application/json',
6658
+ ...config.headers
6659
+ },
6660
+ keepalive: true,
6661
+ method: 'POST'
6662
+ });
6663
+ if (!response.ok) throw new Error(`Widget event request failed with ${response.status}`);
6664
+ } catch {}
6665
+ }
6666
+ function parseWidgetSession(value) {
6667
+ const session = value;
6668
+ if (!session || 'object' != typeof session || 'string' != typeof session.token || !session.token.trim() || 'string' != typeof session.sid || !session.sid.trim() || 'number' != typeof session.expiresIn || !Number.isFinite(session.expiresIn) || session.expiresIn <= 0) throw new TypeError('Invalid widget session response');
6669
+ return session;
6670
+ }
6671
+ const client_runtime_runtime = createApiClientRuntime({
6672
+ clientName: 'API client',
6673
+ configureFunctionName: 'configureApiClient',
6674
+ defaultBaseURL: "https://api.vizbl.com/v1"
6675
+ });
6676
+ client_runtime_runtime.configureClientRuntime;
6677
+ client_runtime_runtime.configureAccessTokenResolverRuntime;
6678
+ client_runtime_runtime.configureHeadersResolverRuntime;
6679
+ client_runtime_runtime.configureUnauthorizedHandlerRuntime;
6680
+ client_runtime_runtime.isClientConfiguredRuntime;
6681
+ client_runtime_runtime.assertClientConfiguredRuntime;
6682
+ client_runtime_runtime.resetClientForTestsRuntime;
6683
+ const client_runtime_requestRuntime = client_runtime_runtime.requestRuntime;
6684
+ client_runtime_runtime.requestWithoutUnauthorizedRecovery;
6685
+ client_runtime_runtime.addResponseInterceptorRuntime;
6686
+ async function client_client(config) {
6687
+ return client_runtime_requestRuntime(config);
6688
+ }
6689
+ async function objFetch(data, config = {}) {
6690
+ const { client: request = client_client, ...requestConfig } = config;
6691
+ const requestData = data;
6692
+ const res = await request({
6693
+ method: "POST",
6694
+ url: "/obj/Fetch",
6695
+ data: requestData,
6696
+ ...requestConfig
6697
+ });
6698
+ return res.data;
6699
+ }
6504
6700
  function useObjectData(tinuuid, initialHid, options) {
6505
6701
  const [materialHid, setMaterialHid] = useState(initialHid ?? null);
6506
6702
  const { data = null, error, isLoading } = react_query_useQuery({
6507
6703
  queryKey: [
6508
6704
  'modelViewer',
6509
6705
  'object',
6510
- tinuuid,
6511
- options.apiKey
6512
- ],
6513
- enabled: Boolean(tinuuid) && options.apiKey.length > 0,
6514
- queryFn: ({ signal })=>publicApiControllerReadObjectByTinuuid({
6706
+ {
6707
+ apiBaseUrl: options.apiBaseUrl ?? null,
6708
+ apiKey: options.apiKey,
6709
+ initialHid: initialHid ?? null,
6710
+ initialWidgetSessionToken: options.initialWidgetSessionToken ?? null,
6711
+ legacyApiBaseUrl: options.legacyApiBaseUrl ?? null,
6712
+ mode: options.mode,
6515
6713
  tinuuid
6516
- }, {
6517
- productType: 'core-ar'
6518
- }, {
6519
- headers: {
6520
- 'x-api-key': options.apiKey
6521
- },
6714
+ }
6715
+ ],
6716
+ enabled: Boolean(tinuuid),
6717
+ gcTime: 0,
6718
+ refetchOnMount: 'always',
6719
+ queryFn: async ({ signal })=>{
6720
+ if (!options.apiKey) {
6721
+ const object = await objFetch({
6722
+ tinuuid
6723
+ }, {
6724
+ baseURL: options.legacyApiBaseUrl,
6725
+ signal
6726
+ });
6727
+ return {
6728
+ object,
6729
+ widgetSession: null
6730
+ };
6731
+ }
6732
+ const widgetSession = await createModelViewerWidgetSession({
6733
+ apiBaseUrl: options.apiBaseUrl,
6734
+ apiKey: options.apiKey,
6735
+ initialWidgetSessionToken: options.initialWidgetSessionToken,
6736
+ mode: options.mode,
6522
6737
  signal
6523
- })
6738
+ });
6739
+ const object = await fetchModelViewerObject(tinuuid, initialHid, widgetSession, signal);
6740
+ return {
6741
+ object,
6742
+ widgetSession
6743
+ };
6744
+ }
6524
6745
  });
6746
+ const object = data?.object ?? null;
6525
6747
  const material = external_react_useMemo(()=>{
6526
- if (!data?.materials?.length) return null;
6527
- return materialHid ? data.materials.find((m)=>m.hid === materialHid) || data.materials[0] : data.materials?.[0] ?? null;
6748
+ if (!object?.materials?.length) return null;
6749
+ return materialHid ? object.materials.find((m)=>m.hid === materialHid) || object.materials[0] : object.materials?.[0] ?? null;
6528
6750
  }, [
6529
- data,
6530
- materialHid
6751
+ materialHid,
6752
+ object
6531
6753
  ]);
6532
6754
  useEffect(()=>{
6533
6755
  setMaterialHid(initialHid ?? null);
@@ -6536,11 +6758,12 @@ function useObjectData(tinuuid, initialHid, options) {
6536
6758
  tinuuid
6537
6759
  ]);
6538
6760
  return {
6539
- object: data,
6761
+ object,
6540
6762
  material,
6541
6763
  error,
6542
6764
  isLoading,
6543
- setMaterialHid
6765
+ setMaterialHid,
6766
+ widgetSession: data?.widgetSession ?? null
6544
6767
  };
6545
6768
  }
6546
6769
  function getErrorResponseStatus(error) {
@@ -6551,36 +6774,119 @@ function getModelViewerApiError(error) {
6551
6774
  if (!isAxiosError(error)) return null;
6552
6775
  const status = error.response?.status;
6553
6776
  if (401 === status) return 'unauthorized';
6777
+ if (403 === status) return 'forbidden';
6554
6778
  if (402 === status) return 'payment-required';
6555
6779
  return null;
6556
6780
  }
6781
+ const RECOVERY_STORAGE_KEY = 'vizbl-model-viewer-blank-canvas-recovery';
6782
+ const RENDER_CHECK_DELAY_MS = 2000;
6783
+ const SAMPLE_SIZE = 64;
6784
+ const NON_ZERO_CHANNEL_THRESHOLD = 8;
6785
+ function scheduleBlankCanvasRecovery(modelViewerEl) {
6786
+ window.setTimeout(()=>{
6787
+ if (isCanvasBlank(modelViewerEl) && !hasAlreadyRecovered()) {
6788
+ markRecovered();
6789
+ window.location.reload();
6790
+ }
6791
+ }, RENDER_CHECK_DELAY_MS);
6792
+ }
6793
+ function hasAlreadyRecovered() {
6794
+ try {
6795
+ return '1' === window.sessionStorage.getItem(RECOVERY_STORAGE_KEY);
6796
+ } catch {
6797
+ return false;
6798
+ }
6799
+ }
6800
+ function markRecovered() {
6801
+ try {
6802
+ window.sessionStorage.setItem(RECOVERY_STORAGE_KEY, '1');
6803
+ } catch {}
6804
+ }
6805
+ function isCanvasBlank(modelViewerEl) {
6806
+ const canvas = modelViewerEl.shadowRoot?.querySelector('canvas.show');
6807
+ if (!canvas || 0 === canvas.width || 0 === canvas.height) return false;
6808
+ const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
6809
+ if (!gl) return false;
6810
+ const size = Math.min(SAMPLE_SIZE, canvas.width, canvas.height);
6811
+ const x = Math.max(0, Math.floor((canvas.width - size) / 2));
6812
+ const y = Math.max(0, Math.floor((canvas.height - size) / 2));
6813
+ const pixels = new Uint8Array(size * size * 4);
6814
+ gl.readPixels(x, y, size, size, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
6815
+ for(let i = 0; i < pixels.length; i += 4)if (pixels[i] > NON_ZERO_CHANNEL_THRESHOLD || pixels[i + 1] > NON_ZERO_CHANNEL_THRESHOLD || pixels[i + 2] > NON_ZERO_CHANNEL_THRESHOLD || pixels[i + 3] > NON_ZERO_CHANNEL_THRESHOLD) return false;
6816
+ return true;
6817
+ }
6557
6818
  const ModelViewerContext = /*#__PURE__*/ createContext(null);
6558
- function ModelViewerProvider({ children, tinuuid, apiKey, hid, layout, background }) {
6559
- const { object, material, error, isLoading, setMaterialHid } = useObjectData(tinuuid, hid, {
6560
- apiKey
6819
+ function ModelViewerProvider({ children, tinuuid, apiKey, appClipBundleId, arExperience, apiBaseUrl, legacyApiBaseUrl, mode, initialWidgetSessionToken, handoffUrl, hid, launchAr, layout, background, sizeType, viewerSurface }) {
6820
+ const { object, material, error, isLoading, setMaterialHid: setObjectMaterialHid, widgetSession } = useObjectData(tinuuid, hid, {
6821
+ apiBaseUrl,
6822
+ apiKey,
6823
+ initialWidgetSessionToken,
6824
+ legacyApiBaseUrl,
6825
+ mode
6561
6826
  });
6562
6827
  const [isModelLoading, setIsModelLoading] = useState(true);
6563
6828
  const [containerEl, setContainerEl] = useState(null);
6564
6829
  const [modelViewerEl, setModelViewerEl] = useState(null);
6565
- const hasNonEmptyApiKey = apiKey.length > 0;
6830
+ const trackedCameraInteractionSidRef = useRef(null);
6831
+ const trackedOpenSidRef = useRef(null);
6566
6832
  const glbUrl = material?.native?.glbUrl ?? null;
6567
6833
  const usdzUrl = material?.native?.usdzUrl ?? null;
6568
6834
  const previewUrl = material?.previews?.[0]?.url ?? null;
6569
6835
  const state = external_react_useMemo(()=>{
6570
- if (!hasNonEmptyApiKey) return 'unauthorized';
6571
6836
  if (isLoading) return 'loading';
6572
6837
  const accessError = getModelViewerApiError(error);
6573
6838
  if (accessError) return accessError;
6839
+ const instantArExperience = 'ar-view' === mode || 'qr-code' === mode && 'standard' === arExperience;
6840
+ const advancedArExperience = 'advanced-ar-view' === mode || 'qr-code' === mode && 'advanced' === arExperience;
6841
+ if (instantArExperience && object?.policy?.instantArView === false) return 'forbidden';
6842
+ if ((advancedArExperience || 'ar-viewer' === mode) && object?.policy?.appClip === false) return 'forbidden';
6574
6843
  if (null != error || !object || !material || !glbUrl) return 'not-found';
6575
6844
  return 'ready';
6576
6845
  }, [
6846
+ arExperience,
6577
6847
  error,
6578
6848
  glbUrl,
6579
- hasNonEmptyApiKey,
6580
6849
  isLoading,
6581
6850
  material,
6851
+ mode,
6582
6852
  object
6583
6853
  ]);
6854
+ const trackEvent = useCallback((event)=>sendModelViewerWidgetEvent(widgetSession, event, {
6855
+ objectId: object?.uuid,
6856
+ variantId: material?.hid
6857
+ }), [
6858
+ material?.hid,
6859
+ object?.uuid,
6860
+ widgetSession
6861
+ ]);
6862
+ const setMaterialHid = useCallback((nextHid)=>{
6863
+ if (nextHid === material?.hid) return;
6864
+ setObjectMaterialHid(nextHid);
6865
+ sendModelViewerWidgetEvent(widgetSession, 'interacted', {
6866
+ objectId: object?.uuid,
6867
+ variantId: nextHid
6868
+ });
6869
+ }, [
6870
+ material?.hid,
6871
+ object?.uuid,
6872
+ setObjectMaterialHid,
6873
+ widgetSession
6874
+ ]);
6875
+ useEffect(()=>{
6876
+ if ('inline' === viewerSurface || 'ready' !== state || !widgetSession || trackedOpenSidRef.current === widgetSession.sid) return;
6877
+ trackEvent('widget_opened');
6878
+ trackedOpenSidRef.current = widgetSession.sid;
6879
+ }, [
6880
+ state,
6881
+ trackEvent,
6882
+ viewerSurface,
6883
+ widgetSession
6884
+ ]);
6885
+ useEffect(()=>{
6886
+ trackedCameraInteractionSidRef.current = null;
6887
+ }, [
6888
+ widgetSession?.sid
6889
+ ]);
6584
6890
  useEffect(()=>{
6585
6891
  setIsModelLoading('ready' === state && Boolean(glbUrl));
6586
6892
  }, [
@@ -6596,53 +6902,82 @@ function ModelViewerProvider({ children, tinuuid, apiKey, hid, layout, backgroun
6596
6902
  };
6597
6903
  const handleLoad = ()=>{
6598
6904
  setIsModelLoading(false);
6905
+ scheduleBlankCanvasRecovery(modelViewerEl);
6599
6906
  };
6600
6907
  const handleError = ()=>{
6601
6908
  setIsModelLoading(false);
6602
6909
  };
6910
+ const handleCameraChange = (event)=>{
6911
+ const source = event.detail?.source;
6912
+ if ('user-interaction' !== source || !widgetSession || trackedCameraInteractionSidRef.current === widgetSession.sid) return;
6913
+ trackEvent('interacted');
6914
+ trackedCameraInteractionSidRef.current = widgetSession.sid;
6915
+ };
6603
6916
  modelViewerEl.addEventListener('progress', handleProgress);
6604
6917
  modelViewerEl.addEventListener('load', handleLoad);
6605
6918
  modelViewerEl.addEventListener('error', handleError);
6919
+ modelViewerEl.addEventListener('camera-change', handleCameraChange);
6606
6920
  return ()=>{
6607
6921
  modelViewerEl.removeEventListener('progress', handleProgress);
6608
6922
  modelViewerEl.removeEventListener('load', handleLoad);
6609
6923
  modelViewerEl.removeEventListener('error', handleError);
6924
+ modelViewerEl.removeEventListener('camera-change', handleCameraChange);
6610
6925
  };
6611
6926
  }, [
6612
- modelViewerEl
6927
+ modelViewerEl,
6928
+ trackEvent,
6929
+ widgetSession
6613
6930
  ]);
6614
6931
  const contextValue = external_react_useMemo(()=>({
6932
+ apiKey,
6933
+ appClipBundleId,
6934
+ arExperience,
6615
6935
  tinuuid,
6616
6936
  object,
6617
6937
  material,
6618
6938
  glbUrl,
6619
6939
  usdzUrl,
6620
6940
  previewUrl,
6941
+ handoffUrl,
6942
+ launchAr,
6621
6943
  layout,
6622
6944
  background,
6945
+ mode,
6946
+ sizeType,
6623
6947
  isLoading,
6624
6948
  isModelLoading,
6625
6949
  state,
6626
6950
  modelViewerEl,
6627
6951
  containerEl,
6952
+ widgetSession,
6628
6953
  setMaterialHid,
6629
6954
  setContainerEl,
6630
- setModelViewerEl
6955
+ setModelViewerEl,
6956
+ trackEvent
6631
6957
  }), [
6958
+ apiKey,
6959
+ appClipBundleId,
6960
+ arExperience,
6632
6961
  tinuuid,
6633
6962
  object,
6634
6963
  material,
6635
6964
  glbUrl,
6636
6965
  usdzUrl,
6637
6966
  previewUrl,
6967
+ handoffUrl,
6968
+ launchAr,
6638
6969
  layout,
6639
6970
  background,
6971
+ mode,
6972
+ sizeType,
6640
6973
  isLoading,
6641
6974
  isModelLoading,
6642
6975
  state,
6643
6976
  modelViewerEl,
6644
6977
  containerEl,
6645
- setMaterialHid
6978
+ widgetSession,
6979
+ setMaterialHid,
6980
+ trackEvent
6646
6981
  ]);
6647
6982
  return /*#__PURE__*/ jsx(ModelViewerContext, {
6648
6983
  value: contextValue,
@@ -6654,27 +6989,204 @@ function useModelViewer() {
6654
6989
  if (!context) throw new Error('useModelViewer must be used within a ModelViewerProvider');
6655
6990
  return context;
6656
6991
  }
6992
+ function getUserAgent() {
6993
+ if ("u" < typeof navigator) return '';
6994
+ return navigator.userAgent || navigator.vendor || '';
6995
+ }
6996
+ function isIOSDevice() {
6997
+ if ("u" < typeof window || "u" < typeof navigator) return false;
6998
+ const isIPadOS = 'MacIntel' === navigator.platform && navigator.maxTouchPoints > 1;
6999
+ return (/iPad|iPhone|iPod/.test(getUserAgent()) || isIPadOS) && !('MSStream' in window);
7000
+ }
7001
+ function isMobileArDevice() {
7002
+ if ("u" < typeof window || "u" < typeof navigator) return false;
7003
+ const mobileUserAgent = /Android|iPhone|iPad|iPod|webOS|BlackBerry|Windows Phone|Opera Mini|IEMobile|Mobile/i.test(getUserAgent());
7004
+ const coarsePointer = window.matchMedia?.('(pointer: coarse)').matches ?? false;
7005
+ return navigator.maxTouchPoints > 1 && coarsePointer && (mobileUserAgent || isIOSDevice());
7006
+ }
7007
+ function isSupportedAppClipDevice() {
7008
+ if (!isIOSDevice()) return false;
7009
+ const versionText = getUserAgent().match(/\bOS (\d+)/)?.[1] ?? getUserAgent().match(/Version\/(\d+)/)?.[1];
7010
+ if (!versionText) return false;
7011
+ return Number.parseInt(versionText, 10) >= 17;
7012
+ }
7013
+ function buildAppClipUrl(bundleId, tinuuid, hostname, hid) {
7014
+ const url = new URL('https://appclip.apple.com/id');
7015
+ url.searchParams.set('p', bundleId);
7016
+ url.searchParams.set('tinuuid', tinuuid);
7017
+ if (hid) url.searchParams.set('hid', hid);
7018
+ url.searchParams.set('region', hostname.endsWith('arizo.ru') ? 'ru' : 'en');
7019
+ return url.toString();
7020
+ }
7021
+ function buildNativeArUrl(glbUrl, usdzUrl, objectType, ios = isIOSDevice()) {
7022
+ if (ios) return `${new URL(usdzUrl).toString()}#allowsContentScaling=0`;
7023
+ const verticalPlacement = 'wall' === objectType || 'ceiling' === objectType ? '&enable_vertical_placement=true' : '';
7024
+ return `intent://arvr.google.com/scene-viewer/1.0?file=${encodeURIComponent(new URL(glbUrl).toString())}&resizable=false${verticalPlacement}&mode=ar_preferred#Intent;scheme=https;package=com.google.android.googlequicksearchbox;action=android.intent.action.VIEW;end;`;
7025
+ }
7026
+ function launchDirectAr(options) {
7027
+ if ('advanced' === options.experience && isSupportedAppClipDevice()) return void window.location.assign(buildAppClipUrl(options.appClipBundleId, options.tinuuid, options.hostname, options.hid));
7028
+ const link = document.createElement('a');
7029
+ const image = document.createElement('img');
7030
+ link.rel = 'ar';
7031
+ link.href = buildNativeArUrl(options.glbUrl, options.usdzUrl, options.objectType);
7032
+ link.hidden = true;
7033
+ image.alt = '';
7034
+ image.hidden = true;
7035
+ link.appendChild(image);
7036
+ document.body.appendChild(link);
7037
+ link.click();
7038
+ link.remove();
7039
+ }
7040
+ async function launchArOverride(launchAr, target) {
7041
+ if (!launchAr || !target.tinuuid) return false;
7042
+ try {
7043
+ return await launchAr(target);
7044
+ } catch {
7045
+ return false;
7046
+ }
7047
+ }
7048
+ function buildModelViewerHandoffUrl({ apiKey, arExperience, baseUrl, hid, mode, tinuuid, widgetSessionToken }) {
7049
+ if (!baseUrl || !tinuuid) return null;
7050
+ let url;
7051
+ try {
7052
+ url = new URL(baseUrl);
7053
+ } catch {
7054
+ return null;
7055
+ }
7056
+ url.searchParams.set('tinuuid', tinuuid);
7057
+ url.searchParams.set('mode', mode);
7058
+ if (arExperience) url.searchParams.set('ar-experience', arExperience);
7059
+ else url.searchParams.delete('ar-experience');
7060
+ if (hid) url.searchParams.set('hid', hid);
7061
+ else url.searchParams.delete('hid');
7062
+ const fragment = new URLSearchParams(url.hash.slice(1));
7063
+ if (apiKey) fragment.set('api-key', apiKey);
7064
+ else fragment.delete('api-key');
7065
+ if (widgetSessionToken) fragment.set('widget-session-token', widgetSessionToken);
7066
+ else fragment.delete('widget-session-token');
7067
+ url.hash = fragment.toString();
7068
+ return url.toString();
7069
+ }
7070
+ const EVENT_FLUSH_TIMEOUT_MS = 300;
7071
+ async function flushWidgetEvent(eventRequest) {
7072
+ await new Promise((resolve)=>{
7073
+ const timeout = window.setTimeout(resolve, EVENT_FLUSH_TIMEOUT_MS);
7074
+ eventRequest.finally(()=>{
7075
+ window.clearTimeout(timeout);
7076
+ resolve();
7077
+ });
7078
+ });
7079
+ }
6657
7080
  function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
6658
- const { containerEl, isLoading, isModelLoading, material, modelViewerEl, object, tinuuid } = useModelViewer();
7081
+ const { apiKey, appClipBundleId, arExperience, containerEl, handoffUrl, isLoading, isModelLoading, launchAr, material, mode, modelViewerEl, object, tinuuid, trackEvent, widgetSession } = useModelViewer();
7082
+ const [handoffSession, setHandoffSession] = useState(null);
6659
7083
  const [open, setOpen] = useState(false);
7084
+ const autoLaunchKeyRef = useRef(null);
7085
+ const widgetSessionSid = widgetSession?.sid;
7086
+ const widgetSessionToken = widgetSession?.token;
6660
7087
  const isDisabled = isLoading || isModelLoading || null === material;
6661
- const url = external_react_useMemo(()=>{
6662
- if (!MODEL_VIEWER_URL) return null;
6663
- const url = new URL(MODEL_VIEWER_URL);
6664
- url.searchParams.set('tinuuid', object?.tinuuid ?? tinuuid);
6665
- if (material?.hid) url.searchParams.set('hid', material.hid);
6666
- return url.toString();
6667
- }, [
7088
+ const arTarget = external_react_useMemo(()=>({
7089
+ hid: material?.hid,
7090
+ tinuuid: object?.tinuuid ?? tinuuid
7091
+ }), [
6668
7092
  material?.hid,
6669
7093
  object?.tinuuid,
6670
7094
  tinuuid
6671
7095
  ]);
7096
+ const directArExperience = 'ar-view' === mode ? 'standard' : 'advanced-ar-view' === mode ? 'advanced' : 'qr-code' === mode ? arExperience : void 0;
7097
+ const directArLaunchOptions = external_react_useMemo(()=>{
7098
+ const native = material?.native;
7099
+ if (!directArExperience || !object || !native?.glbUrl || !native.usdzUrl || "u" < typeof window) return null;
7100
+ return {
7101
+ appClipBundleId,
7102
+ experience: directArExperience,
7103
+ glbUrl: native.glbUrl,
7104
+ hid: material?.hid,
7105
+ hostname: window.location.hostname,
7106
+ objectType: object.type,
7107
+ tinuuid: object.tinuuid ?? tinuuid,
7108
+ usdzUrl: native.usdzUrl
7109
+ };
7110
+ }, [
7111
+ appClipBundleId,
7112
+ directArExperience,
7113
+ material?.hid,
7114
+ material?.native,
7115
+ object,
7116
+ tinuuid
7117
+ ]);
7118
+ const url = external_react_useMemo(()=>buildModelViewerHandoffUrl({
7119
+ apiKey,
7120
+ arExperience,
7121
+ baseUrl: handoffUrl || MODEL_VIEWER_URL,
7122
+ hid: material?.hid,
7123
+ mode,
7124
+ tinuuid: object?.tinuuid ?? tinuuid,
7125
+ widgetSessionToken: handoffSession && handoffSession.sid === widgetSessionSid ? handoffSession.token : widgetSessionToken
7126
+ }), [
7127
+ apiKey,
7128
+ arExperience,
7129
+ handoffSession,
7130
+ handoffUrl,
7131
+ material?.hid,
7132
+ mode,
7133
+ object?.tinuuid,
7134
+ tinuuid,
7135
+ widgetSessionSid,
7136
+ widgetSessionToken
7137
+ ]);
7138
+ useEffect(()=>{
7139
+ if (!directArLaunchOptions || isLoading || !isMobileArDevice()) return;
7140
+ const launchKey = [
7141
+ widgetSessionSid ?? 'legacy',
7142
+ directArLaunchOptions.tinuuid,
7143
+ material?.hid,
7144
+ directArLaunchOptions.experience
7145
+ ].join(':');
7146
+ if (autoLaunchKeyRef.current === launchKey) return;
7147
+ autoLaunchKeyRef.current = launchKey;
7148
+ const timer = window.setTimeout(()=>{
7149
+ (async ()=>{
7150
+ if (autoLaunchKeyRef.current !== launchKey) return;
7151
+ if (await launchArOverride(launchAr, arTarget)) return;
7152
+ await flushWidgetEvent(trackEvent('ar_launched'));
7153
+ if (autoLaunchKeyRef.current === launchKey) launchDirectAr(directArLaunchOptions);
7154
+ })();
7155
+ }, 0);
7156
+ return ()=>{
7157
+ window.clearTimeout(timer);
7158
+ if (autoLaunchKeyRef.current === launchKey) autoLaunchKeyRef.current = null;
7159
+ };
7160
+ }, [
7161
+ arTarget,
7162
+ directArLaunchOptions,
7163
+ isLoading,
7164
+ launchAr,
7165
+ material?.hid,
7166
+ trackEvent,
7167
+ widgetSessionSid
7168
+ ]);
6672
7169
  const handleClick = async ()=>{
6673
7170
  if (!modelViewerEl || isDisabled) return;
6674
- if (modelViewerEl.canActivateAR) {
7171
+ if (await launchArOverride(launchAr, arTarget)) return void onBeforeActivateAr?.();
7172
+ if (directArLaunchOptions && isMobileArDevice()) {
6675
7173
  onBeforeActivateAr?.();
7174
+ await flushWidgetEvent(trackEvent('ar_launched'));
7175
+ launchDirectAr(directArLaunchOptions);
7176
+ } else if (modelViewerEl.canActivateAR) {
7177
+ onBeforeActivateAr?.();
7178
+ await flushWidgetEvent(trackEvent('ar_launched'));
6676
7179
  await modelViewerEl.activateAR();
6677
- } else if (url) setOpen(true);
7180
+ } else if (url) {
7181
+ const activeSession = widgetSession;
7182
+ const sessionToken = await activeSession?.getTokenForRequest().catch(()=>activeSession.token);
7183
+ setHandoffSession(activeSession && sessionToken ? {
7184
+ sid: activeSession.sid,
7185
+ token: sessionToken
7186
+ } : null);
7187
+ trackEvent('interacted');
7188
+ setOpen(true);
7189
+ }
6678
7190
  };
6679
7191
  const handleButtonClick = async (event)=>{
6680
7192
  onClick?.(event);
@@ -7097,6 +7609,7 @@ function MaterialVariants() {
7097
7609
  });
7098
7610
  }
7099
7611
  const viewerErrorTitle = {
7612
+ forbidden: 'Viewer unavailable for this domain',
7100
7613
  'not-found': 'Object not found',
7101
7614
  unauthorized: 'Invalid API key',
7102
7615
  'payment-required': 'Viewer temporarily unavailable'
@@ -7164,14 +7677,14 @@ function getHotspotConfig(slot) {
7164
7677
  if (!config) throw new Error(`Missing hotspot config for slot "${slot}"`);
7165
7678
  return config;
7166
7679
  }
7167
- function createDimensionLabels(modelViewerEl) {
7680
+ function createDimensionLabels(modelViewerEl, sizeType) {
7168
7681
  const info = calculateBoundingBoxInfo(modelViewerEl);
7169
7682
  return DIMENSION_LABEL_CONFIGS.map(({ slot, sizeAxis, offsetMultiplier })=>{
7170
7683
  const hotspotConfig = getHotspotConfig(slot);
7171
7684
  return {
7172
7685
  name: slot,
7173
7686
  position: calculateDimensionLabelPosition(info, hotspotConfig, offsetMultiplier),
7174
- text: formatSize(info.size[sizeAxis], 'cm')
7687
+ text: formatSize(info.size[sizeAxis], sizeType)
7175
7688
  };
7176
7689
  });
7177
7690
  }
@@ -7184,7 +7697,7 @@ function syncLabelHotspots(modelViewerEl, labels) {
7184
7697
  });
7185
7698
  }
7186
7699
  function useDimensions({ isVisible }) {
7187
- const { glbUrl, isModelLoading, modelViewerEl } = useModelViewer();
7700
+ const { glbUrl, isModelLoading, modelViewerEl, sizeType } = useModelViewer();
7188
7701
  const [lines, setLines] = useState([]);
7189
7702
  const [labels, setLabels] = useState([]);
7190
7703
  const [hiddenHotspots, setHiddenHotspots] = useState(()=>new Set());
@@ -7201,7 +7714,7 @@ function useDimensions({ isVisible }) {
7201
7714
  ]);
7202
7715
  const calculateLabels = useCallback(()=>{
7203
7716
  if (!modelViewerEl) return [];
7204
- const dimensionLabels = createDimensionLabels(modelViewerEl);
7717
+ const dimensionLabels = createDimensionLabels(modelViewerEl, sizeType);
7205
7718
  syncLabelHotspots(modelViewerEl, dimensionLabels);
7206
7719
  return dimensionLabels.map((label)=>({
7207
7720
  slot: label.name,
@@ -7209,7 +7722,8 @@ function useDimensions({ isVisible }) {
7209
7722
  position: label.position
7210
7723
  }));
7211
7724
  }, [
7212
- modelViewerEl
7725
+ modelViewerEl,
7726
+ sizeType
7213
7727
  ]);
7214
7728
  const updateLines = useCallback(()=>{
7215
7729
  if (!modelViewerEl) return;
@@ -7282,7 +7796,7 @@ function useDimensions({ isVisible }) {
7282
7796
  };
7283
7797
  }
7284
7798
  function ModelViewerContainer({ showDialogCloseButton = false }) {
7285
- const { glbUrl, isModelLoading, object, previewUrl, state, setContainerEl, setModelViewerEl, usdzUrl, layout, background } = useModelViewer();
7799
+ const { glbUrl, isModelLoading, object, previewUrl, state, setContainerEl, setModelViewerEl, usdzUrl, layout, background, trackEvent } = useModelViewer();
7286
7800
  const [isDimensionsVisible, setDimensionsVisible] = useState(false);
7287
7801
  const isModelLayout = 'model' === layout;
7288
7802
  const showViewerLoader = 'loading' === state || 'ready' === state && isModelLoading;
@@ -7293,7 +7807,7 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7293
7807
  const { lines, labels, hiddenHotspots } = useDimensions({
7294
7808
  isVisible: isDimensionsActive
7295
7809
  });
7296
- const errorState = 'not-found' === state || 'unauthorized' === state || 'payment-required' === state ? state : null;
7810
+ const errorState = 'forbidden' === state || 'not-found' === state || 'unauthorized' === state || 'payment-required' === state ? state : null;
7297
7811
  if (errorState) return /*#__PURE__*/ jsx("div", {
7298
7812
  ref: setContainerEl,
7299
7813
  className: utils_cn('relative size-full overflow-hidden text-foreground', 'default' === background && 'bg-background-layout'),
@@ -7323,6 +7837,7 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7323
7837
  arScale: "fixed",
7324
7838
  shadowIntensity: 1,
7325
7839
  interactionPromptThreshold: 100,
7840
+ loading: "eager",
7326
7841
  children: [
7327
7842
  /*#__PURE__*/ jsx("div", {
7328
7843
  slot: "progress-bar"
@@ -7366,7 +7881,10 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7366
7881
  disabled: !canToggleDimensions,
7367
7882
  "aria-label": "Toggle dimensions",
7368
7883
  "aria-pressed": isDimensionsVisible,
7369
- onClick: ()=>setDimensionsVisible((current)=>!current),
7884
+ onClick: ()=>{
7885
+ setDimensionsVisible((current)=>!current);
7886
+ trackEvent('interacted');
7887
+ },
7370
7888
  children: [
7371
7889
  isDimensionsVisible ? /*#__PURE__*/ jsx(Eye, {
7372
7890
  className: "size-6"
@@ -7391,7 +7909,7 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7391
7909
  });
7392
7910
  }
7393
7911
  function normalizeApiKey(apiKey) {
7394
- return apiKey.trim();
7912
+ return apiKey?.trim() ?? '';
7395
7913
  }
7396
7914
  const root_queryClient = new QueryClient({
7397
7915
  defaultOptions: {
@@ -7408,26 +7926,33 @@ const root_queryClient = new QueryClient({
7408
7926
  }
7409
7927
  }
7410
7928
  });
7411
- function ModelViewerRoot({ tinuuid, apiKey, hid, layout = 'default', background = 'default', showDialogCloseButton = false }) {
7929
+ function ModelViewerRoot({ tinuuid, apiKey, appClipBundleId = 'com.us.vizbl.Clip', arExperience, hid, apiBaseUrl = "https://api.vizbl.com", legacyApiBaseUrl = "https://api.vizbl.com/v1", mode = 'embedded-3d-viewer', initialWidgetSessionToken, handoffUrl = "https://integrations.vizbl.com/model-viewer", launchAr, sizeType = 'cm', layout = 'default', background = 'default', showDialogCloseButton = false, viewerSurface = 'inline' }) {
7412
7930
  const normalizedApiKey = normalizeApiKey(apiKey);
7413
7931
  return /*#__PURE__*/ jsx(QueryClientProvider, {
7414
7932
  client: root_queryClient,
7415
7933
  children: /*#__PURE__*/ jsx(ModelViewerProvider, {
7416
7934
  tinuuid: tinuuid,
7417
7935
  apiKey: normalizedApiKey,
7936
+ appClipBundleId: appClipBundleId,
7937
+ arExperience: arExperience,
7938
+ apiBaseUrl: apiBaseUrl,
7939
+ legacyApiBaseUrl: legacyApiBaseUrl,
7940
+ mode: mode,
7941
+ initialWidgetSessionToken: initialWidgetSessionToken,
7942
+ handoffUrl: handoffUrl,
7418
7943
  hid: hid,
7944
+ launchAr: launchAr,
7419
7945
  layout: layout,
7420
7946
  background: background,
7947
+ sizeType: sizeType,
7948
+ viewerSurface: viewerSurface,
7421
7949
  children: /*#__PURE__*/ jsx(ModelViewerContainer, {
7422
7950
  showDialogCloseButton: showDialogCloseButton
7423
7951
  })
7424
7952
  })
7425
7953
  });
7426
7954
  }
7427
- configureNodeApiClient({
7428
- baseURL: "https://api.vizbl.com"
7429
- });
7430
- function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7955
+ function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, legacyApiBaseUrl, initialWidgetSessionToken, handoffUrl, hid, sizeType, ...props }) {
7431
7956
  const [container, setContainer] = useState(null);
7432
7957
  return /*#__PURE__*/ jsxs(Fragment, {
7433
7958
  children: [
@@ -7440,7 +7965,7 @@ function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7440
7965
  children: /*#__PURE__*/ jsx(Dialog, {
7441
7966
  ...props,
7442
7967
  children: /*#__PURE__*/ jsxs(DialogContent, {
7443
- className: "min-w-full h-svh p-0 rounded-none sm:p-0",
7968
+ className: "min-w-full h-dvh p-0 rounded-none sm:p-0",
7444
7969
  portalContainer: container,
7445
7970
  showCloseButton: false,
7446
7971
  onInteractOutside: (e)=>{
@@ -7461,8 +7986,15 @@ function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7461
7986
  /*#__PURE__*/ jsx(ModelViewerRoot, {
7462
7987
  tinuuid: tinuuid,
7463
7988
  apiKey: apiKey,
7989
+ apiBaseUrl: apiBaseUrl,
7990
+ legacyApiBaseUrl: legacyApiBaseUrl,
7991
+ mode: "ar-viewer",
7992
+ initialWidgetSessionToken: initialWidgetSessionToken,
7993
+ handoffUrl: handoffUrl,
7464
7994
  hid: hid,
7465
- showDialogCloseButton: true
7995
+ sizeType: sizeType,
7996
+ showDialogCloseButton: true,
7997
+ viewerSurface: "dialog"
7466
7998
  })
7467
7999
  ]
7468
8000
  })
@@ -7486,7 +8018,7 @@ function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7486
8018
  ]
7487
8019
  });
7488
8020
  }
7489
- function ModelViewer({ tinuuid, apiKey, hid, layout, background, className, ...props }) {
8021
+ function ModelViewer({ tinuuid, apiKey, appClipBundleId, arExperience, hid, apiBaseUrl, legacyApiBaseUrl, mode, initialWidgetSessionToken, handoffUrl, launchAr, sizeType, layout, background, viewerSurface, className, ...props }) {
7490
8022
  const [container, setContainer] = useState(null);
7491
8023
  return /*#__PURE__*/ jsxs(Fragment, {
7492
8024
  children: [
@@ -7517,9 +8049,19 @@ function ModelViewer({ tinuuid, apiKey, hid, layout, background, className, ...p
7517
8049
  children: /*#__PURE__*/ jsx(ModelViewerRoot, {
7518
8050
  tinuuid: tinuuid,
7519
8051
  apiKey: apiKey,
8052
+ appClipBundleId: appClipBundleId,
8053
+ arExperience: arExperience,
8054
+ apiBaseUrl: apiBaseUrl,
8055
+ legacyApiBaseUrl: legacyApiBaseUrl,
8056
+ mode: mode,
8057
+ initialWidgetSessionToken: initialWidgetSessionToken,
8058
+ handoffUrl: handoffUrl,
7520
8059
  hid: hid,
8060
+ launchAr: launchAr,
8061
+ sizeType: sizeType,
7521
8062
  layout: layout,
7522
- background: background
8063
+ background: background,
8064
+ viewerSurface: viewerSurface
7523
8065
  })
7524
8066
  })
7525
8067
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neurodyn/react-model-viewer",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -36,7 +36,7 @@
36
36
  "radix-ui": "1.4.3",
37
37
  "react-shadow": "^20.6.0",
38
38
  "tailwind-merge": "3.4.0",
39
- "three": "^0.182.0"
39
+ "three": "0.182.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@microsoft/api-extractor": "^7.58.0",