@neurodyn/react-model-viewer 0.0.2 → 0.0.3

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,82 @@ 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
  }
6557
6781
  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
6782
+ function ModelViewerProvider({ children, tinuuid, apiKey, appClipBundleId, arExperience, apiBaseUrl, legacyApiBaseUrl, mode, initialWidgetSessionToken, handoffUrl, hid, launchAr, layout, background, sizeType, viewerSurface }) {
6783
+ const { object, material, error, isLoading, setMaterialHid: setObjectMaterialHid, widgetSession } = useObjectData(tinuuid, hid, {
6784
+ apiBaseUrl,
6785
+ apiKey,
6786
+ initialWidgetSessionToken,
6787
+ legacyApiBaseUrl,
6788
+ mode
6561
6789
  });
6562
6790
  const [isModelLoading, setIsModelLoading] = useState(true);
6563
6791
  const [containerEl, setContainerEl] = useState(null);
6564
6792
  const [modelViewerEl, setModelViewerEl] = useState(null);
6565
- const hasNonEmptyApiKey = apiKey.length > 0;
6793
+ const trackedCameraInteractionSidRef = useRef(null);
6794
+ const trackedOpenSidRef = useRef(null);
6566
6795
  const glbUrl = material?.native?.glbUrl ?? null;
6567
6796
  const usdzUrl = material?.native?.usdzUrl ?? null;
6568
6797
  const previewUrl = material?.previews?.[0]?.url ?? null;
6569
6798
  const state = external_react_useMemo(()=>{
6570
- if (!hasNonEmptyApiKey) return 'unauthorized';
6571
6799
  if (isLoading) return 'loading';
6572
6800
  const accessError = getModelViewerApiError(error);
6573
6801
  if (accessError) return accessError;
6802
+ const instantArExperience = 'ar-view' === mode || 'qr-code' === mode && 'standard' === arExperience;
6803
+ const advancedArExperience = 'advanced-ar-view' === mode || 'qr-code' === mode && 'advanced' === arExperience;
6804
+ if (instantArExperience && object?.policy?.instantArView === false) return 'forbidden';
6805
+ if ((advancedArExperience || 'ar-viewer' === mode) && object?.policy?.appClip === false) return 'forbidden';
6574
6806
  if (null != error || !object || !material || !glbUrl) return 'not-found';
6575
6807
  return 'ready';
6576
6808
  }, [
6809
+ arExperience,
6577
6810
  error,
6578
6811
  glbUrl,
6579
- hasNonEmptyApiKey,
6580
6812
  isLoading,
6581
6813
  material,
6814
+ mode,
6582
6815
  object
6583
6816
  ]);
6817
+ const trackEvent = useCallback((event)=>sendModelViewerWidgetEvent(widgetSession, event, {
6818
+ objectId: object?.uuid,
6819
+ variantId: material?.hid
6820
+ }), [
6821
+ material?.hid,
6822
+ object?.uuid,
6823
+ widgetSession
6824
+ ]);
6825
+ const setMaterialHid = useCallback((nextHid)=>{
6826
+ if (nextHid === material?.hid) return;
6827
+ setObjectMaterialHid(nextHid);
6828
+ sendModelViewerWidgetEvent(widgetSession, 'interacted', {
6829
+ objectId: object?.uuid,
6830
+ variantId: nextHid
6831
+ });
6832
+ }, [
6833
+ material?.hid,
6834
+ object?.uuid,
6835
+ setObjectMaterialHid,
6836
+ widgetSession
6837
+ ]);
6838
+ useEffect(()=>{
6839
+ if ('inline' === viewerSurface || 'ready' !== state || !widgetSession || trackedOpenSidRef.current === widgetSession.sid) return;
6840
+ trackEvent('widget_opened');
6841
+ trackedOpenSidRef.current = widgetSession.sid;
6842
+ }, [
6843
+ state,
6844
+ trackEvent,
6845
+ viewerSurface,
6846
+ widgetSession
6847
+ ]);
6848
+ useEffect(()=>{
6849
+ trackedCameraInteractionSidRef.current = null;
6850
+ }, [
6851
+ widgetSession?.sid
6852
+ ]);
6584
6853
  useEffect(()=>{
6585
6854
  setIsModelLoading('ready' === state && Boolean(glbUrl));
6586
6855
  }, [
@@ -6600,49 +6869,77 @@ function ModelViewerProvider({ children, tinuuid, apiKey, hid, layout, backgroun
6600
6869
  const handleError = ()=>{
6601
6870
  setIsModelLoading(false);
6602
6871
  };
6872
+ const handleCameraChange = (event)=>{
6873
+ const source = event.detail?.source;
6874
+ if ('user-interaction' !== source || !widgetSession || trackedCameraInteractionSidRef.current === widgetSession.sid) return;
6875
+ trackEvent('interacted');
6876
+ trackedCameraInteractionSidRef.current = widgetSession.sid;
6877
+ };
6603
6878
  modelViewerEl.addEventListener('progress', handleProgress);
6604
6879
  modelViewerEl.addEventListener('load', handleLoad);
6605
6880
  modelViewerEl.addEventListener('error', handleError);
6881
+ modelViewerEl.addEventListener('camera-change', handleCameraChange);
6606
6882
  return ()=>{
6607
6883
  modelViewerEl.removeEventListener('progress', handleProgress);
6608
6884
  modelViewerEl.removeEventListener('load', handleLoad);
6609
6885
  modelViewerEl.removeEventListener('error', handleError);
6886
+ modelViewerEl.removeEventListener('camera-change', handleCameraChange);
6610
6887
  };
6611
6888
  }, [
6612
- modelViewerEl
6889
+ modelViewerEl,
6890
+ trackEvent,
6891
+ widgetSession
6613
6892
  ]);
6614
6893
  const contextValue = external_react_useMemo(()=>({
6894
+ apiKey,
6895
+ appClipBundleId,
6896
+ arExperience,
6615
6897
  tinuuid,
6616
6898
  object,
6617
6899
  material,
6618
6900
  glbUrl,
6619
6901
  usdzUrl,
6620
6902
  previewUrl,
6903
+ handoffUrl,
6904
+ launchAr,
6621
6905
  layout,
6622
6906
  background,
6907
+ mode,
6908
+ sizeType,
6623
6909
  isLoading,
6624
6910
  isModelLoading,
6625
6911
  state,
6626
6912
  modelViewerEl,
6627
6913
  containerEl,
6914
+ widgetSession,
6628
6915
  setMaterialHid,
6629
6916
  setContainerEl,
6630
- setModelViewerEl
6917
+ setModelViewerEl,
6918
+ trackEvent
6631
6919
  }), [
6920
+ apiKey,
6921
+ appClipBundleId,
6922
+ arExperience,
6632
6923
  tinuuid,
6633
6924
  object,
6634
6925
  material,
6635
6926
  glbUrl,
6636
6927
  usdzUrl,
6637
6928
  previewUrl,
6929
+ handoffUrl,
6930
+ launchAr,
6638
6931
  layout,
6639
6932
  background,
6933
+ mode,
6934
+ sizeType,
6640
6935
  isLoading,
6641
6936
  isModelLoading,
6642
6937
  state,
6643
6938
  modelViewerEl,
6644
6939
  containerEl,
6645
- setMaterialHid
6940
+ widgetSession,
6941
+ setMaterialHid,
6942
+ trackEvent
6646
6943
  ]);
6647
6944
  return /*#__PURE__*/ jsx(ModelViewerContext, {
6648
6945
  value: contextValue,
@@ -6654,27 +6951,204 @@ function useModelViewer() {
6654
6951
  if (!context) throw new Error('useModelViewer must be used within a ModelViewerProvider');
6655
6952
  return context;
6656
6953
  }
6954
+ function getUserAgent() {
6955
+ if ("u" < typeof navigator) return '';
6956
+ return navigator.userAgent || navigator.vendor || '';
6957
+ }
6958
+ function isIOSDevice() {
6959
+ if ("u" < typeof window || "u" < typeof navigator) return false;
6960
+ const isIPadOS = 'MacIntel' === navigator.platform && navigator.maxTouchPoints > 1;
6961
+ return (/iPad|iPhone|iPod/.test(getUserAgent()) || isIPadOS) && !('MSStream' in window);
6962
+ }
6963
+ function isMobileArDevice() {
6964
+ if ("u" < typeof window || "u" < typeof navigator) return false;
6965
+ const mobileUserAgent = /Android|iPhone|iPad|iPod|webOS|BlackBerry|Windows Phone|Opera Mini|IEMobile|Mobile/i.test(getUserAgent());
6966
+ const coarsePointer = window.matchMedia?.('(pointer: coarse)').matches ?? false;
6967
+ return navigator.maxTouchPoints > 1 && coarsePointer && (mobileUserAgent || isIOSDevice());
6968
+ }
6969
+ function isSupportedAppClipDevice() {
6970
+ if (!isIOSDevice()) return false;
6971
+ const versionText = getUserAgent().match(/\bOS (\d+)/)?.[1] ?? getUserAgent().match(/Version\/(\d+)/)?.[1];
6972
+ if (!versionText) return false;
6973
+ return Number.parseInt(versionText, 10) >= 17;
6974
+ }
6975
+ function buildAppClipUrl(bundleId, tinuuid, hostname, hid) {
6976
+ const url = new URL('https://appclip.apple.com/id');
6977
+ url.searchParams.set('p', bundleId);
6978
+ url.searchParams.set('tinuuid', tinuuid);
6979
+ if (hid) url.searchParams.set('hid', hid);
6980
+ url.searchParams.set('region', hostname.endsWith('arizo.ru') ? 'ru' : 'en');
6981
+ return url.toString();
6982
+ }
6983
+ function buildNativeArUrl(glbUrl, usdzUrl, objectType, ios = isIOSDevice()) {
6984
+ if (ios) return `${new URL(usdzUrl).toString()}#allowsContentScaling=0`;
6985
+ const verticalPlacement = 'wall' === objectType || 'ceiling' === objectType ? '&enable_vertical_placement=true' : '';
6986
+ 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;`;
6987
+ }
6988
+ function launchDirectAr(options) {
6989
+ if ('advanced' === options.experience && isSupportedAppClipDevice()) return void window.location.assign(buildAppClipUrl(options.appClipBundleId, options.tinuuid, options.hostname, options.hid));
6990
+ const link = document.createElement('a');
6991
+ const image = document.createElement('img');
6992
+ link.rel = 'ar';
6993
+ link.href = buildNativeArUrl(options.glbUrl, options.usdzUrl, options.objectType);
6994
+ link.hidden = true;
6995
+ image.alt = '';
6996
+ image.hidden = true;
6997
+ link.appendChild(image);
6998
+ document.body.appendChild(link);
6999
+ link.click();
7000
+ link.remove();
7001
+ }
7002
+ async function launchArOverride(launchAr, target) {
7003
+ if (!launchAr || !target.tinuuid) return false;
7004
+ try {
7005
+ return await launchAr(target);
7006
+ } catch {
7007
+ return false;
7008
+ }
7009
+ }
7010
+ function buildModelViewerHandoffUrl({ apiKey, arExperience, baseUrl, hid, mode, tinuuid, widgetSessionToken }) {
7011
+ if (!baseUrl || !tinuuid) return null;
7012
+ let url;
7013
+ try {
7014
+ url = new URL(baseUrl);
7015
+ } catch {
7016
+ return null;
7017
+ }
7018
+ url.searchParams.set('tinuuid', tinuuid);
7019
+ url.searchParams.set('mode', mode);
7020
+ if (arExperience) url.searchParams.set('ar-experience', arExperience);
7021
+ else url.searchParams.delete('ar-experience');
7022
+ if (hid) url.searchParams.set('hid', hid);
7023
+ else url.searchParams.delete('hid');
7024
+ const fragment = new URLSearchParams(url.hash.slice(1));
7025
+ if (apiKey) fragment.set('api-key', apiKey);
7026
+ else fragment.delete('api-key');
7027
+ if (widgetSessionToken) fragment.set('widget-session-token', widgetSessionToken);
7028
+ else fragment.delete('widget-session-token');
7029
+ url.hash = fragment.toString();
7030
+ return url.toString();
7031
+ }
7032
+ const EVENT_FLUSH_TIMEOUT_MS = 300;
7033
+ async function flushWidgetEvent(eventRequest) {
7034
+ await new Promise((resolve)=>{
7035
+ const timeout = window.setTimeout(resolve, EVENT_FLUSH_TIMEOUT_MS);
7036
+ eventRequest.finally(()=>{
7037
+ window.clearTimeout(timeout);
7038
+ resolve();
7039
+ });
7040
+ });
7041
+ }
6657
7042
  function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
6658
- const { containerEl, isLoading, isModelLoading, material, modelViewerEl, object, tinuuid } = useModelViewer();
7043
+ const { apiKey, appClipBundleId, arExperience, containerEl, handoffUrl, isLoading, isModelLoading, launchAr, material, mode, modelViewerEl, object, tinuuid, trackEvent, widgetSession } = useModelViewer();
7044
+ const [handoffSession, setHandoffSession] = useState(null);
6659
7045
  const [open, setOpen] = useState(false);
7046
+ const autoLaunchKeyRef = useRef(null);
7047
+ const widgetSessionSid = widgetSession?.sid;
7048
+ const widgetSessionToken = widgetSession?.token;
6660
7049
  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
- }, [
7050
+ const arTarget = external_react_useMemo(()=>({
7051
+ hid: material?.hid,
7052
+ tinuuid: object?.tinuuid ?? tinuuid
7053
+ }), [
6668
7054
  material?.hid,
6669
7055
  object?.tinuuid,
6670
7056
  tinuuid
6671
7057
  ]);
7058
+ const directArExperience = 'ar-view' === mode ? 'standard' : 'advanced-ar-view' === mode ? 'advanced' : 'qr-code' === mode ? arExperience : void 0;
7059
+ const directArLaunchOptions = external_react_useMemo(()=>{
7060
+ const native = material?.native;
7061
+ if (!directArExperience || !object || !native?.glbUrl || !native.usdzUrl || "u" < typeof window) return null;
7062
+ return {
7063
+ appClipBundleId,
7064
+ experience: directArExperience,
7065
+ glbUrl: native.glbUrl,
7066
+ hid: material?.hid,
7067
+ hostname: window.location.hostname,
7068
+ objectType: object.type,
7069
+ tinuuid: object.tinuuid ?? tinuuid,
7070
+ usdzUrl: native.usdzUrl
7071
+ };
7072
+ }, [
7073
+ appClipBundleId,
7074
+ directArExperience,
7075
+ material?.hid,
7076
+ material?.native,
7077
+ object,
7078
+ tinuuid
7079
+ ]);
7080
+ const url = external_react_useMemo(()=>buildModelViewerHandoffUrl({
7081
+ apiKey,
7082
+ arExperience,
7083
+ baseUrl: handoffUrl || MODEL_VIEWER_URL,
7084
+ hid: material?.hid,
7085
+ mode,
7086
+ tinuuid: object?.tinuuid ?? tinuuid,
7087
+ widgetSessionToken: handoffSession && handoffSession.sid === widgetSessionSid ? handoffSession.token : widgetSessionToken
7088
+ }), [
7089
+ apiKey,
7090
+ arExperience,
7091
+ handoffSession,
7092
+ handoffUrl,
7093
+ material?.hid,
7094
+ mode,
7095
+ object?.tinuuid,
7096
+ tinuuid,
7097
+ widgetSessionSid,
7098
+ widgetSessionToken
7099
+ ]);
7100
+ useEffect(()=>{
7101
+ if (!directArLaunchOptions || isLoading || !isMobileArDevice()) return;
7102
+ const launchKey = [
7103
+ widgetSessionSid ?? 'legacy',
7104
+ directArLaunchOptions.tinuuid,
7105
+ material?.hid,
7106
+ directArLaunchOptions.experience
7107
+ ].join(':');
7108
+ if (autoLaunchKeyRef.current === launchKey) return;
7109
+ autoLaunchKeyRef.current = launchKey;
7110
+ const timer = window.setTimeout(()=>{
7111
+ (async ()=>{
7112
+ if (autoLaunchKeyRef.current !== launchKey) return;
7113
+ if (await launchArOverride(launchAr, arTarget)) return;
7114
+ await flushWidgetEvent(trackEvent('ar_launched'));
7115
+ if (autoLaunchKeyRef.current === launchKey) launchDirectAr(directArLaunchOptions);
7116
+ })();
7117
+ }, 0);
7118
+ return ()=>{
7119
+ window.clearTimeout(timer);
7120
+ if (autoLaunchKeyRef.current === launchKey) autoLaunchKeyRef.current = null;
7121
+ };
7122
+ }, [
7123
+ arTarget,
7124
+ directArLaunchOptions,
7125
+ isLoading,
7126
+ launchAr,
7127
+ material?.hid,
7128
+ trackEvent,
7129
+ widgetSessionSid
7130
+ ]);
6672
7131
  const handleClick = async ()=>{
6673
7132
  if (!modelViewerEl || isDisabled) return;
6674
- if (modelViewerEl.canActivateAR) {
7133
+ if (await launchArOverride(launchAr, arTarget)) return void onBeforeActivateAr?.();
7134
+ if (directArLaunchOptions && isMobileArDevice()) {
6675
7135
  onBeforeActivateAr?.();
7136
+ await flushWidgetEvent(trackEvent('ar_launched'));
7137
+ launchDirectAr(directArLaunchOptions);
7138
+ } else if (modelViewerEl.canActivateAR) {
7139
+ onBeforeActivateAr?.();
7140
+ await flushWidgetEvent(trackEvent('ar_launched'));
6676
7141
  await modelViewerEl.activateAR();
6677
- } else if (url) setOpen(true);
7142
+ } else if (url) {
7143
+ const activeSession = widgetSession;
7144
+ const sessionToken = await activeSession?.getTokenForRequest().catch(()=>activeSession.token);
7145
+ setHandoffSession(activeSession && sessionToken ? {
7146
+ sid: activeSession.sid,
7147
+ token: sessionToken
7148
+ } : null);
7149
+ trackEvent('interacted');
7150
+ setOpen(true);
7151
+ }
6678
7152
  };
6679
7153
  const handleButtonClick = async (event)=>{
6680
7154
  onClick?.(event);
@@ -7097,6 +7571,7 @@ function MaterialVariants() {
7097
7571
  });
7098
7572
  }
7099
7573
  const viewerErrorTitle = {
7574
+ forbidden: 'Viewer unavailable for this domain',
7100
7575
  'not-found': 'Object not found',
7101
7576
  unauthorized: 'Invalid API key',
7102
7577
  'payment-required': 'Viewer temporarily unavailable'
@@ -7164,14 +7639,14 @@ function getHotspotConfig(slot) {
7164
7639
  if (!config) throw new Error(`Missing hotspot config for slot "${slot}"`);
7165
7640
  return config;
7166
7641
  }
7167
- function createDimensionLabels(modelViewerEl) {
7642
+ function createDimensionLabels(modelViewerEl, sizeType) {
7168
7643
  const info = calculateBoundingBoxInfo(modelViewerEl);
7169
7644
  return DIMENSION_LABEL_CONFIGS.map(({ slot, sizeAxis, offsetMultiplier })=>{
7170
7645
  const hotspotConfig = getHotspotConfig(slot);
7171
7646
  return {
7172
7647
  name: slot,
7173
7648
  position: calculateDimensionLabelPosition(info, hotspotConfig, offsetMultiplier),
7174
- text: formatSize(info.size[sizeAxis], 'cm')
7649
+ text: formatSize(info.size[sizeAxis], sizeType)
7175
7650
  };
7176
7651
  });
7177
7652
  }
@@ -7184,7 +7659,7 @@ function syncLabelHotspots(modelViewerEl, labels) {
7184
7659
  });
7185
7660
  }
7186
7661
  function useDimensions({ isVisible }) {
7187
- const { glbUrl, isModelLoading, modelViewerEl } = useModelViewer();
7662
+ const { glbUrl, isModelLoading, modelViewerEl, sizeType } = useModelViewer();
7188
7663
  const [lines, setLines] = useState([]);
7189
7664
  const [labels, setLabels] = useState([]);
7190
7665
  const [hiddenHotspots, setHiddenHotspots] = useState(()=>new Set());
@@ -7201,7 +7676,7 @@ function useDimensions({ isVisible }) {
7201
7676
  ]);
7202
7677
  const calculateLabels = useCallback(()=>{
7203
7678
  if (!modelViewerEl) return [];
7204
- const dimensionLabels = createDimensionLabels(modelViewerEl);
7679
+ const dimensionLabels = createDimensionLabels(modelViewerEl, sizeType);
7205
7680
  syncLabelHotspots(modelViewerEl, dimensionLabels);
7206
7681
  return dimensionLabels.map((label)=>({
7207
7682
  slot: label.name,
@@ -7209,7 +7684,8 @@ function useDimensions({ isVisible }) {
7209
7684
  position: label.position
7210
7685
  }));
7211
7686
  }, [
7212
- modelViewerEl
7687
+ modelViewerEl,
7688
+ sizeType
7213
7689
  ]);
7214
7690
  const updateLines = useCallback(()=>{
7215
7691
  if (!modelViewerEl) return;
@@ -7282,7 +7758,7 @@ function useDimensions({ isVisible }) {
7282
7758
  };
7283
7759
  }
7284
7760
  function ModelViewerContainer({ showDialogCloseButton = false }) {
7285
- const { glbUrl, isModelLoading, object, previewUrl, state, setContainerEl, setModelViewerEl, usdzUrl, layout, background } = useModelViewer();
7761
+ const { glbUrl, isModelLoading, object, previewUrl, state, setContainerEl, setModelViewerEl, usdzUrl, layout, background, trackEvent } = useModelViewer();
7286
7762
  const [isDimensionsVisible, setDimensionsVisible] = useState(false);
7287
7763
  const isModelLayout = 'model' === layout;
7288
7764
  const showViewerLoader = 'loading' === state || 'ready' === state && isModelLoading;
@@ -7293,7 +7769,7 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7293
7769
  const { lines, labels, hiddenHotspots } = useDimensions({
7294
7770
  isVisible: isDimensionsActive
7295
7771
  });
7296
- const errorState = 'not-found' === state || 'unauthorized' === state || 'payment-required' === state ? state : null;
7772
+ const errorState = 'forbidden' === state || 'not-found' === state || 'unauthorized' === state || 'payment-required' === state ? state : null;
7297
7773
  if (errorState) return /*#__PURE__*/ jsx("div", {
7298
7774
  ref: setContainerEl,
7299
7775
  className: utils_cn('relative size-full overflow-hidden text-foreground', 'default' === background && 'bg-background-layout'),
@@ -7366,7 +7842,10 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7366
7842
  disabled: !canToggleDimensions,
7367
7843
  "aria-label": "Toggle dimensions",
7368
7844
  "aria-pressed": isDimensionsVisible,
7369
- onClick: ()=>setDimensionsVisible((current)=>!current),
7845
+ onClick: ()=>{
7846
+ setDimensionsVisible((current)=>!current);
7847
+ trackEvent('interacted');
7848
+ },
7370
7849
  children: [
7371
7850
  isDimensionsVisible ? /*#__PURE__*/ jsx(Eye, {
7372
7851
  className: "size-6"
@@ -7391,7 +7870,7 @@ function ModelViewerContainer({ showDialogCloseButton = false }) {
7391
7870
  });
7392
7871
  }
7393
7872
  function normalizeApiKey(apiKey) {
7394
- return apiKey.trim();
7873
+ return apiKey?.trim() ?? '';
7395
7874
  }
7396
7875
  const root_queryClient = new QueryClient({
7397
7876
  defaultOptions: {
@@ -7408,26 +7887,33 @@ const root_queryClient = new QueryClient({
7408
7887
  }
7409
7888
  }
7410
7889
  });
7411
- function ModelViewerRoot({ tinuuid, apiKey, hid, layout = 'default', background = 'default', showDialogCloseButton = false }) {
7890
+ 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
7891
  const normalizedApiKey = normalizeApiKey(apiKey);
7413
7892
  return /*#__PURE__*/ jsx(QueryClientProvider, {
7414
7893
  client: root_queryClient,
7415
7894
  children: /*#__PURE__*/ jsx(ModelViewerProvider, {
7416
7895
  tinuuid: tinuuid,
7417
7896
  apiKey: normalizedApiKey,
7897
+ appClipBundleId: appClipBundleId,
7898
+ arExperience: arExperience,
7899
+ apiBaseUrl: apiBaseUrl,
7900
+ legacyApiBaseUrl: legacyApiBaseUrl,
7901
+ mode: mode,
7902
+ initialWidgetSessionToken: initialWidgetSessionToken,
7903
+ handoffUrl: handoffUrl,
7418
7904
  hid: hid,
7905
+ launchAr: launchAr,
7419
7906
  layout: layout,
7420
7907
  background: background,
7908
+ sizeType: sizeType,
7909
+ viewerSurface: viewerSurface,
7421
7910
  children: /*#__PURE__*/ jsx(ModelViewerContainer, {
7422
7911
  showDialogCloseButton: showDialogCloseButton
7423
7912
  })
7424
7913
  })
7425
7914
  });
7426
7915
  }
7427
- configureNodeApiClient({
7428
- baseURL: "https://api.vizbl.com"
7429
- });
7430
- function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7916
+ function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, legacyApiBaseUrl, initialWidgetSessionToken, handoffUrl, hid, sizeType, ...props }) {
7431
7917
  const [container, setContainer] = useState(null);
7432
7918
  return /*#__PURE__*/ jsxs(Fragment, {
7433
7919
  children: [
@@ -7440,7 +7926,7 @@ function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7440
7926
  children: /*#__PURE__*/ jsx(Dialog, {
7441
7927
  ...props,
7442
7928
  children: /*#__PURE__*/ jsxs(DialogContent, {
7443
- className: "min-w-full h-svh p-0 rounded-none sm:p-0",
7929
+ className: "min-w-full h-dvh p-0 rounded-none sm:p-0",
7444
7930
  portalContainer: container,
7445
7931
  showCloseButton: false,
7446
7932
  onInteractOutside: (e)=>{
@@ -7461,8 +7947,15 @@ function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7461
7947
  /*#__PURE__*/ jsx(ModelViewerRoot, {
7462
7948
  tinuuid: tinuuid,
7463
7949
  apiKey: apiKey,
7950
+ apiBaseUrl: apiBaseUrl,
7951
+ legacyApiBaseUrl: legacyApiBaseUrl,
7952
+ mode: "ar-viewer",
7953
+ initialWidgetSessionToken: initialWidgetSessionToken,
7954
+ handoffUrl: handoffUrl,
7464
7955
  hid: hid,
7465
- showDialogCloseButton: true
7956
+ sizeType: sizeType,
7957
+ showDialogCloseButton: true,
7958
+ viewerSurface: "dialog"
7466
7959
  })
7467
7960
  ]
7468
7961
  })
@@ -7486,7 +7979,7 @@ function ModelViewerDialog({ tinuuid, apiKey, hid, ...props }) {
7486
7979
  ]
7487
7980
  });
7488
7981
  }
7489
- function ModelViewer({ tinuuid, apiKey, hid, layout, background, className, ...props }) {
7982
+ function ModelViewer({ tinuuid, apiKey, appClipBundleId, arExperience, hid, apiBaseUrl, legacyApiBaseUrl, mode, initialWidgetSessionToken, handoffUrl, launchAr, sizeType, layout, background, viewerSurface, className, ...props }) {
7490
7983
  const [container, setContainer] = useState(null);
7491
7984
  return /*#__PURE__*/ jsxs(Fragment, {
7492
7985
  children: [
@@ -7517,9 +8010,19 @@ function ModelViewer({ tinuuid, apiKey, hid, layout, background, className, ...p
7517
8010
  children: /*#__PURE__*/ jsx(ModelViewerRoot, {
7518
8011
  tinuuid: tinuuid,
7519
8012
  apiKey: apiKey,
8013
+ appClipBundleId: appClipBundleId,
8014
+ arExperience: arExperience,
8015
+ apiBaseUrl: apiBaseUrl,
8016
+ legacyApiBaseUrl: legacyApiBaseUrl,
8017
+ mode: mode,
8018
+ initialWidgetSessionToken: initialWidgetSessionToken,
8019
+ handoffUrl: handoffUrl,
7520
8020
  hid: hid,
8021
+ launchAr: launchAr,
8022
+ sizeType: sizeType,
7521
8023
  layout: layout,
7522
- background: background
8024
+ background: background,
8025
+ viewerSurface: viewerSurface
7523
8026
  })
7524
8027
  })
7525
8028
  })
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.3",
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.185.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@microsoft/api-extractor": "^7.58.0",