@thinkersteps_labs/3d-viewer-react 0.0.0-stage → 1.0.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.
package/README.md CHANGED
@@ -1,3 +1,107 @@
1
- # Temporary Holding Version
1
+ # @thinkersteps_labs/3d-viewer-react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Official React component wrapper for `@thinkersteps_labs/3d-viewer-core`. Renders photorealistic 3D jewelry viewers, swatches, and customizers in Next.js, Remix, Vite, and standard React applications.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @thinkersteps_labs/3d-viewer-react @thinkersteps_labs/3d-viewer-core three
11
+ # or with npm
12
+ npm install @thinkersteps_labs/3d-viewer-react @thinkersteps_labs/3d-viewer-core three
13
+ ```
14
+
15
+ ---
16
+
17
+ ## 1. Turnkey Customizer (5-Line Embed)
18
+
19
+ The `<JewelryCustomizer />` provides a complete, responsive 3D canvas and luxury swatch controls out of the box:
20
+
21
+ ```tsx
22
+ import { JewelryCustomizer } from '@thinkersteps_labs/3d-viewer-react'
23
+
24
+ export function ProductPage() {
25
+ return (
26
+ <JewelryCustomizer
27
+ src="https://cdn.example.com/models/solitaire-ring.glb"
28
+ defaultMetal="yellow-gold-18k"
29
+ defaultStone="diamond-d"
30
+ height={520}
31
+ autoRotate
32
+ onMetalChange={(metal) => console.log('Selected metal:', metal)}
33
+ onStoneChange={(stone) => console.log('Selected stone:', stone)}
34
+ />
35
+ )
36
+ }
37
+ ```
38
+
39
+ ---
40
+
41
+ ## 2. Composable Primitives (`<Viewer />` + Swatches)
42
+
43
+ Combine `<Viewer />` with pre-built `<MetalSwatches />` and `<StoneSwatches />` for custom layouts:
44
+
45
+ ```tsx
46
+ import { useState } from 'react'
47
+ import {
48
+ Viewer,
49
+ MetalSwatches,
50
+ StoneSwatches,
51
+ type MetalColorProp,
52
+ type StoneColorProp,
53
+ } from '@thinkersteps_labs/3d-viewer-react'
54
+
55
+ export function Customizer() {
56
+ const [metal, setMetal] = useState<MetalColorProp>('yellow-gold-18k')
57
+ const [stone, setStone] = useState<StoneColorProp>('diamond-d')
58
+
59
+ return (
60
+ <div style={{ maxWidth: '840px', margin: '0 auto' }}>
61
+ <Viewer
62
+ src="https://cdn.example.com/models/solitaire-ring.glb"
63
+ metalColor={metal}
64
+ stoneColor={stone}
65
+ height={500}
66
+ autoRotate
67
+ />
68
+
69
+ <div style={{ marginTop: '16px', display: 'flex', flexDirection: 'column', gap: '14px' }}>
70
+ <MetalSwatches value={metal} onChange={setMetal} variant="pills" />
71
+ <StoneSwatches value={stone} onChange={setStone} variant="pills" />
72
+ </div>
73
+ </div>
74
+ )
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 3. Headless Hook (`useJewelryCustomizer`)
81
+
82
+ For custom UI layouts with pre-wired state and preset lookup:
83
+
84
+ ```tsx
85
+ import { Viewer, useJewelryCustomizer } from '@thinkersteps_labs/3d-viewer-react'
86
+
87
+ export function HeadlessCustomizer() {
88
+ const { metal, setMetal, stone, setStone, metalOptions, stoneOptions, viewerProps } =
89
+ useJewelryCustomizer({
90
+ defaultMetal: 'yellow-gold-18k',
91
+ defaultStone: 'diamond-d',
92
+ })
93
+
94
+ return (
95
+ <div>
96
+ <Viewer src="/models/ring.glb" {...viewerProps} height={480} />
97
+ {/* Build your bespoke buttons, dropdowns, or swatches */}
98
+ </div>
99
+ )
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## License
106
+
107
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),u=require("react"),l=require("@thinkersteps_labs/3d-viewer-core"),k=u.forwardRef(function(i,c){const{src:N,source:P,metalColor:S,stoneColor:f,materialColors:m,partColors:b,background:r,metalEnvUrl:p,gemEnvUrl:t,options:j,fov:y,shadows:M,autoRotate:x,quality:v,height:T,minHeight:_,aspectRatio:L,className:I,style:$}=i,A=u.useRef(null),E=u.useRef(null),h=u.useRef(i);h.current=i,u.useImperativeHandle(c,()=>E.current,[]),u.useEffect(()=>{if(!A.current)return;const o=l.createViewer(A.current,{...j,...r!==void 0&&{background:r},...y!==void 0&&{fov:y},...M!==void 0&&{shadows:M},...x!==void 0&&{autoRotate:x},...v!==void 0&&{quality:v},...p!==void 0&&{metalEnvUrl:p},...t!==void 0&&{gemEnvUrl:t}});return E.current=o,c&&(typeof c=="function"?c(o):c.current=o),o.on("loaded",({parts:d,materials:s})=>{var n,g;return(g=(n=h.current).onLoaded)==null?void 0:g.call(n,d,s)}),o.on("partSelected",d=>{var s,n;return(n=(s=h.current).onPartSelected)==null?void 0:n.call(s,d)}),o.on("change",d=>{var s,n;return(n=(s=h.current).onChange)==null?void 0:n.call(s,d)}),o.on("error",d=>{var s,n;return(n=(s=h.current).onError)==null?void 0:n.call(s,d)}),o.on("loading",({progress:d})=>{var s,n;return(n=(s=h.current).onProgress)==null?void 0:n.call(s,d)}),()=>{o.dispose(),c&&(typeof c=="function"?c(null):c.current=null)}},[]),u.useEffect(()=>{if(!E.current)return;const o=l.normalizeModelSource(N??P);if(!o){E.current.unload();return}const d=new AbortController;return E.current.load(o,{signal:d.signal}),()=>d.abort()},[N,P]),u.useEffect(()=>{var o;S&&((o=E.current)==null||o.setMetalColor(S))},[S]),u.useEffect(()=>{var o;f&&((o=E.current)==null||o.setStoneColor(f))},[f]);const w=u.useRef({});u.useEffect(()=>{if(!E.current)return;const o=w.current,d=m??{};for(const s of Object.keys(o))s in d||E.current.clearMaterialColor(s);for(const[s,n]of Object.entries(d))o[s]!==n&&E.current.setMaterialColor(s,n);w.current={...d}},[m]);const O=u.useRef({});u.useEffect(()=>{if(!E.current)return;const o=O.current,d=b??{};for(const s of Object.keys(o))if(!(s in d))if(s.includes(":")){const[n,g]=s.split(":"),C=parseInt(g??"",10);E.current.clearPartColor(n??"",isNaN(C)?void 0:C)}else E.current.clearPartColor(s);for(const[s,n]of Object.entries(d))if(o[s]!==n)if(s.includes(":")){const[g,C]=s.split(":"),z=parseInt(C??"",10);E.current.setPartColor(g??"",n,isNaN(z)?void 0:z)}else E.current.setPartColor(s,n);O.current={...d}},[b]),u.useEffect(()=>{E.current&&(x?E.current.startRotation():E.current.stopRotation())},[x]),u.useEffect(()=>{var o;v&&((o=E.current)==null||o.setQuality(v))},[v]);const R={width:"100%",height:T!==void 0?typeof T=="number"?`${T}px`:T:"100%",minHeight:_!==void 0?typeof _=="number"?`${_}px`:_:void 0,aspectRatio:L,position:"relative",...$};return e.jsx("div",{ref:A,className:I,style:R})});function D({value:a,onChange:i,options:c=l.CANONICAL_METAL_PRESETS,category:N="all",variant:P="pills",showLabel:S=!0,className:f="",style:m}){const b=N==="all"?c:c.filter(t=>t.category===N),r=a?l.getMetalPreset(a):void 0,p=(r==null?void 0:r.id)??a;return e.jsxs("div",{className:`td-swatch-group ${f}`,style:m,children:[S&&e.jsxs("div",{className:"td-swatch-header",children:[e.jsx("span",{className:"td-swatch-title",children:"Metal"}),r&&e.jsx("span",{className:"td-swatch-selected-name",children:r.label})]}),e.jsx("div",{className:"td-swatch-row",role:"radiogroup","aria-label":"Metal Selection",children:b.map(t=>{const j=p===t.id||a===t.hex;return e.jsxs("button",{type:"button",role:"radio","aria-checked":j,title:t.label,className:`td-swatch-${P==="dots"?"dot":"pill"} ${j?"active":""}`,onClick:()=>i==null?void 0:i(t.id,t),children:[e.jsx("span",{className:"td-swatch-color-circle",style:{backgroundColor:t.hex}}),P==="pills"&&e.jsx("span",{children:t.label})]},t.id)})})]})}function F({value:a,onChange:i,options:c=l.CANONICAL_METAL_PRESETS,label:N="Select Metal",className:P="",style:S}){var b;const f=a?l.getMetalPreset(a):void 0,m=(f==null?void 0:f.id)??a??((b=c[0])==null?void 0:b.id);return e.jsxs("div",{className:`td-select-wrapper ${P}`,style:S,children:[N&&e.jsx("label",{className:"td-swatch-title",children:N}),e.jsx("select",{className:"td-select",value:m,onChange:r=>{const p=r.target.value,t=l.getMetalPreset(p);i==null||i(p,t)},children:c.map(r=>e.jsx("option",{value:r.id,children:r.label},r.id))})]})}function H({value:a,onChange:i,options:c=l.CANONICAL_STONE_PRESETS,category:N="all",variant:P="pills",showLabel:S=!0,className:f="",style:m}){const b=N==="all"?c:c.filter(t=>t.category===N),r=a?l.getStonePreset(a):void 0,p=(r==null?void 0:r.id)??a;return e.jsxs("div",{className:`td-swatch-group ${f}`,style:m,children:[S&&e.jsxs("div",{className:"td-swatch-header",children:[e.jsx("span",{className:"td-swatch-title",children:"Gemstone"}),r&&e.jsxs("span",{className:"td-swatch-selected-name",children:[r.label,r.grade?` (${r.grade})`:""]})]}),e.jsx("div",{className:"td-swatch-row",role:"radiogroup","aria-label":"Gemstone Selection",children:b.map(t=>{const j=p===t.id||a===t.hex;return e.jsxs("button",{type:"button",role:"radio","aria-checked":j,title:`${t.label}${t.grade?` - ${t.grade}`:""}`,className:`td-swatch-${P==="dots"?"dot":"pill"} ${j?"active":""}`,onClick:()=>i==null?void 0:i(t.id,t),children:[e.jsx("span",{className:"td-swatch-color-circle",style:{backgroundColor:t.hex}}),P==="pills"&&e.jsx("span",{children:t.label})]},t.id)})})]})}function G({value:a,onChange:i,options:c=l.CANONICAL_STONE_PRESETS,label:N="Select Gemstone",className:P="",style:S}){var b;const f=a?l.getStonePreset(a):void 0,m=(f==null?void 0:f.id)??a??((b=c[0])==null?void 0:b.id);return e.jsxs("div",{className:`td-select-wrapper ${P}`,style:S,children:[N&&e.jsx("label",{className:"td-swatch-title",children:N}),e.jsx("select",{className:"td-select",value:m,onChange:r=>{const p=r.target.value,t=l.getStonePreset(p);i==null||i(p,t)},children:c.map(r=>e.jsxs("option",{value:r.id,children:[r.label," ",r.grade?`(${r.grade})`:""]},r.id))})]})}function q({src:a,source:i,defaultMetal:c="yellow-gold-18k",defaultStone:N="diamond-d",metal:P,onMetalChange:S,stone:f,onStoneChange:m,metalOptions:b,stoneOptions:r,controlsVariant:p="pills",height:t=520,autoRotate:j=!0,quality:y="high",fov:M,shadows:x,onLoaded:v,onPartSelected:T,className:_="",style:L,viewerRef:I}){const[$,A]=u.useState(c),[E,h]=u.useState(N),w=P!==void 0?P:$,O=f!==void 0?f:E,R=(s,n)=>{P===void 0&&A(s),S==null||S(s,n)},o=(s,n)=>{f===void 0&&h(s),m==null||m(s,n)},d=typeof t=="number"?`${t}px`:t;return e.jsxs("div",{className:`td-customizer-card ${_}`,style:L,children:[e.jsx("div",{className:"td-customizer-viewport",style:{height:d},children:e.jsx(k,{ref:I,src:a,source:i,metalColor:w,stoneColor:O,autoRotate:j,quality:y,fov:M,shadows:x,onLoaded:v,onPartSelected:T})}),p!=="none"&&e.jsx("div",{className:"td-customizer-controls",children:p==="select"?e.jsxs(e.Fragment,{children:[e.jsx(F,{value:w,onChange:R,options:b}),e.jsx(G,{value:O,onChange:o,options:r})]}):e.jsxs(e.Fragment,{children:[e.jsx(D,{value:w,onChange:R,options:b,variant:p}),e.jsx(H,{value:O,onChange:o,options:r,variant:p})]})})]})}function J(a={}){const{defaultMetal:i="yellow-gold-18k",defaultStone:c="diamond-d",metalOptions:N=l.CANONICAL_METAL_PRESETS,stoneOptions:P=l.CANONICAL_STONE_PRESETS}=a,[S,f]=u.useState(i),[m,b]=u.useState(c),r=u.useMemo(()=>l.getMetalPreset(S),[S]),p=u.useMemo(()=>l.getStonePreset(m),[m]),t=u.useMemo(()=>({metalColor:S,stoneColor:m}),[S,m]);return{metal:S,setMetal:f,stone:m,setStone:b,metalPreset:r,stonePreset:p,metalOptions:N,stoneOptions:P,viewerProps:t}}Object.defineProperty(exports,"ALL_METAL_PRESETS",{enumerable:!0,get:()=>l.ALL_METAL_PRESETS});Object.defineProperty(exports,"ALL_STONE_PRESETS",{enumerable:!0,get:()=>l.ALL_STONE_PRESETS});Object.defineProperty(exports,"CANONICAL_METAL_PRESETS",{enumerable:!0,get:()=>l.CANONICAL_METAL_PRESETS});Object.defineProperty(exports,"CANONICAL_STONE_PRESETS",{enumerable:!0,get:()=>l.CANONICAL_STONE_PRESETS});Object.defineProperty(exports,"DIAMOND_PRESETS",{enumerable:!0,get:()=>l.DIAMOND_PRESETS});Object.defineProperty(exports,"GEMSTONE_PRESETS",{enumerable:!0,get:()=>l.GEMSTONE_PRESETS});Object.defineProperty(exports,"METAL_PRESETS",{enumerable:!0,get:()=>l.METAL_PRESETS});Object.defineProperty(exports,"PHYSICALLY_BASED_METAL_PRESETS",{enumerable:!0,get:()=>l.PHYSICALLY_BASED_METAL_PRESETS});Object.defineProperty(exports,"STONE_PRESETS",{enumerable:!0,get:()=>l.STONE_PRESETS});Object.defineProperty(exports,"getMetalPreset",{enumerable:!0,get:()=>l.getMetalPreset});Object.defineProperty(exports,"getStonePreset",{enumerable:!0,get:()=>l.getStonePreset});Object.defineProperty(exports,"inferFormatFromUrl",{enumerable:!0,get:()=>l.inferFormatFromUrl});Object.defineProperty(exports,"normalizeModelSource",{enumerable:!0,get:()=>l.normalizeModelSource});exports.JewelryCustomizer=q;exports.MetalSelect=F;exports.MetalSwatches=D;exports.StoneSelect=G;exports.StoneSwatches=H;exports.Viewer=k;exports.default=k;exports.useJewelryCustomizer=J;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/Viewer.tsx","../src/swatches/MetalSwatches.tsx","../src/swatches/StoneSwatches.tsx","../src/swatches/JewelryCustomizer.tsx","../src/swatches/useJewelryCustomizer.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\nimport { useRef, useEffect, forwardRef, useImperativeHandle, type CSSProperties } from 'react'\nimport {\n createViewer,\n normalizeModelSource,\n type Viewer as CoreViewer,\n type ModelSource,\n type ViewerSourceInput,\n type MetalColorProp,\n type StoneColorProp,\n type ViewerOptions,\n type ViewerState,\n type PartInfo,\n type MaterialSlotInfo,\n type PartType,\n} from '@thinkersteps_labs/3d-viewer-core'\n\nexport interface ViewerRef extends CoreViewer { }\n\nexport interface ViewerProps {\n /** 3D model URL or source object. Takes precedence over `source` if both provided. */\n src?: string\n /** 3D model source (URL string, ModelSource payload, or null). */\n source?: ViewerSourceInput\n /** Metal preset ID or hex code. IDE autocompletes standard presets. */\n metalColor?: MetalColorProp\n /** Stone preset ID or hex code. IDE autocompletes standard presets. */\n stoneColor?: StoneColorProp\n /** Bulk material name or slot ID color overrides. */\n materialColors?: Record<string, string>\n /** Part color overrides keyed by partId or partId:slotIndex. */\n partColors?: Record<string, string>\n background?: string | number\n metalEnvUrl?: string | ArrayBuffer\n gemEnvUrl?: string | ArrayBuffer\n options?: ViewerOptions\n fov?: number\n shadows?: boolean\n autoRotate?: boolean\n quality?: 'low' | 'medium' | 'high'\n /** Explicit container height (e.g. 520 or '520px'). Prevents zero-height collapse. */\n height?: number | string\n /** Explicit container min-height. */\n minHeight?: number | string\n /** Aspect ratio for responsive sizing (e.g. '16/9' or '4/3'). */\n aspectRatio?: string\n onLoaded?: (parts: PartInfo[], materials?: MaterialSlotInfo[]) => void\n onPartSelected?: (info: {\n meshId: string\n partId?: string\n type: PartType\n slotIndex?: number\n slotId?: string\n materialName?: string\n }) => void\n onChange?: (state: ViewerState) => void\n onError?: (err: Error) => void\n onProgress?: (progress: number) => void\n className?: string\n style?: CSSProperties\n}\n\nexport const Viewer = forwardRef<ViewerRef, ViewerProps>(function Viewer(props, ref) {\n const {\n src,\n source,\n metalColor,\n stoneColor,\n materialColors,\n partColors,\n background,\n metalEnvUrl,\n gemEnvUrl,\n options,\n fov,\n shadows,\n autoRotate,\n quality,\n height,\n minHeight,\n aspectRatio,\n className,\n style,\n } = props\n const containerRef = useRef<HTMLDivElement>(null)\n const viewerRef = useRef<ViewerRef | null>(null)\n const cb = useRef(props)\n cb.current = props\n\n useImperativeHandle(ref, () => viewerRef.current!, [])\n\n useEffect(() => {\n if (!containerRef.current) return\n const viewer = createViewer(containerRef.current, {\n ...options,\n ...(background !== undefined && { background }),\n ...(fov !== undefined && { fov }),\n ...(shadows !== undefined && { shadows }),\n ...(autoRotate !== undefined && { autoRotate }),\n ...(quality !== undefined && { quality }),\n ...(metalEnvUrl !== undefined && { metalEnvUrl }),\n ...(gemEnvUrl !== undefined && { gemEnvUrl }),\n })\n viewerRef.current = viewer\n if (import.meta.env.DEV && typeof window !== 'undefined') (window as any).__viewer = viewer\n if (ref) {\n if (typeof ref === 'function') ref(viewer)\n else (ref as any).current = viewer\n }\n viewer.on('loaded', ({ parts, materials }) => cb.current.onLoaded?.(parts, materials))\n viewer.on('partSelected', (info) => cb.current.onPartSelected?.(info))\n viewer.on('change', (state) => cb.current.onChange?.(state))\n viewer.on('error', (err) => cb.current.onError?.(err))\n viewer.on('loading', ({ progress }) => cb.current.onProgress?.(progress))\n return () => {\n viewer.dispose()\n if (ref) {\n if (typeof ref === 'function') ref(null)\n else (ref as any).current = null\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n useEffect(() => {\n if (!viewerRef.current) return\n const resolved = normalizeModelSource(src ?? source)\n if (!resolved) {\n viewerRef.current.unload()\n return\n }\n const ac = new AbortController()\n viewerRef.current.load(resolved, { signal: ac.signal })\n return () => ac.abort()\n }, [src, source])\n\n useEffect(() => {\n if (metalColor) viewerRef.current?.setMetalColor(metalColor)\n }, [metalColor])\n\n useEffect(() => {\n if (stoneColor) viewerRef.current?.setStoneColor(stoneColor)\n }, [stoneColor])\n\n const prevMaterialColorsRef = useRef<Record<string, string>>({})\n useEffect(() => {\n if (!viewerRef.current) return\n const prev = prevMaterialColorsRef.current\n const next = materialColors ?? {}\n\n for (const key of Object.keys(prev)) {\n if (!(key in next)) {\n viewerRef.current.clearMaterialColor(key)\n }\n }\n for (const [key, color] of Object.entries(next)) {\n if (prev[key] !== color) {\n viewerRef.current.setMaterialColor(key, color)\n }\n }\n prevMaterialColorsRef.current = { ...next }\n }, [materialColors])\n\n const prevPartColorsRef = useRef<Record<string, string>>({})\n useEffect(() => {\n if (!viewerRef.current) return\n const prev = prevPartColorsRef.current\n const next = partColors ?? {}\n\n for (const key of Object.keys(prev)) {\n if (!(key in next)) {\n if (key.includes(':')) {\n const [partId, slotIdxStr] = key.split(':')\n const slotIndex = parseInt(slotIdxStr ?? '', 10)\n viewerRef.current.clearPartColor(partId ?? '', isNaN(slotIndex) ? undefined : slotIndex)\n } else {\n viewerRef.current.clearPartColor(key)\n }\n }\n }\n for (const [key, color] of Object.entries(next)) {\n if (prev[key] !== color) {\n if (key.includes(':')) {\n const [partId, slotIdxStr] = key.split(':')\n const slotIndex = parseInt(slotIdxStr ?? '', 10)\n viewerRef.current.setPartColor(partId ?? '', color, isNaN(slotIndex) ? undefined : slotIndex)\n } else {\n viewerRef.current.setPartColor(key, color)\n }\n }\n }\n prevPartColorsRef.current = { ...next }\n }, [partColors])\n\n useEffect(() => {\n if (!viewerRef.current) return\n if (autoRotate) viewerRef.current.startRotation()\n else viewerRef.current.stopRotation()\n }, [autoRotate])\n\n useEffect(() => {\n if (quality) viewerRef.current?.setQuality(quality)\n }, [quality])\n\n const containerStyle: CSSProperties = {\n width: '100%',\n height: height !== undefined ? (typeof height === 'number' ? `${height}px` : height) : '100%',\n minHeight: minHeight !== undefined ? (typeof minHeight === 'number' ? `${minHeight}px` : minHeight) : undefined,\n aspectRatio,\n position: 'relative',\n ...style,\n }\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n />\n )\n})\n\nexport default Viewer\n","import React, { type CSSProperties } from 'react'\nimport {\n CANONICAL_METAL_PRESETS,\n getMetalPreset,\n type MetalPreset,\n type MetalColorProp,\n} from '@thinkersteps_labs/3d-viewer-core'\nimport './swatches.css'\n\nexport interface MetalSwatchesProps {\n value?: MetalColorProp\n onChange?: (metalId: string, preset?: MetalPreset) => void\n options?: MetalPreset[]\n category?: 'all' | 'gold' | 'platinum-silver' | 'alternative'\n variant?: 'pills' | 'dots'\n showLabel?: boolean\n className?: string\n style?: CSSProperties\n}\n\nexport function MetalSwatches({\n value,\n onChange,\n options = CANONICAL_METAL_PRESETS,\n category = 'all',\n variant = 'pills',\n showLabel = true,\n className = '',\n style,\n}: MetalSwatchesProps) {\n const filteredOptions = category === 'all'\n ? options\n : options.filter((m) => m.category === category)\n\n const activePreset = value ? getMetalPreset(value) : undefined\n const activeId = activePreset?.id ?? value\n\n return (\n <div className={`td-swatch-group ${className}`} style={style}>\n {showLabel && (\n <div className=\"td-swatch-header\">\n <span className=\"td-swatch-title\">Metal</span>\n {activePreset && (\n <span className=\"td-swatch-selected-name\">{activePreset.label}</span>\n )}\n </div>\n )}\n <div className=\"td-swatch-row\" role=\"radiogroup\" aria-label=\"Metal Selection\">\n {filteredOptions.map((m) => {\n const isSelected = activeId === m.id || value === m.hex\n return (\n <button\n key={m.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n title={m.label}\n className={`td-swatch-${variant === 'dots' ? 'dot' : 'pill'} ${isSelected ? 'active' : ''}`}\n onClick={() => onChange?.(m.id, m)}\n >\n <span\n className=\"td-swatch-color-circle\"\n style={{ backgroundColor: m.hex }}\n />\n {variant === 'pills' && <span>{m.label}</span>}\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport interface MetalSelectProps {\n value?: MetalColorProp\n onChange?: (metalId: string, preset?: MetalPreset) => void\n options?: MetalPreset[]\n label?: string\n className?: string\n style?: CSSProperties\n}\n\nexport function MetalSelect({\n value,\n onChange,\n options = CANONICAL_METAL_PRESETS,\n label = 'Select Metal',\n className = '',\n style,\n}: MetalSelectProps) {\n const activePreset = value ? getMetalPreset(value) : undefined\n const activeValue = activePreset?.id ?? value ?? options[0]?.id\n\n return (\n <div className={`td-select-wrapper ${className}`} style={style}>\n {label && <label className=\"td-swatch-title\">{label}</label>}\n <select\n className=\"td-select\"\n value={activeValue}\n onChange={(e) => {\n const val = e.target.value\n const preset = getMetalPreset(val)\n onChange?.(val, preset)\n }}\n >\n {options.map((m) => (\n <option key={m.id} value={m.id}>\n {m.label}\n </option>\n ))}\n </select>\n </div>\n )\n}\n","import React, { type CSSProperties } from 'react'\nimport {\n CANONICAL_STONE_PRESETS,\n getStonePreset,\n type StonePreset,\n type StoneColorProp,\n} from '@thinkersteps_labs/3d-viewer-core'\nimport './swatches.css'\n\nexport interface StoneSwatchesProps {\n value?: StoneColorProp\n onChange?: (stoneId: string, preset?: StonePreset) => void\n options?: StonePreset[]\n category?: 'all' | 'diamond' | 'gemstone'\n variant?: 'pills' | 'dots'\n showLabel?: boolean\n className?: string\n style?: CSSProperties\n}\n\nexport function StoneSwatches({\n value,\n onChange,\n options = CANONICAL_STONE_PRESETS,\n category = 'all',\n variant = 'pills',\n showLabel = true,\n className = '',\n style,\n}: StoneSwatchesProps) {\n const filteredOptions = category === 'all'\n ? options\n : options.filter((s) => s.category === category)\n\n const activePreset = value ? getStonePreset(value) : undefined\n const activeId = activePreset?.id ?? value\n\n return (\n <div className={`td-swatch-group ${className}`} style={style}>\n {showLabel && (\n <div className=\"td-swatch-header\">\n <span className=\"td-swatch-title\">Gemstone</span>\n {activePreset && (\n <span className=\"td-swatch-selected-name\">\n {activePreset.label}\n {activePreset.grade ? ` (${activePreset.grade})` : ''}\n </span>\n )}\n </div>\n )}\n <div className=\"td-swatch-row\" role=\"radiogroup\" aria-label=\"Gemstone Selection\">\n {filteredOptions.map((s) => {\n const isSelected = activeId === s.id || value === s.hex\n return (\n <button\n key={s.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n title={`${s.label}${s.grade ? ` - ${s.grade}` : ''}`}\n className={`td-swatch-${variant === 'dots' ? 'dot' : 'pill'} ${isSelected ? 'active' : ''}`}\n onClick={() => onChange?.(s.id, s)}\n >\n <span\n className=\"td-swatch-color-circle\"\n style={{ backgroundColor: s.hex }}\n />\n {variant === 'pills' && <span>{s.label}</span>}\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport interface StoneSelectProps {\n value?: StoneColorProp\n onChange?: (stoneId: string, preset?: StonePreset) => void\n options?: StonePreset[]\n label?: string\n className?: string\n style?: CSSProperties\n}\n\nexport function StoneSelect({\n value,\n onChange,\n options = CANONICAL_STONE_PRESETS,\n label = 'Select Gemstone',\n className = '',\n style,\n}: StoneSelectProps) {\n const activePreset = value ? getStonePreset(value) : undefined\n const activeValue = activePreset?.id ?? value ?? options[0]?.id\n\n return (\n <div className={`td-select-wrapper ${className}`} style={style}>\n {label && <label className=\"td-swatch-title\">{label}</label>}\n <select\n className=\"td-select\"\n value={activeValue}\n onChange={(e) => {\n const val = e.target.value\n const preset = getStonePreset(val)\n onChange?.(val, preset)\n }}\n >\n {options.map((s) => (\n <option key={s.id} value={s.id}>\n {s.label} {s.grade ? `(${s.grade})` : ''}\n </option>\n ))}\n </select>\n </div>\n )\n}\n","import React, { useState, type CSSProperties } from 'react'\nimport {\n type MetalColorProp,\n type StoneColorProp,\n type ViewerSourceInput,\n type MetalPreset,\n type StonePreset,\n type PartInfo,\n type PartType,\n} from '@thinkersteps_labs/3d-viewer-core'\nimport { Viewer, type ViewerRef } from '../Viewer'\nimport { MetalSwatches, MetalSelect } from './MetalSwatches'\nimport { StoneSwatches, StoneSelect } from './StoneSwatches'\nimport './swatches.css'\n\nexport interface JewelryCustomizerProps {\n src?: string\n source?: ViewerSourceInput\n defaultMetal?: MetalColorProp\n defaultStone?: StoneColorProp\n metal?: MetalColorProp\n onMetalChange?: (metal: string, preset?: MetalPreset) => void\n stone?: StoneColorProp\n onStoneChange?: (stone: string, preset?: StonePreset) => void\n metalOptions?: MetalPreset[]\n stoneOptions?: StonePreset[]\n controlsVariant?: 'pills' | 'dots' | 'select' | 'none'\n height?: number | string\n autoRotate?: boolean\n quality?: 'low' | 'medium' | 'high'\n fov?: number\n shadows?: boolean\n onLoaded?: (parts: PartInfo[]) => void\n onPartSelected?: (info: { meshId: string; type: PartType }) => void\n className?: string\n style?: CSSProperties\n viewerRef?: React.Ref<ViewerRef>\n}\n\nexport function JewelryCustomizer({\n src,\n source,\n defaultMetal = 'yellow-gold-18k',\n defaultStone = 'diamond-d',\n metal: controlledMetal,\n onMetalChange,\n stone: controlledStone,\n onStoneChange,\n metalOptions,\n stoneOptions,\n controlsVariant = 'pills',\n height = 520,\n autoRotate = true,\n quality = 'high',\n fov,\n shadows,\n onLoaded,\n onPartSelected,\n className = '',\n style,\n viewerRef,\n}: JewelryCustomizerProps) {\n const [internalMetal, setInternalMetal] = useState<MetalColorProp>(defaultMetal)\n const [internalStone, setInternalStone] = useState<StoneColorProp>(defaultStone)\n\n const currentMetal = controlledMetal !== undefined ? controlledMetal : internalMetal\n const currentStone = controlledStone !== undefined ? controlledStone : internalStone\n\n const handleMetalChange = (val: string, preset?: MetalPreset) => {\n if (controlledMetal === undefined) setInternalMetal(val)\n onMetalChange?.(val, preset)\n }\n\n const handleStoneChange = (val: string, preset?: StonePreset) => {\n if (controlledStone === undefined) setInternalStone(val)\n onStoneChange?.(val, preset)\n }\n\n const viewportHeight = typeof height === 'number' ? `${height}px` : height\n\n return (\n <div className={`td-customizer-card ${className}`} style={style}>\n <div className=\"td-customizer-viewport\" style={{ height: viewportHeight }}>\n <Viewer\n ref={viewerRef}\n src={src}\n source={source}\n metalColor={currentMetal}\n stoneColor={currentStone}\n autoRotate={autoRotate}\n quality={quality}\n fov={fov}\n shadows={shadows}\n onLoaded={onLoaded}\n onPartSelected={onPartSelected}\n />\n </div>\n\n {controlsVariant !== 'none' && (\n <div className=\"td-customizer-controls\">\n {controlsVariant === 'select' ? (\n <>\n <MetalSelect\n value={currentMetal}\n onChange={handleMetalChange}\n options={metalOptions}\n />\n <StoneSelect\n value={currentStone}\n onChange={handleStoneChange}\n options={stoneOptions}\n />\n </>\n ) : (\n <>\n <MetalSwatches\n value={currentMetal}\n onChange={handleMetalChange}\n options={metalOptions}\n variant={controlsVariant}\n />\n <StoneSwatches\n value={currentStone}\n onChange={handleStoneChange}\n options={stoneOptions}\n variant={controlsVariant}\n />\n </>\n )}\n </div>\n )}\n </div>\n )\n}\n","import { useState, useMemo } from 'react'\nimport {\n CANONICAL_METAL_PRESETS,\n CANONICAL_STONE_PRESETS,\n getMetalPreset,\n getStonePreset,\n type MetalColorProp,\n type StoneColorProp,\n type MetalPreset,\n type StonePreset,\n} from '@thinkersteps_labs/3d-viewer-core'\n\nexport interface UseJewelryCustomizerOptions {\n defaultMetal?: MetalColorProp\n defaultStone?: StoneColorProp\n metalOptions?: MetalPreset[]\n stoneOptions?: StonePreset[]\n}\n\nexport interface UseJewelryCustomizerReturn {\n metal: MetalColorProp\n setMetal: (val: MetalColorProp) => void\n stone: StoneColorProp\n setStone: (val: StoneColorProp) => void\n metalPreset?: MetalPreset\n stonePreset?: StonePreset\n metalOptions: MetalPreset[]\n stoneOptions: StonePreset[]\n viewerProps: {\n metalColor: MetalColorProp\n stoneColor: StoneColorProp\n }\n}\n\nexport function useJewelryCustomizer(\n options: UseJewelryCustomizerOptions = {}\n): UseJewelryCustomizerReturn {\n const {\n defaultMetal = 'yellow-gold-18k',\n defaultStone = 'diamond-d',\n metalOptions = CANONICAL_METAL_PRESETS,\n stoneOptions = CANONICAL_STONE_PRESETS,\n } = options\n\n const [metal, setMetal] = useState<MetalColorProp>(defaultMetal)\n const [stone, setStone] = useState<StoneColorProp>(defaultStone)\n\n const metalPreset = useMemo(() => getMetalPreset(metal), [metal])\n const stonePreset = useMemo(() => getStonePreset(stone), [stone])\n\n const viewerProps = useMemo(\n () => ({\n metalColor: metal,\n stoneColor: stone,\n }),\n [metal, stone]\n )\n\n return {\n metal,\n setMetal,\n stone,\n setStone,\n metalPreset,\n stonePreset,\n metalOptions,\n stoneOptions,\n viewerProps,\n }\n}\n"],"names":["Viewer","forwardRef","props","ref","src","source","metalColor","stoneColor","materialColors","partColors","background","metalEnvUrl","gemEnvUrl","options","fov","shadows","autoRotate","quality","height","minHeight","aspectRatio","className","style","containerRef","useRef","viewerRef","cb","useImperativeHandle","useEffect","viewer","createViewer","parts","materials","_b","_a","info","state","err","progress","resolved","normalizeModelSource","ac","prevMaterialColorsRef","prev","next","key","color","prevPartColorsRef","partId","slotIdxStr","slotIndex","containerStyle","jsx","MetalSwatches","value","onChange","CANONICAL_METAL_PRESETS","category","variant","showLabel","filteredOptions","m","activePreset","getMetalPreset","activeId","jsxs","isSelected","MetalSelect","label","activeValue","e","val","preset","StoneSwatches","CANONICAL_STONE_PRESETS","s","getStonePreset","StoneSelect","JewelryCustomizer","defaultMetal","defaultStone","controlledMetal","onMetalChange","controlledStone","onStoneChange","metalOptions","stoneOptions","controlsVariant","onLoaded","onPartSelected","internalMetal","setInternalMetal","useState","internalStone","setInternalStone","currentMetal","currentStone","handleMetalChange","handleStoneChange","viewportHeight","Fragment","useJewelryCustomizer","metal","setMetal","stone","setStone","metalPreset","useMemo","stonePreset","viewerProps"],"mappings":"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"}
@@ -0,0 +1,247 @@
1
+ import { ALL_METAL_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
2
+ import { ALL_STONE_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
3
+ import { CANONICAL_METAL_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
4
+ import { CANONICAL_STONE_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
5
+ import { Viewer as CoreViewer } from '@thinkersteps_labs/3d-viewer-core';
6
+ import { CSSProperties } from 'react';
7
+ import { default as default_2 } from 'react';
8
+ import { DIAMOND_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
9
+ import { DiamondQuality } from '@thinkersteps_labs/3d-viewer-core';
10
+ import { ForwardRefExoticComponent } from 'react';
11
+ import { GEMSTONE_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
12
+ import { getMetalPreset } from '@thinkersteps_labs/3d-viewer-core';
13
+ import { getStonePreset } from '@thinkersteps_labs/3d-viewer-core';
14
+ import { inferFormatFromUrl } from '@thinkersteps_labs/3d-viewer-core';
15
+ import { MaterialSlotInfo } from '@thinkersteps_labs/3d-viewer-core';
16
+ import { MeshMaterialSlot } from '@thinkersteps_labs/3d-viewer-core';
17
+ import { METAL_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
18
+ import { MetalColorProp } from '@thinkersteps_labs/3d-viewer-core';
19
+ import { MetalPreset } from '@thinkersteps_labs/3d-viewer-core';
20
+ import { ModelSource } from '@thinkersteps_labs/3d-viewer-core';
21
+ import { normalizeModelSource } from '@thinkersteps_labs/3d-viewer-core';
22
+ import { PartInfo } from '@thinkersteps_labs/3d-viewer-core';
23
+ import { PartType } from '@thinkersteps_labs/3d-viewer-core';
24
+ import { PHYSICALLY_BASED_METAL_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
25
+ import { RefAttributes } from 'react';
26
+ import { StandardMetalId } from '@thinkersteps_labs/3d-viewer-core';
27
+ import { StandardStoneId } from '@thinkersteps_labs/3d-viewer-core';
28
+ import { StateRestoreResult } from '@thinkersteps_labs/3d-viewer-core';
29
+ import { STONE_PRESETS } from '@thinkersteps_labs/3d-viewer-core';
30
+ import { StoneColorProp } from '@thinkersteps_labs/3d-viewer-core';
31
+ import { StonePreset } from '@thinkersteps_labs/3d-viewer-core';
32
+ import { ViewerOptions } from '@thinkersteps_labs/3d-viewer-core';
33
+ import { ViewerSourceInput } from '@thinkersteps_labs/3d-viewer-core';
34
+ import { ViewerState } from '@thinkersteps_labs/3d-viewer-core';
35
+
36
+ export { ALL_METAL_PRESETS }
37
+
38
+ export { ALL_STONE_PRESETS }
39
+
40
+ export { CANONICAL_METAL_PRESETS }
41
+
42
+ export { CANONICAL_STONE_PRESETS }
43
+
44
+ export { CoreViewer }
45
+
46
+ export { DIAMOND_PRESETS }
47
+
48
+ export { DiamondQuality }
49
+
50
+ export { GEMSTONE_PRESETS }
51
+
52
+ export { getMetalPreset }
53
+
54
+ export { getStonePreset }
55
+
56
+ export { inferFormatFromUrl }
57
+
58
+ export declare function JewelryCustomizer({ src, source, defaultMetal, defaultStone, metal: controlledMetal, onMetalChange, stone: controlledStone, onStoneChange, metalOptions, stoneOptions, controlsVariant, height, autoRotate, quality, fov, shadows, onLoaded, onPartSelected, className, style, viewerRef, }: JewelryCustomizerProps): default_2.JSX.Element;
59
+
60
+ export declare interface JewelryCustomizerProps {
61
+ src?: string;
62
+ source?: ViewerSourceInput;
63
+ defaultMetal?: MetalColorProp;
64
+ defaultStone?: StoneColorProp;
65
+ metal?: MetalColorProp;
66
+ onMetalChange?: (metal: string, preset?: MetalPreset) => void;
67
+ stone?: StoneColorProp;
68
+ onStoneChange?: (stone: string, preset?: StonePreset) => void;
69
+ metalOptions?: MetalPreset[];
70
+ stoneOptions?: StonePreset[];
71
+ controlsVariant?: 'pills' | 'dots' | 'select' | 'none';
72
+ height?: number | string;
73
+ autoRotate?: boolean;
74
+ quality?: 'low' | 'medium' | 'high';
75
+ fov?: number;
76
+ shadows?: boolean;
77
+ onLoaded?: (parts: PartInfo[]) => void;
78
+ onPartSelected?: (info: {
79
+ meshId: string;
80
+ type: PartType;
81
+ }) => void;
82
+ className?: string;
83
+ style?: CSSProperties;
84
+ viewerRef?: default_2.Ref<ViewerRef>;
85
+ }
86
+
87
+ export { MaterialSlotInfo }
88
+
89
+ export { MeshMaterialSlot }
90
+
91
+ export { METAL_PRESETS }
92
+
93
+ export { MetalColorProp }
94
+
95
+ export { MetalPreset }
96
+
97
+ export declare function MetalSelect({ value, onChange, options, label, className, style, }: MetalSelectProps): default_2.JSX.Element;
98
+
99
+ export declare interface MetalSelectProps {
100
+ value?: MetalColorProp;
101
+ onChange?: (metalId: string, preset?: MetalPreset) => void;
102
+ options?: MetalPreset[];
103
+ label?: string;
104
+ className?: string;
105
+ style?: CSSProperties;
106
+ }
107
+
108
+ export declare function MetalSwatches({ value, onChange, options, category, variant, showLabel, className, style, }: MetalSwatchesProps): default_2.JSX.Element;
109
+
110
+ export declare interface MetalSwatchesProps {
111
+ value?: MetalColorProp;
112
+ onChange?: (metalId: string, preset?: MetalPreset) => void;
113
+ options?: MetalPreset[];
114
+ category?: 'all' | 'gold' | 'platinum-silver' | 'alternative';
115
+ variant?: 'pills' | 'dots';
116
+ showLabel?: boolean;
117
+ className?: string;
118
+ style?: CSSProperties;
119
+ }
120
+
121
+ export { ModelSource }
122
+
123
+ export { normalizeModelSource }
124
+
125
+ export { PartInfo }
126
+
127
+ export { PartType }
128
+
129
+ export { PHYSICALLY_BASED_METAL_PRESETS }
130
+
131
+ export { StandardMetalId }
132
+
133
+ export { StandardStoneId }
134
+
135
+ export { StateRestoreResult }
136
+
137
+ export { STONE_PRESETS }
138
+
139
+ export { StoneColorProp }
140
+
141
+ export { StonePreset }
142
+
143
+ export declare function StoneSelect({ value, onChange, options, label, className, style, }: StoneSelectProps): default_2.JSX.Element;
144
+
145
+ export declare interface StoneSelectProps {
146
+ value?: StoneColorProp;
147
+ onChange?: (stoneId: string, preset?: StonePreset) => void;
148
+ options?: StonePreset[];
149
+ label?: string;
150
+ className?: string;
151
+ style?: CSSProperties;
152
+ }
153
+
154
+ export declare function StoneSwatches({ value, onChange, options, category, variant, showLabel, className, style, }: StoneSwatchesProps): default_2.JSX.Element;
155
+
156
+ export declare interface StoneSwatchesProps {
157
+ value?: StoneColorProp;
158
+ onChange?: (stoneId: string, preset?: StonePreset) => void;
159
+ options?: StonePreset[];
160
+ category?: 'all' | 'diamond' | 'gemstone';
161
+ variant?: 'pills' | 'dots';
162
+ showLabel?: boolean;
163
+ className?: string;
164
+ style?: CSSProperties;
165
+ }
166
+
167
+ export declare function useJewelryCustomizer(options?: UseJewelryCustomizerOptions): UseJewelryCustomizerReturn;
168
+
169
+ export declare interface UseJewelryCustomizerOptions {
170
+ defaultMetal?: MetalColorProp;
171
+ defaultStone?: StoneColorProp;
172
+ metalOptions?: MetalPreset[];
173
+ stoneOptions?: StonePreset[];
174
+ }
175
+
176
+ export declare interface UseJewelryCustomizerReturn {
177
+ metal: MetalColorProp;
178
+ setMetal: (val: MetalColorProp) => void;
179
+ stone: StoneColorProp;
180
+ setStone: (val: StoneColorProp) => void;
181
+ metalPreset?: MetalPreset;
182
+ stonePreset?: StonePreset;
183
+ metalOptions: MetalPreset[];
184
+ stoneOptions: StonePreset[];
185
+ viewerProps: {
186
+ metalColor: MetalColorProp;
187
+ stoneColor: StoneColorProp;
188
+ };
189
+ }
190
+
191
+ declare const Viewer: ForwardRefExoticComponent<ViewerProps & RefAttributes<ViewerRef>>;
192
+ export { Viewer }
193
+ export default Viewer;
194
+
195
+ export { ViewerOptions }
196
+
197
+ export declare interface ViewerProps {
198
+ /** 3D model URL or source object. Takes precedence over `source` if both provided. */
199
+ src?: string;
200
+ /** 3D model source (URL string, ModelSource payload, or null). */
201
+ source?: ViewerSourceInput;
202
+ /** Metal preset ID or hex code. IDE autocompletes standard presets. */
203
+ metalColor?: MetalColorProp;
204
+ /** Stone preset ID or hex code. IDE autocompletes standard presets. */
205
+ stoneColor?: StoneColorProp;
206
+ /** Bulk material name or slot ID color overrides. */
207
+ materialColors?: Record<string, string>;
208
+ /** Part color overrides keyed by partId or partId:slotIndex. */
209
+ partColors?: Record<string, string>;
210
+ background?: string | number;
211
+ metalEnvUrl?: string | ArrayBuffer;
212
+ gemEnvUrl?: string | ArrayBuffer;
213
+ options?: ViewerOptions;
214
+ fov?: number;
215
+ shadows?: boolean;
216
+ autoRotate?: boolean;
217
+ quality?: 'low' | 'medium' | 'high';
218
+ /** Explicit container height (e.g. 520 or '520px'). Prevents zero-height collapse. */
219
+ height?: number | string;
220
+ /** Explicit container min-height. */
221
+ minHeight?: number | string;
222
+ /** Aspect ratio for responsive sizing (e.g. '16/9' or '4/3'). */
223
+ aspectRatio?: string;
224
+ onLoaded?: (parts: PartInfo[], materials?: MaterialSlotInfo[]) => void;
225
+ onPartSelected?: (info: {
226
+ meshId: string;
227
+ partId?: string;
228
+ type: PartType;
229
+ slotIndex?: number;
230
+ slotId?: string;
231
+ materialName?: string;
232
+ }) => void;
233
+ onChange?: (state: ViewerState) => void;
234
+ onError?: (err: Error) => void;
235
+ onProgress?: (progress: number) => void;
236
+ className?: string;
237
+ style?: CSSProperties;
238
+ }
239
+
240
+ export declare interface ViewerRef extends CoreViewer {
241
+ }
242
+
243
+ export { ViewerSourceInput }
244
+
245
+ export { ViewerState }
246
+
247
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,402 @@
1
+ import { jsx as a, jsxs as N, Fragment as V } from "react/jsx-runtime";
2
+ import { forwardRef as Y, useRef as _, useImperativeHandle as q, useEffect as b, useState as O, useMemo as D } from "react";
3
+ import { createViewer as B, normalizeModelSource as Q, getMetalPreset as k, CANONICAL_METAL_PRESETS as F, getStonePreset as z, CANONICAL_STONE_PRESETS as U } from "@thinkersteps_labs/3d-viewer-core";
4
+ import { ALL_METAL_PRESETS as ie, ALL_STONE_PRESETS as ae, CANONICAL_METAL_PRESETS as de, CANONICAL_STONE_PRESETS as ue, DIAMOND_PRESETS as pe, GEMSTONE_PRESETS as fe, METAL_PRESETS as me, PHYSICALLY_BASED_METAL_PRESETS as Se, STONE_PRESETS as he, getMetalPreset as ve, getStonePreset as Ne, inferFormatFromUrl as we, normalizeModelSource as Ee } from "@thinkersteps_labs/3d-viewer-core";
5
+ const K = Y(function(s, c) {
6
+ const {
7
+ src: S,
8
+ source: h,
9
+ metalColor: d,
10
+ stoneColor: u,
11
+ materialColors: f,
12
+ partColors: v,
13
+ background: t,
14
+ metalEnvUrl: m,
15
+ gemEnvUrl: e,
16
+ options: w,
17
+ fov: g,
18
+ shadows: x,
19
+ autoRotate: P,
20
+ quality: y,
21
+ height: M,
22
+ minHeight: R,
23
+ aspectRatio: j,
24
+ className: H,
25
+ style: G
26
+ } = s, I = _(null), p = _(null), E = _(s);
27
+ E.current = s, q(c, () => p.current, []), b(() => {
28
+ if (!I.current) return;
29
+ const o = B(I.current, {
30
+ ...w,
31
+ ...t !== void 0 && { background: t },
32
+ ...g !== void 0 && { fov: g },
33
+ ...x !== void 0 && { shadows: x },
34
+ ...P !== void 0 && { autoRotate: P },
35
+ ...y !== void 0 && { quality: y },
36
+ ...m !== void 0 && { metalEnvUrl: m },
37
+ ...e !== void 0 && { gemEnvUrl: e }
38
+ });
39
+ return p.current = o, c && (typeof c == "function" ? c(o) : c.current = o), o.on("loaded", ({ parts: i, materials: r }) => {
40
+ var l, C;
41
+ return (C = (l = E.current).onLoaded) == null ? void 0 : C.call(l, i, r);
42
+ }), o.on("partSelected", (i) => {
43
+ var r, l;
44
+ return (l = (r = E.current).onPartSelected) == null ? void 0 : l.call(r, i);
45
+ }), o.on("change", (i) => {
46
+ var r, l;
47
+ return (l = (r = E.current).onChange) == null ? void 0 : l.call(r, i);
48
+ }), o.on("error", (i) => {
49
+ var r, l;
50
+ return (l = (r = E.current).onError) == null ? void 0 : l.call(r, i);
51
+ }), o.on("loading", ({ progress: i }) => {
52
+ var r, l;
53
+ return (l = (r = E.current).onProgress) == null ? void 0 : l.call(r, i);
54
+ }), () => {
55
+ o.dispose(), c && (typeof c == "function" ? c(null) : c.current = null);
56
+ };
57
+ }, []), b(() => {
58
+ if (!p.current) return;
59
+ const o = Q(S ?? h);
60
+ if (!o) {
61
+ p.current.unload();
62
+ return;
63
+ }
64
+ const i = new AbortController();
65
+ return p.current.load(o, { signal: i.signal }), () => i.abort();
66
+ }, [S, h]), b(() => {
67
+ var o;
68
+ d && ((o = p.current) == null || o.setMetalColor(d));
69
+ }, [d]), b(() => {
70
+ var o;
71
+ u && ((o = p.current) == null || o.setStoneColor(u));
72
+ }, [u]);
73
+ const T = _({});
74
+ b(() => {
75
+ if (!p.current) return;
76
+ const o = T.current, i = f ?? {};
77
+ for (const r of Object.keys(o))
78
+ r in i || p.current.clearMaterialColor(r);
79
+ for (const [r, l] of Object.entries(i))
80
+ o[r] !== l && p.current.setMaterialColor(r, l);
81
+ T.current = { ...i };
82
+ }, [f]);
83
+ const A = _({});
84
+ b(() => {
85
+ if (!p.current) return;
86
+ const o = A.current, i = v ?? {};
87
+ for (const r of Object.keys(o))
88
+ if (!(r in i))
89
+ if (r.includes(":")) {
90
+ const [l, C] = r.split(":"), L = parseInt(C ?? "", 10);
91
+ p.current.clearPartColor(l ?? "", isNaN(L) ? void 0 : L);
92
+ } else
93
+ p.current.clearPartColor(r);
94
+ for (const [r, l] of Object.entries(i))
95
+ if (o[r] !== l)
96
+ if (r.includes(":")) {
97
+ const [C, L] = r.split(":"), J = parseInt(L ?? "", 10);
98
+ p.current.setPartColor(C ?? "", l, isNaN(J) ? void 0 : J);
99
+ } else
100
+ p.current.setPartColor(r, l);
101
+ A.current = { ...i };
102
+ }, [v]), b(() => {
103
+ p.current && (P ? p.current.startRotation() : p.current.stopRotation());
104
+ }, [P]), b(() => {
105
+ var o;
106
+ y && ((o = p.current) == null || o.setQuality(y));
107
+ }, [y]);
108
+ const $ = {
109
+ width: "100%",
110
+ height: M !== void 0 ? typeof M == "number" ? `${M}px` : M : "100%",
111
+ minHeight: R !== void 0 ? typeof R == "number" ? `${R}px` : R : void 0,
112
+ aspectRatio: j,
113
+ position: "relative",
114
+ ...G
115
+ };
116
+ return /* @__PURE__ */ a(
117
+ "div",
118
+ {
119
+ ref: I,
120
+ className: H,
121
+ style: $
122
+ }
123
+ );
124
+ });
125
+ function W({
126
+ value: n,
127
+ onChange: s,
128
+ options: c = F,
129
+ category: S = "all",
130
+ variant: h = "pills",
131
+ showLabel: d = !0,
132
+ className: u = "",
133
+ style: f
134
+ }) {
135
+ const v = S === "all" ? c : c.filter((e) => e.category === S), t = n ? k(n) : void 0, m = (t == null ? void 0 : t.id) ?? n;
136
+ return /* @__PURE__ */ N("div", { className: `td-swatch-group ${u}`, style: f, children: [
137
+ d && /* @__PURE__ */ N("div", { className: "td-swatch-header", children: [
138
+ /* @__PURE__ */ a("span", { className: "td-swatch-title", children: "Metal" }),
139
+ t && /* @__PURE__ */ a("span", { className: "td-swatch-selected-name", children: t.label })
140
+ ] }),
141
+ /* @__PURE__ */ a("div", { className: "td-swatch-row", role: "radiogroup", "aria-label": "Metal Selection", children: v.map((e) => {
142
+ const w = m === e.id || n === e.hex;
143
+ return /* @__PURE__ */ N(
144
+ "button",
145
+ {
146
+ type: "button",
147
+ role: "radio",
148
+ "aria-checked": w,
149
+ title: e.label,
150
+ className: `td-swatch-${h === "dots" ? "dot" : "pill"} ${w ? "active" : ""}`,
151
+ onClick: () => s == null ? void 0 : s(e.id, e),
152
+ children: [
153
+ /* @__PURE__ */ a(
154
+ "span",
155
+ {
156
+ className: "td-swatch-color-circle",
157
+ style: { backgroundColor: e.hex }
158
+ }
159
+ ),
160
+ h === "pills" && /* @__PURE__ */ a("span", { children: e.label })
161
+ ]
162
+ },
163
+ e.id
164
+ );
165
+ }) })
166
+ ] });
167
+ }
168
+ function X({
169
+ value: n,
170
+ onChange: s,
171
+ options: c = F,
172
+ label: S = "Select Metal",
173
+ className: h = "",
174
+ style: d
175
+ }) {
176
+ var v;
177
+ const u = n ? k(n) : void 0, f = (u == null ? void 0 : u.id) ?? n ?? ((v = c[0]) == null ? void 0 : v.id);
178
+ return /* @__PURE__ */ N("div", { className: `td-select-wrapper ${h}`, style: d, children: [
179
+ S && /* @__PURE__ */ a("label", { className: "td-swatch-title", children: S }),
180
+ /* @__PURE__ */ a(
181
+ "select",
182
+ {
183
+ className: "td-select",
184
+ value: f,
185
+ onChange: (t) => {
186
+ const m = t.target.value, e = k(m);
187
+ s == null || s(m, e);
188
+ },
189
+ children: c.map((t) => /* @__PURE__ */ a("option", { value: t.id, children: t.label }, t.id))
190
+ }
191
+ )
192
+ ] });
193
+ }
194
+ function Z({
195
+ value: n,
196
+ onChange: s,
197
+ options: c = U,
198
+ category: S = "all",
199
+ variant: h = "pills",
200
+ showLabel: d = !0,
201
+ className: u = "",
202
+ style: f
203
+ }) {
204
+ const v = S === "all" ? c : c.filter((e) => e.category === S), t = n ? z(n) : void 0, m = (t == null ? void 0 : t.id) ?? n;
205
+ return /* @__PURE__ */ N("div", { className: `td-swatch-group ${u}`, style: f, children: [
206
+ d && /* @__PURE__ */ N("div", { className: "td-swatch-header", children: [
207
+ /* @__PURE__ */ a("span", { className: "td-swatch-title", children: "Gemstone" }),
208
+ t && /* @__PURE__ */ N("span", { className: "td-swatch-selected-name", children: [
209
+ t.label,
210
+ t.grade ? ` (${t.grade})` : ""
211
+ ] })
212
+ ] }),
213
+ /* @__PURE__ */ a("div", { className: "td-swatch-row", role: "radiogroup", "aria-label": "Gemstone Selection", children: v.map((e) => {
214
+ const w = m === e.id || n === e.hex;
215
+ return /* @__PURE__ */ N(
216
+ "button",
217
+ {
218
+ type: "button",
219
+ role: "radio",
220
+ "aria-checked": w,
221
+ title: `${e.label}${e.grade ? ` - ${e.grade}` : ""}`,
222
+ className: `td-swatch-${h === "dots" ? "dot" : "pill"} ${w ? "active" : ""}`,
223
+ onClick: () => s == null ? void 0 : s(e.id, e),
224
+ children: [
225
+ /* @__PURE__ */ a(
226
+ "span",
227
+ {
228
+ className: "td-swatch-color-circle",
229
+ style: { backgroundColor: e.hex }
230
+ }
231
+ ),
232
+ h === "pills" && /* @__PURE__ */ a("span", { children: e.label })
233
+ ]
234
+ },
235
+ e.id
236
+ );
237
+ }) })
238
+ ] });
239
+ }
240
+ function ee({
241
+ value: n,
242
+ onChange: s,
243
+ options: c = U,
244
+ label: S = "Select Gemstone",
245
+ className: h = "",
246
+ style: d
247
+ }) {
248
+ var v;
249
+ const u = n ? z(n) : void 0, f = (u == null ? void 0 : u.id) ?? n ?? ((v = c[0]) == null ? void 0 : v.id);
250
+ return /* @__PURE__ */ N("div", { className: `td-select-wrapper ${h}`, style: d, children: [
251
+ S && /* @__PURE__ */ a("label", { className: "td-swatch-title", children: S }),
252
+ /* @__PURE__ */ a(
253
+ "select",
254
+ {
255
+ className: "td-select",
256
+ value: f,
257
+ onChange: (t) => {
258
+ const m = t.target.value, e = z(m);
259
+ s == null || s(m, e);
260
+ },
261
+ children: c.map((t) => /* @__PURE__ */ N("option", { value: t.id, children: [
262
+ t.label,
263
+ " ",
264
+ t.grade ? `(${t.grade})` : ""
265
+ ] }, t.id))
266
+ }
267
+ )
268
+ ] });
269
+ }
270
+ function le({
271
+ src: n,
272
+ source: s,
273
+ defaultMetal: c = "yellow-gold-18k",
274
+ defaultStone: S = "diamond-d",
275
+ metal: h,
276
+ onMetalChange: d,
277
+ stone: u,
278
+ onStoneChange: f,
279
+ metalOptions: v,
280
+ stoneOptions: t,
281
+ controlsVariant: m = "pills",
282
+ height: e = 520,
283
+ autoRotate: w = !0,
284
+ quality: g = "high",
285
+ fov: x,
286
+ shadows: P,
287
+ onLoaded: y,
288
+ onPartSelected: M,
289
+ className: R = "",
290
+ style: j,
291
+ viewerRef: H
292
+ }) {
293
+ const [G, I] = O(c), [p, E] = O(S), T = h !== void 0 ? h : G, A = u !== void 0 ? u : p, $ = (r, l) => {
294
+ h === void 0 && I(r), d == null || d(r, l);
295
+ }, o = (r, l) => {
296
+ u === void 0 && E(r), f == null || f(r, l);
297
+ }, i = typeof e == "number" ? `${e}px` : e;
298
+ return /* @__PURE__ */ N("div", { className: `td-customizer-card ${R}`, style: j, children: [
299
+ /* @__PURE__ */ a("div", { className: "td-customizer-viewport", style: { height: i }, children: /* @__PURE__ */ a(
300
+ K,
301
+ {
302
+ ref: H,
303
+ src: n,
304
+ source: s,
305
+ metalColor: T,
306
+ stoneColor: A,
307
+ autoRotate: w,
308
+ quality: g,
309
+ fov: x,
310
+ shadows: P,
311
+ onLoaded: y,
312
+ onPartSelected: M
313
+ }
314
+ ) }),
315
+ m !== "none" && /* @__PURE__ */ a("div", { className: "td-customizer-controls", children: m === "select" ? /* @__PURE__ */ N(V, { children: [
316
+ /* @__PURE__ */ a(
317
+ X,
318
+ {
319
+ value: T,
320
+ onChange: $,
321
+ options: v
322
+ }
323
+ ),
324
+ /* @__PURE__ */ a(
325
+ ee,
326
+ {
327
+ value: A,
328
+ onChange: o,
329
+ options: t
330
+ }
331
+ )
332
+ ] }) : /* @__PURE__ */ N(V, { children: [
333
+ /* @__PURE__ */ a(
334
+ W,
335
+ {
336
+ value: T,
337
+ onChange: $,
338
+ options: v,
339
+ variant: m
340
+ }
341
+ ),
342
+ /* @__PURE__ */ a(
343
+ Z,
344
+ {
345
+ value: A,
346
+ onChange: o,
347
+ options: t,
348
+ variant: m
349
+ }
350
+ )
351
+ ] }) })
352
+ ] });
353
+ }
354
+ function ce(n = {}) {
355
+ const {
356
+ defaultMetal: s = "yellow-gold-18k",
357
+ defaultStone: c = "diamond-d",
358
+ metalOptions: S = F,
359
+ stoneOptions: h = U
360
+ } = n, [d, u] = O(s), [f, v] = O(c), t = D(() => k(d), [d]), m = D(() => z(f), [f]), e = D(
361
+ () => ({
362
+ metalColor: d,
363
+ stoneColor: f
364
+ }),
365
+ [d, f]
366
+ );
367
+ return {
368
+ metal: d,
369
+ setMetal: u,
370
+ stone: f,
371
+ setStone: v,
372
+ metalPreset: t,
373
+ stonePreset: m,
374
+ metalOptions: S,
375
+ stoneOptions: h,
376
+ viewerProps: e
377
+ };
378
+ }
379
+ export {
380
+ ie as ALL_METAL_PRESETS,
381
+ ae as ALL_STONE_PRESETS,
382
+ de as CANONICAL_METAL_PRESETS,
383
+ ue as CANONICAL_STONE_PRESETS,
384
+ pe as DIAMOND_PRESETS,
385
+ fe as GEMSTONE_PRESETS,
386
+ le as JewelryCustomizer,
387
+ me as METAL_PRESETS,
388
+ X as MetalSelect,
389
+ W as MetalSwatches,
390
+ Se as PHYSICALLY_BASED_METAL_PRESETS,
391
+ he as STONE_PRESETS,
392
+ ee as StoneSelect,
393
+ Z as StoneSwatches,
394
+ K as Viewer,
395
+ K as default,
396
+ ve as getMetalPreset,
397
+ Ne as getStonePreset,
398
+ we as inferFormatFromUrl,
399
+ Ee as normalizeModelSource,
400
+ ce as useJewelryCustomizer
401
+ };
402
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/Viewer.tsx","../src/swatches/MetalSwatches.tsx","../src/swatches/StoneSwatches.tsx","../src/swatches/JewelryCustomizer.tsx","../src/swatches/useJewelryCustomizer.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\nimport { useRef, useEffect, forwardRef, useImperativeHandle, type CSSProperties } from 'react'\nimport {\n createViewer,\n normalizeModelSource,\n type Viewer as CoreViewer,\n type ModelSource,\n type ViewerSourceInput,\n type MetalColorProp,\n type StoneColorProp,\n type ViewerOptions,\n type ViewerState,\n type PartInfo,\n type MaterialSlotInfo,\n type PartType,\n} from '@thinkersteps_labs/3d-viewer-core'\n\nexport interface ViewerRef extends CoreViewer { }\n\nexport interface ViewerProps {\n /** 3D model URL or source object. Takes precedence over `source` if both provided. */\n src?: string\n /** 3D model source (URL string, ModelSource payload, or null). */\n source?: ViewerSourceInput\n /** Metal preset ID or hex code. IDE autocompletes standard presets. */\n metalColor?: MetalColorProp\n /** Stone preset ID or hex code. IDE autocompletes standard presets. */\n stoneColor?: StoneColorProp\n /** Bulk material name or slot ID color overrides. */\n materialColors?: Record<string, string>\n /** Part color overrides keyed by partId or partId:slotIndex. */\n partColors?: Record<string, string>\n background?: string | number\n metalEnvUrl?: string | ArrayBuffer\n gemEnvUrl?: string | ArrayBuffer\n options?: ViewerOptions\n fov?: number\n shadows?: boolean\n autoRotate?: boolean\n quality?: 'low' | 'medium' | 'high'\n /** Explicit container height (e.g. 520 or '520px'). Prevents zero-height collapse. */\n height?: number | string\n /** Explicit container min-height. */\n minHeight?: number | string\n /** Aspect ratio for responsive sizing (e.g. '16/9' or '4/3'). */\n aspectRatio?: string\n onLoaded?: (parts: PartInfo[], materials?: MaterialSlotInfo[]) => void\n onPartSelected?: (info: {\n meshId: string\n partId?: string\n type: PartType\n slotIndex?: number\n slotId?: string\n materialName?: string\n }) => void\n onChange?: (state: ViewerState) => void\n onError?: (err: Error) => void\n onProgress?: (progress: number) => void\n className?: string\n style?: CSSProperties\n}\n\nexport const Viewer = forwardRef<ViewerRef, ViewerProps>(function Viewer(props, ref) {\n const {\n src,\n source,\n metalColor,\n stoneColor,\n materialColors,\n partColors,\n background,\n metalEnvUrl,\n gemEnvUrl,\n options,\n fov,\n shadows,\n autoRotate,\n quality,\n height,\n minHeight,\n aspectRatio,\n className,\n style,\n } = props\n const containerRef = useRef<HTMLDivElement>(null)\n const viewerRef = useRef<ViewerRef | null>(null)\n const cb = useRef(props)\n cb.current = props\n\n useImperativeHandle(ref, () => viewerRef.current!, [])\n\n useEffect(() => {\n if (!containerRef.current) return\n const viewer = createViewer(containerRef.current, {\n ...options,\n ...(background !== undefined && { background }),\n ...(fov !== undefined && { fov }),\n ...(shadows !== undefined && { shadows }),\n ...(autoRotate !== undefined && { autoRotate }),\n ...(quality !== undefined && { quality }),\n ...(metalEnvUrl !== undefined && { metalEnvUrl }),\n ...(gemEnvUrl !== undefined && { gemEnvUrl }),\n })\n viewerRef.current = viewer\n if (import.meta.env.DEV && typeof window !== 'undefined') (window as any).__viewer = viewer\n if (ref) {\n if (typeof ref === 'function') ref(viewer)\n else (ref as any).current = viewer\n }\n viewer.on('loaded', ({ parts, materials }) => cb.current.onLoaded?.(parts, materials))\n viewer.on('partSelected', (info) => cb.current.onPartSelected?.(info))\n viewer.on('change', (state) => cb.current.onChange?.(state))\n viewer.on('error', (err) => cb.current.onError?.(err))\n viewer.on('loading', ({ progress }) => cb.current.onProgress?.(progress))\n return () => {\n viewer.dispose()\n if (ref) {\n if (typeof ref === 'function') ref(null)\n else (ref as any).current = null\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n useEffect(() => {\n if (!viewerRef.current) return\n const resolved = normalizeModelSource(src ?? source)\n if (!resolved) {\n viewerRef.current.unload()\n return\n }\n const ac = new AbortController()\n viewerRef.current.load(resolved, { signal: ac.signal })\n return () => ac.abort()\n }, [src, source])\n\n useEffect(() => {\n if (metalColor) viewerRef.current?.setMetalColor(metalColor)\n }, [metalColor])\n\n useEffect(() => {\n if (stoneColor) viewerRef.current?.setStoneColor(stoneColor)\n }, [stoneColor])\n\n const prevMaterialColorsRef = useRef<Record<string, string>>({})\n useEffect(() => {\n if (!viewerRef.current) return\n const prev = prevMaterialColorsRef.current\n const next = materialColors ?? {}\n\n for (const key of Object.keys(prev)) {\n if (!(key in next)) {\n viewerRef.current.clearMaterialColor(key)\n }\n }\n for (const [key, color] of Object.entries(next)) {\n if (prev[key] !== color) {\n viewerRef.current.setMaterialColor(key, color)\n }\n }\n prevMaterialColorsRef.current = { ...next }\n }, [materialColors])\n\n const prevPartColorsRef = useRef<Record<string, string>>({})\n useEffect(() => {\n if (!viewerRef.current) return\n const prev = prevPartColorsRef.current\n const next = partColors ?? {}\n\n for (const key of Object.keys(prev)) {\n if (!(key in next)) {\n if (key.includes(':')) {\n const [partId, slotIdxStr] = key.split(':')\n const slotIndex = parseInt(slotIdxStr ?? '', 10)\n viewerRef.current.clearPartColor(partId ?? '', isNaN(slotIndex) ? undefined : slotIndex)\n } else {\n viewerRef.current.clearPartColor(key)\n }\n }\n }\n for (const [key, color] of Object.entries(next)) {\n if (prev[key] !== color) {\n if (key.includes(':')) {\n const [partId, slotIdxStr] = key.split(':')\n const slotIndex = parseInt(slotIdxStr ?? '', 10)\n viewerRef.current.setPartColor(partId ?? '', color, isNaN(slotIndex) ? undefined : slotIndex)\n } else {\n viewerRef.current.setPartColor(key, color)\n }\n }\n }\n prevPartColorsRef.current = { ...next }\n }, [partColors])\n\n useEffect(() => {\n if (!viewerRef.current) return\n if (autoRotate) viewerRef.current.startRotation()\n else viewerRef.current.stopRotation()\n }, [autoRotate])\n\n useEffect(() => {\n if (quality) viewerRef.current?.setQuality(quality)\n }, [quality])\n\n const containerStyle: CSSProperties = {\n width: '100%',\n height: height !== undefined ? (typeof height === 'number' ? `${height}px` : height) : '100%',\n minHeight: minHeight !== undefined ? (typeof minHeight === 'number' ? `${minHeight}px` : minHeight) : undefined,\n aspectRatio,\n position: 'relative',\n ...style,\n }\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n />\n )\n})\n\nexport default Viewer\n","import React, { type CSSProperties } from 'react'\nimport {\n CANONICAL_METAL_PRESETS,\n getMetalPreset,\n type MetalPreset,\n type MetalColorProp,\n} from '@thinkersteps_labs/3d-viewer-core'\nimport './swatches.css'\n\nexport interface MetalSwatchesProps {\n value?: MetalColorProp\n onChange?: (metalId: string, preset?: MetalPreset) => void\n options?: MetalPreset[]\n category?: 'all' | 'gold' | 'platinum-silver' | 'alternative'\n variant?: 'pills' | 'dots'\n showLabel?: boolean\n className?: string\n style?: CSSProperties\n}\n\nexport function MetalSwatches({\n value,\n onChange,\n options = CANONICAL_METAL_PRESETS,\n category = 'all',\n variant = 'pills',\n showLabel = true,\n className = '',\n style,\n}: MetalSwatchesProps) {\n const filteredOptions = category === 'all'\n ? options\n : options.filter((m) => m.category === category)\n\n const activePreset = value ? getMetalPreset(value) : undefined\n const activeId = activePreset?.id ?? value\n\n return (\n <div className={`td-swatch-group ${className}`} style={style}>\n {showLabel && (\n <div className=\"td-swatch-header\">\n <span className=\"td-swatch-title\">Metal</span>\n {activePreset && (\n <span className=\"td-swatch-selected-name\">{activePreset.label}</span>\n )}\n </div>\n )}\n <div className=\"td-swatch-row\" role=\"radiogroup\" aria-label=\"Metal Selection\">\n {filteredOptions.map((m) => {\n const isSelected = activeId === m.id || value === m.hex\n return (\n <button\n key={m.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n title={m.label}\n className={`td-swatch-${variant === 'dots' ? 'dot' : 'pill'} ${isSelected ? 'active' : ''}`}\n onClick={() => onChange?.(m.id, m)}\n >\n <span\n className=\"td-swatch-color-circle\"\n style={{ backgroundColor: m.hex }}\n />\n {variant === 'pills' && <span>{m.label}</span>}\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport interface MetalSelectProps {\n value?: MetalColorProp\n onChange?: (metalId: string, preset?: MetalPreset) => void\n options?: MetalPreset[]\n label?: string\n className?: string\n style?: CSSProperties\n}\n\nexport function MetalSelect({\n value,\n onChange,\n options = CANONICAL_METAL_PRESETS,\n label = 'Select Metal',\n className = '',\n style,\n}: MetalSelectProps) {\n const activePreset = value ? getMetalPreset(value) : undefined\n const activeValue = activePreset?.id ?? value ?? options[0]?.id\n\n return (\n <div className={`td-select-wrapper ${className}`} style={style}>\n {label && <label className=\"td-swatch-title\">{label}</label>}\n <select\n className=\"td-select\"\n value={activeValue}\n onChange={(e) => {\n const val = e.target.value\n const preset = getMetalPreset(val)\n onChange?.(val, preset)\n }}\n >\n {options.map((m) => (\n <option key={m.id} value={m.id}>\n {m.label}\n </option>\n ))}\n </select>\n </div>\n )\n}\n","import React, { type CSSProperties } from 'react'\nimport {\n CANONICAL_STONE_PRESETS,\n getStonePreset,\n type StonePreset,\n type StoneColorProp,\n} from '@thinkersteps_labs/3d-viewer-core'\nimport './swatches.css'\n\nexport interface StoneSwatchesProps {\n value?: StoneColorProp\n onChange?: (stoneId: string, preset?: StonePreset) => void\n options?: StonePreset[]\n category?: 'all' | 'diamond' | 'gemstone'\n variant?: 'pills' | 'dots'\n showLabel?: boolean\n className?: string\n style?: CSSProperties\n}\n\nexport function StoneSwatches({\n value,\n onChange,\n options = CANONICAL_STONE_PRESETS,\n category = 'all',\n variant = 'pills',\n showLabel = true,\n className = '',\n style,\n}: StoneSwatchesProps) {\n const filteredOptions = category === 'all'\n ? options\n : options.filter((s) => s.category === category)\n\n const activePreset = value ? getStonePreset(value) : undefined\n const activeId = activePreset?.id ?? value\n\n return (\n <div className={`td-swatch-group ${className}`} style={style}>\n {showLabel && (\n <div className=\"td-swatch-header\">\n <span className=\"td-swatch-title\">Gemstone</span>\n {activePreset && (\n <span className=\"td-swatch-selected-name\">\n {activePreset.label}\n {activePreset.grade ? ` (${activePreset.grade})` : ''}\n </span>\n )}\n </div>\n )}\n <div className=\"td-swatch-row\" role=\"radiogroup\" aria-label=\"Gemstone Selection\">\n {filteredOptions.map((s) => {\n const isSelected = activeId === s.id || value === s.hex\n return (\n <button\n key={s.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n title={`${s.label}${s.grade ? ` - ${s.grade}` : ''}`}\n className={`td-swatch-${variant === 'dots' ? 'dot' : 'pill'} ${isSelected ? 'active' : ''}`}\n onClick={() => onChange?.(s.id, s)}\n >\n <span\n className=\"td-swatch-color-circle\"\n style={{ backgroundColor: s.hex }}\n />\n {variant === 'pills' && <span>{s.label}</span>}\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport interface StoneSelectProps {\n value?: StoneColorProp\n onChange?: (stoneId: string, preset?: StonePreset) => void\n options?: StonePreset[]\n label?: string\n className?: string\n style?: CSSProperties\n}\n\nexport function StoneSelect({\n value,\n onChange,\n options = CANONICAL_STONE_PRESETS,\n label = 'Select Gemstone',\n className = '',\n style,\n}: StoneSelectProps) {\n const activePreset = value ? getStonePreset(value) : undefined\n const activeValue = activePreset?.id ?? value ?? options[0]?.id\n\n return (\n <div className={`td-select-wrapper ${className}`} style={style}>\n {label && <label className=\"td-swatch-title\">{label}</label>}\n <select\n className=\"td-select\"\n value={activeValue}\n onChange={(e) => {\n const val = e.target.value\n const preset = getStonePreset(val)\n onChange?.(val, preset)\n }}\n >\n {options.map((s) => (\n <option key={s.id} value={s.id}>\n {s.label} {s.grade ? `(${s.grade})` : ''}\n </option>\n ))}\n </select>\n </div>\n )\n}\n","import React, { useState, type CSSProperties } from 'react'\nimport {\n type MetalColorProp,\n type StoneColorProp,\n type ViewerSourceInput,\n type MetalPreset,\n type StonePreset,\n type PartInfo,\n type PartType,\n} from '@thinkersteps_labs/3d-viewer-core'\nimport { Viewer, type ViewerRef } from '../Viewer'\nimport { MetalSwatches, MetalSelect } from './MetalSwatches'\nimport { StoneSwatches, StoneSelect } from './StoneSwatches'\nimport './swatches.css'\n\nexport interface JewelryCustomizerProps {\n src?: string\n source?: ViewerSourceInput\n defaultMetal?: MetalColorProp\n defaultStone?: StoneColorProp\n metal?: MetalColorProp\n onMetalChange?: (metal: string, preset?: MetalPreset) => void\n stone?: StoneColorProp\n onStoneChange?: (stone: string, preset?: StonePreset) => void\n metalOptions?: MetalPreset[]\n stoneOptions?: StonePreset[]\n controlsVariant?: 'pills' | 'dots' | 'select' | 'none'\n height?: number | string\n autoRotate?: boolean\n quality?: 'low' | 'medium' | 'high'\n fov?: number\n shadows?: boolean\n onLoaded?: (parts: PartInfo[]) => void\n onPartSelected?: (info: { meshId: string; type: PartType }) => void\n className?: string\n style?: CSSProperties\n viewerRef?: React.Ref<ViewerRef>\n}\n\nexport function JewelryCustomizer({\n src,\n source,\n defaultMetal = 'yellow-gold-18k',\n defaultStone = 'diamond-d',\n metal: controlledMetal,\n onMetalChange,\n stone: controlledStone,\n onStoneChange,\n metalOptions,\n stoneOptions,\n controlsVariant = 'pills',\n height = 520,\n autoRotate = true,\n quality = 'high',\n fov,\n shadows,\n onLoaded,\n onPartSelected,\n className = '',\n style,\n viewerRef,\n}: JewelryCustomizerProps) {\n const [internalMetal, setInternalMetal] = useState<MetalColorProp>(defaultMetal)\n const [internalStone, setInternalStone] = useState<StoneColorProp>(defaultStone)\n\n const currentMetal = controlledMetal !== undefined ? controlledMetal : internalMetal\n const currentStone = controlledStone !== undefined ? controlledStone : internalStone\n\n const handleMetalChange = (val: string, preset?: MetalPreset) => {\n if (controlledMetal === undefined) setInternalMetal(val)\n onMetalChange?.(val, preset)\n }\n\n const handleStoneChange = (val: string, preset?: StonePreset) => {\n if (controlledStone === undefined) setInternalStone(val)\n onStoneChange?.(val, preset)\n }\n\n const viewportHeight = typeof height === 'number' ? `${height}px` : height\n\n return (\n <div className={`td-customizer-card ${className}`} style={style}>\n <div className=\"td-customizer-viewport\" style={{ height: viewportHeight }}>\n <Viewer\n ref={viewerRef}\n src={src}\n source={source}\n metalColor={currentMetal}\n stoneColor={currentStone}\n autoRotate={autoRotate}\n quality={quality}\n fov={fov}\n shadows={shadows}\n onLoaded={onLoaded}\n onPartSelected={onPartSelected}\n />\n </div>\n\n {controlsVariant !== 'none' && (\n <div className=\"td-customizer-controls\">\n {controlsVariant === 'select' ? (\n <>\n <MetalSelect\n value={currentMetal}\n onChange={handleMetalChange}\n options={metalOptions}\n />\n <StoneSelect\n value={currentStone}\n onChange={handleStoneChange}\n options={stoneOptions}\n />\n </>\n ) : (\n <>\n <MetalSwatches\n value={currentMetal}\n onChange={handleMetalChange}\n options={metalOptions}\n variant={controlsVariant}\n />\n <StoneSwatches\n value={currentStone}\n onChange={handleStoneChange}\n options={stoneOptions}\n variant={controlsVariant}\n />\n </>\n )}\n </div>\n )}\n </div>\n )\n}\n","import { useState, useMemo } from 'react'\nimport {\n CANONICAL_METAL_PRESETS,\n CANONICAL_STONE_PRESETS,\n getMetalPreset,\n getStonePreset,\n type MetalColorProp,\n type StoneColorProp,\n type MetalPreset,\n type StonePreset,\n} from '@thinkersteps_labs/3d-viewer-core'\n\nexport interface UseJewelryCustomizerOptions {\n defaultMetal?: MetalColorProp\n defaultStone?: StoneColorProp\n metalOptions?: MetalPreset[]\n stoneOptions?: StonePreset[]\n}\n\nexport interface UseJewelryCustomizerReturn {\n metal: MetalColorProp\n setMetal: (val: MetalColorProp) => void\n stone: StoneColorProp\n setStone: (val: StoneColorProp) => void\n metalPreset?: MetalPreset\n stonePreset?: StonePreset\n metalOptions: MetalPreset[]\n stoneOptions: StonePreset[]\n viewerProps: {\n metalColor: MetalColorProp\n stoneColor: StoneColorProp\n }\n}\n\nexport function useJewelryCustomizer(\n options: UseJewelryCustomizerOptions = {}\n): UseJewelryCustomizerReturn {\n const {\n defaultMetal = 'yellow-gold-18k',\n defaultStone = 'diamond-d',\n metalOptions = CANONICAL_METAL_PRESETS,\n stoneOptions = CANONICAL_STONE_PRESETS,\n } = options\n\n const [metal, setMetal] = useState<MetalColorProp>(defaultMetal)\n const [stone, setStone] = useState<StoneColorProp>(defaultStone)\n\n const metalPreset = useMemo(() => getMetalPreset(metal), [metal])\n const stonePreset = useMemo(() => getStonePreset(stone), [stone])\n\n const viewerProps = useMemo(\n () => ({\n metalColor: metal,\n stoneColor: stone,\n }),\n [metal, stone]\n )\n\n return {\n metal,\n setMetal,\n stone,\n setStone,\n metalPreset,\n stonePreset,\n metalOptions,\n stoneOptions,\n viewerProps,\n }\n}\n"],"names":["Viewer","forwardRef","props","ref","src","source","metalColor","stoneColor","materialColors","partColors","background","metalEnvUrl","gemEnvUrl","options","fov","shadows","autoRotate","quality","height","minHeight","aspectRatio","className","style","containerRef","useRef","viewerRef","cb","useImperativeHandle","useEffect","viewer","createViewer","parts","materials","_b","_a","info","state","err","progress","resolved","normalizeModelSource","ac","prevMaterialColorsRef","prev","next","key","color","prevPartColorsRef","partId","slotIdxStr","slotIndex","containerStyle","jsx","MetalSwatches","value","onChange","CANONICAL_METAL_PRESETS","category","variant","showLabel","filteredOptions","m","activePreset","getMetalPreset","activeId","jsxs","isSelected","MetalSelect","label","activeValue","e","val","preset","StoneSwatches","CANONICAL_STONE_PRESETS","s","getStonePreset","StoneSelect","JewelryCustomizer","defaultMetal","defaultStone","controlledMetal","onMetalChange","controlledStone","onStoneChange","metalOptions","stoneOptions","controlsVariant","onLoaded","onPartSelected","internalMetal","setInternalMetal","useState","internalStone","setInternalStone","currentMetal","currentStone","handleMetalChange","handleStoneChange","viewportHeight","Fragment","useJewelryCustomizer","metal","setMetal","stone","setStone","metalPreset","useMemo","stonePreset","viewerProps"],"mappings":";;;;AA8DO,MAAMA,IAASC,EAAmC,SAAgBC,GAAOC,GAAK;AACnF,QAAM;AAAA,IACJ,KAAAC;AAAA,IACA,QAAAC;AAAA,IACA,YAAAC;AAAA,IACA,YAAAC;AAAA,IACA,gBAAAC;AAAA,IACA,YAAAC;AAAA,IACA,YAAAC;AAAA,IACA,aAAAC;AAAA,IACA,WAAAC;AAAA,IACA,SAAAC;AAAA,IACA,KAAAC;AAAA,IACA,SAAAC;AAAA,IACA,YAAAC;AAAA,IACA,SAAAC;AAAA,IACA,QAAAC;AAAA,IACA,WAAAC;AAAA,IACA,aAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,EAAA,IACEpB,GACEqB,IAAeC,EAAuB,IAAI,GAC1CC,IAAYD,EAAyB,IAAI,GACzCE,IAAKF,EAAOtB,CAAK;AACvB,EAAAwB,EAAG,UAAUxB,GAEbyB,EAAoBxB,GAAK,MAAMsB,EAAU,SAAU,CAAA,CAAE,GAErDG,EAAU,MAAM;AACd,QAAI,CAACL,EAAa,QAAS;AAC3B,UAAMM,IAASC,EAAaP,EAAa,SAAS;AAAA,MAChD,GAAGV;AAAA,MACH,GAAIH,MAAe,UAAa,EAAE,YAAAA,EAAA;AAAA,MAClC,GAAII,MAAQ,UAAa,EAAE,KAAAA,EAAA;AAAA,MAC3B,GAAIC,MAAY,UAAa,EAAE,SAAAA,EAAA;AAAA,MAC/B,GAAIC,MAAe,UAAa,EAAE,YAAAA,EAAA;AAAA,MAClC,GAAIC,MAAY,UAAa,EAAE,SAAAA,EAAA;AAAA,MAC/B,GAAIN,MAAgB,UAAa,EAAE,aAAAA,EAAA;AAAA,MACnC,GAAIC,MAAc,UAAa,EAAE,WAAAA,EAAA;AAAA,IAAU,CAC5C;AACD,WAAAa,EAAU,UAAUI,GAEhB1B,MACE,OAAOA,KAAQ,aAAYA,EAAI0B,CAAM,MACvB,UAAUA,IAE9BA,EAAO,GAAG,UAAU,CAAC,EAAE,OAAAE,GAAO,WAAAC,QAAU;;AAAM,cAAAC,KAAAC,IAAAR,EAAG,SAAQ,aAAX,gBAAAO,EAAA,KAAAC,GAAsBH,GAAOC;AAAA,KAAU,GACrFH,EAAO,GAAG,gBAAgB,CAACM,MAAA;;AAAS,cAAAF,KAAAC,IAAAR,EAAG,SAAQ,mBAAX,gBAAAO,EAAA,KAAAC,GAA4BC;AAAA,KAAK,GACrEN,EAAO,GAAG,UAAU,CAACO,MAAA;;AAAU,cAAAH,KAAAC,IAAAR,EAAG,SAAQ,aAAX,gBAAAO,EAAA,KAAAC,GAAsBE;AAAA,KAAM,GAC3DP,EAAO,GAAG,SAAS,CAACQ,MAAA;;AAAQ,cAAAJ,KAAAC,IAAAR,EAAG,SAAQ,YAAX,gBAAAO,EAAA,KAAAC,GAAqBG;AAAA,KAAI,GACrDR,EAAO,GAAG,WAAW,CAAC,EAAE,UAAAS;;AAAe,cAAAL,KAAAC,IAAAR,EAAG,SAAQ,eAAX,gBAAAO,EAAA,KAAAC,GAAwBI;AAAA,KAAS,GACjE,MAAM;AACX,MAAAT,EAAO,QAAA,GACH1B,MACE,OAAOA,KAAQ,aAAYA,EAAI,IAAI,MACrB,UAAU;AAAA,IAEhC;AAAA,EAEF,GAAG,CAAA,CAAE,GAELyB,EAAU,MAAM;AACd,QAAI,CAACH,EAAU,QAAS;AACxB,UAAMc,IAAWC,EAAqBpC,KAAOC,CAAM;AACnD,QAAI,CAACkC,GAAU;AACb,MAAAd,EAAU,QAAQ,OAAA;AAClB;AAAA,IACF;AACA,UAAMgB,IAAK,IAAI,gBAAA;AACf,WAAAhB,EAAU,QAAQ,KAAKc,GAAU,EAAE,QAAQE,EAAG,QAAQ,GAC/C,MAAMA,EAAG,MAAA;AAAA,EAClB,GAAG,CAACrC,GAAKC,CAAM,CAAC,GAEhBuB,EAAU,MAAM;;AACd,IAAItB,OAAY4B,IAAAT,EAAU,YAAV,QAAAS,EAAmB,cAAc5B;AAAA,EACnD,GAAG,CAACA,CAAU,CAAC,GAEfsB,EAAU,MAAM;;AACd,IAAIrB,OAAY2B,IAAAT,EAAU,YAAV,QAAAS,EAAmB,cAAc3B;AAAA,EACnD,GAAG,CAACA,CAAU,CAAC;AAEf,QAAMmC,IAAwBlB,EAA+B,EAAE;AAC/D,EAAAI,EAAU,MAAM;AACd,QAAI,CAACH,EAAU,QAAS;AACxB,UAAMkB,IAAOD,EAAsB,SAC7BE,IAAOpC,KAAkB,CAAA;AAE/B,eAAWqC,KAAO,OAAO,KAAKF,CAAI;AAChC,MAAME,KAAOD,KACXnB,EAAU,QAAQ,mBAAmBoB,CAAG;AAG5C,eAAW,CAACA,GAAKC,CAAK,KAAK,OAAO,QAAQF,CAAI;AAC5C,MAAID,EAAKE,CAAG,MAAMC,KAChBrB,EAAU,QAAQ,iBAAiBoB,GAAKC,CAAK;AAGjD,IAAAJ,EAAsB,UAAU,EAAE,GAAGE,EAAA;AAAA,EACvC,GAAG,CAACpC,CAAc,CAAC;AAEnB,QAAMuC,IAAoBvB,EAA+B,EAAE;AAC3D,EAAAI,EAAU,MAAM;AACd,QAAI,CAACH,EAAU,QAAS;AACxB,UAAMkB,IAAOI,EAAkB,SACzBH,IAAOnC,KAAc,CAAA;AAE3B,eAAWoC,KAAO,OAAO,KAAKF,CAAI;AAChC,UAAI,EAAEE,KAAOD;AACX,YAAIC,EAAI,SAAS,GAAG,GAAG;AACrB,gBAAM,CAACG,GAAQC,CAAU,IAAIJ,EAAI,MAAM,GAAG,GACpCK,IAAY,SAASD,KAAc,IAAI,EAAE;AAC/C,UAAAxB,EAAU,QAAQ,eAAeuB,KAAU,IAAI,MAAME,CAAS,IAAI,SAAYA,CAAS;AAAA,QACzF;AACE,UAAAzB,EAAU,QAAQ,eAAeoB,CAAG;AAI1C,eAAW,CAACA,GAAKC,CAAK,KAAK,OAAO,QAAQF,CAAI;AAC5C,UAAID,EAAKE,CAAG,MAAMC;AAChB,YAAID,EAAI,SAAS,GAAG,GAAG;AACrB,gBAAM,CAACG,GAAQC,CAAU,IAAIJ,EAAI,MAAM,GAAG,GACpCK,IAAY,SAASD,KAAc,IAAI,EAAE;AAC/C,UAAAxB,EAAU,QAAQ,aAAauB,KAAU,IAAIF,GAAO,MAAMI,CAAS,IAAI,SAAYA,CAAS;AAAA,QAC9F;AACE,UAAAzB,EAAU,QAAQ,aAAaoB,GAAKC,CAAK;AAI/C,IAAAC,EAAkB,UAAU,EAAE,GAAGH,EAAA;AAAA,EACnC,GAAG,CAACnC,CAAU,CAAC,GAEfmB,EAAU,MAAM;AACd,IAAKH,EAAU,YACXT,IAAYS,EAAU,QAAQ,cAAA,IAC7BA,EAAU,QAAQ,aAAA;AAAA,EACzB,GAAG,CAACT,CAAU,CAAC,GAEfY,EAAU,MAAM;;AACd,IAAIX,OAASiB,IAAAT,EAAU,YAAV,QAAAS,EAAmB,WAAWjB;AAAA,EAC7C,GAAG,CAACA,CAAO,CAAC;AAEZ,QAAMkC,IAAgC;AAAA,IACpC,OAAO;AAAA,IACP,QAAQjC,MAAW,SAAa,OAAOA,KAAW,WAAW,GAAGA,CAAM,OAAOA,IAAU;AAAA,IACvF,WAAWC,MAAc,SAAa,OAAOA,KAAc,WAAW,GAAGA,CAAS,OAAOA,IAAa;AAAA,IACtG,aAAAC;AAAA,IACA,UAAU;AAAA,IACV,GAAGE;AAAA,EAAA;AAGL,SACE,gBAAA8B;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK7B;AAAA,MACL,WAAAF;AAAA,MACA,OAAO8B;AAAA,IAAA;AAAA,EAAA;AAGb,CAAC;ACxMM,SAASE,EAAc;AAAA,EAC5B,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,SAAA1C,IAAU2C;AAAA,EACV,UAAAC,IAAW;AAAA,EACX,SAAAC,IAAU;AAAA,EACV,WAAAC,IAAY;AAAA,EACZ,WAAAtC,IAAY;AAAA,EACZ,OAAAC;AACF,GAAuB;AACrB,QAAMsC,IAAkBH,MAAa,QACjC5C,IACAA,EAAQ,OAAO,CAACgD,MAAMA,EAAE,aAAaJ,CAAQ,GAE3CK,IAAeR,IAAQS,EAAeT,CAAK,IAAI,QAC/CU,KAAWF,KAAA,gBAAAA,EAAc,OAAMR;AAErC,2BACG,OAAA,EAAI,WAAW,mBAAmBjC,CAAS,IAAI,OAAAC,GAC7C,UAAA;AAAA,IAAAqC,KACC,gBAAAM,EAAC,OAAA,EAAI,WAAU,oBACb,UAAA;AAAA,MAAA,gBAAAb,EAAC,QAAA,EAAK,WAAU,mBAAkB,UAAA,SAAK;AAAA,MACtCU,KACC,gBAAAV,EAAC,QAAA,EAAK,WAAU,2BAA2B,YAAa,MAAA,CAAM;AAAA,IAAA,GAElE;AAAA,IAEF,gBAAAA,EAAC,OAAA,EAAI,WAAU,iBAAgB,MAAK,cAAa,cAAW,mBACzD,UAAAQ,EAAgB,IAAI,CAACC,MAAM;AAC1B,YAAMK,IAAaF,MAAaH,EAAE,MAAMP,MAAUO,EAAE;AACpD,aACE,gBAAAI;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,gBAAcC;AAAA,UACd,OAAOL,EAAE;AAAA,UACT,WAAW,aAAaH,MAAY,SAAS,QAAQ,MAAM,IAAIQ,IAAa,WAAW,EAAE;AAAA,UACzF,SAAS,MAAMX,KAAA,gBAAAA,EAAWM,EAAE,IAAIA;AAAA,UAEhC,UAAA;AAAA,YAAA,gBAAAT;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,iBAAiBS,EAAE,IAAA;AAAA,cAAI;AAAA,YAAA;AAAA,YAEjCH,MAAY,WAAW,gBAAAN,EAAC,QAAA,EAAM,YAAE,MAAA,CAAM;AAAA,UAAA;AAAA,QAAA;AAAA,QAZlCS,EAAE;AAAA,MAAA;AAAA,IAeb,CAAC,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAWO,SAASM,EAAY;AAAA,EAC1B,OAAAb;AAAA,EACA,UAAAC;AAAA,EACA,SAAA1C,IAAU2C;AAAA,EACV,OAAAY,IAAQ;AAAA,EACR,WAAA/C,IAAY;AAAA,EACZ,OAAAC;AACF,GAAqB;;AACnB,QAAMwC,IAAeR,IAAQS,EAAeT,CAAK,IAAI,QAC/Ce,KAAcP,KAAA,gBAAAA,EAAc,OAAMR,OAASpB,IAAArB,EAAQ,CAAC,MAAT,gBAAAqB,EAAY;AAE7D,2BACG,OAAA,EAAI,WAAW,qBAAqBb,CAAS,IAAI,OAAAC,GAC/C,UAAA;AAAA,IAAA8C,KAAS,gBAAAhB,EAAC,SAAA,EAAM,WAAU,mBAAmB,UAAAgB,GAAM;AAAA,IACpD,gBAAAhB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAOiB;AAAA,QACP,UAAU,CAACC,MAAM;AACf,gBAAMC,IAAMD,EAAE,OAAO,OACfE,IAAST,EAAeQ,CAAG;AACjC,UAAAhB,KAAA,QAAAA,EAAWgB,GAAKC;AAAA,QAClB;AAAA,QAEC,UAAA3D,EAAQ,IAAI,CAACgD,MACZ,gBAAAT,EAAC,UAAA,EAAkB,OAAOS,EAAE,IACzB,UAAAA,EAAE,MAAA,GADQA,EAAE,EAEf,CACD;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GACF;AAEJ;AC7FO,SAASY,EAAc;AAAA,EAC5B,OAAAnB;AAAA,EACA,UAAAC;AAAA,EACA,SAAA1C,IAAU6D;AAAA,EACV,UAAAjB,IAAW;AAAA,EACX,SAAAC,IAAU;AAAA,EACV,WAAAC,IAAY;AAAA,EACZ,WAAAtC,IAAY;AAAA,EACZ,OAAAC;AACF,GAAuB;AACrB,QAAMsC,IAAkBH,MAAa,QACjC5C,IACAA,EAAQ,OAAO,CAAC8D,MAAMA,EAAE,aAAalB,CAAQ,GAE3CK,IAAeR,IAAQsB,EAAetB,CAAK,IAAI,QAC/CU,KAAWF,KAAA,gBAAAA,EAAc,OAAMR;AAErC,2BACG,OAAA,EAAI,WAAW,mBAAmBjC,CAAS,IAAI,OAAAC,GAC7C,UAAA;AAAA,IAAAqC,KACC,gBAAAM,EAAC,OAAA,EAAI,WAAU,oBACb,UAAA;AAAA,MAAA,gBAAAb,EAAC,QAAA,EAAK,WAAU,mBAAkB,UAAA,YAAQ;AAAA,MACzCU,KACC,gBAAAG,EAAC,QAAA,EAAK,WAAU,2BACb,UAAA;AAAA,QAAAH,EAAa;AAAA,QACbA,EAAa,QAAQ,KAAKA,EAAa,KAAK,MAAM;AAAA,MAAA,EAAA,CACrD;AAAA,IAAA,GAEJ;AAAA,IAEF,gBAAAV,EAAC,OAAA,EAAI,WAAU,iBAAgB,MAAK,cAAa,cAAW,sBACzD,UAAAQ,EAAgB,IAAI,CAACe,MAAM;AAC1B,YAAMT,IAAaF,MAAaW,EAAE,MAAMrB,MAAUqB,EAAE;AACpD,aACE,gBAAAV;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,gBAAcC;AAAA,UACd,OAAO,GAAGS,EAAE,KAAK,GAAGA,EAAE,QAAQ,MAAMA,EAAE,KAAK,KAAK,EAAE;AAAA,UAClD,WAAW,aAAajB,MAAY,SAAS,QAAQ,MAAM,IAAIQ,IAAa,WAAW,EAAE;AAAA,UACzF,SAAS,MAAMX,KAAA,gBAAAA,EAAWoB,EAAE,IAAIA;AAAA,UAEhC,UAAA;AAAA,YAAA,gBAAAvB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,iBAAiBuB,EAAE,IAAA;AAAA,cAAI;AAAA,YAAA;AAAA,YAEjCjB,MAAY,WAAW,gBAAAN,EAAC,QAAA,EAAM,YAAE,MAAA,CAAM;AAAA,UAAA;AAAA,QAAA;AAAA,QAZlCuB,EAAE;AAAA,MAAA;AAAA,IAeb,CAAC,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAWO,SAASE,GAAY;AAAA,EAC1B,OAAAvB;AAAA,EACA,UAAAC;AAAA,EACA,SAAA1C,IAAU6D;AAAA,EACV,OAAAN,IAAQ;AAAA,EACR,WAAA/C,IAAY;AAAA,EACZ,OAAAC;AACF,GAAqB;;AACnB,QAAMwC,IAAeR,IAAQsB,EAAetB,CAAK,IAAI,QAC/Ce,KAAcP,KAAA,gBAAAA,EAAc,OAAMR,OAASpB,IAAArB,EAAQ,CAAC,MAAT,gBAAAqB,EAAY;AAE7D,2BACG,OAAA,EAAI,WAAW,qBAAqBb,CAAS,IAAI,OAAAC,GAC/C,UAAA;AAAA,IAAA8C,KAAS,gBAAAhB,EAAC,SAAA,EAAM,WAAU,mBAAmB,UAAAgB,GAAM;AAAA,IACpD,gBAAAhB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAOiB;AAAA,QACP,UAAU,CAACC,MAAM;AACf,gBAAMC,IAAMD,EAAE,OAAO,OACfE,IAASI,EAAeL,CAAG;AACjC,UAAAhB,KAAA,QAAAA,EAAWgB,GAAKC;AAAA,QAClB;AAAA,QAEC,UAAA3D,EAAQ,IAAI,CAAC8D,wBACX,UAAA,EAAkB,OAAOA,EAAE,IACzB,UAAA;AAAA,UAAAA,EAAE;AAAA,UAAM;AAAA,UAAEA,EAAE,QAAQ,IAAIA,EAAE,KAAK,MAAM;AAAA,QAAA,EAAA,GAD3BA,EAAE,EAEf,CACD;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GACF;AAEJ;AC7EO,SAASG,GAAkB;AAAA,EAChC,KAAA1E;AAAA,EACA,QAAAC;AAAA,EACA,cAAA0E,IAAe;AAAA,EACf,cAAAC,IAAe;AAAA,EACf,OAAOC;AAAA,EACP,eAAAC;AAAA,EACA,OAAOC;AAAA,EACP,eAAAC;AAAA,EACA,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,iBAAAC,IAAkB;AAAA,EAClB,QAAArE,IAAS;AAAA,EACT,YAAAF,IAAa;AAAA,EACb,SAAAC,IAAU;AAAA,EACV,KAAAH;AAAA,EACA,SAAAC;AAAA,EACA,UAAAyE;AAAA,EACA,gBAAAC;AAAA,EACA,WAAApE,IAAY;AAAA,EACZ,OAAAC;AAAA,EACA,WAAAG;AACF,GAA2B;AACzB,QAAM,CAACiE,GAAeC,CAAgB,IAAIC,EAAyBb,CAAY,GACzE,CAACc,GAAeC,CAAgB,IAAIF,EAAyBZ,CAAY,GAEzEe,IAAed,MAAoB,SAAYA,IAAkBS,GACjEM,IAAeb,MAAoB,SAAYA,IAAkBU,GAEjEI,IAAoB,CAAC1B,GAAaC,MAAyB;AAC/D,IAAIS,MAAoB,UAAWU,EAAiBpB,CAAG,GACvDW,KAAA,QAAAA,EAAgBX,GAAKC;AAAA,EACvB,GAEM0B,IAAoB,CAAC3B,GAAaC,MAAyB;AAC/D,IAAIW,MAAoB,UAAWW,EAAiBvB,CAAG,GACvDa,KAAA,QAAAA,EAAgBb,GAAKC;AAAA,EACvB,GAEM2B,IAAiB,OAAOjF,KAAW,WAAW,GAAGA,CAAM,OAAOA;AAEpE,2BACG,OAAA,EAAI,WAAW,sBAAsBG,CAAS,IAAI,OAAAC,GACjD,UAAA;AAAA,IAAA,gBAAA8B,EAAC,SAAI,WAAU,0BAAyB,OAAO,EAAE,QAAQ+C,KACvD,UAAA,gBAAA/C;AAAA,MAACpD;AAAA,MAAA;AAAA,QACC,KAAKyB;AAAA,QACL,KAAArB;AAAA,QACA,QAAAC;AAAA,QACA,YAAY0F;AAAA,QACZ,YAAYC;AAAA,QACZ,YAAAhF;AAAA,QACA,SAAAC;AAAA,QACA,KAAAH;AAAA,QACA,SAAAC;AAAA,QACA,UAAAyE;AAAA,QACA,gBAAAC;AAAA,MAAA;AAAA,IAAA,GAEJ;AAAA,IAECF,MAAoB,UACnB,gBAAAnC,EAAC,OAAA,EAAI,WAAU,0BACZ,UAAAmC,MAAoB,WACnB,gBAAAtB,EAAAmC,GAAA,EACE,UAAA;AAAA,MAAA,gBAAAhD;AAAA,QAACe;AAAA,QAAA;AAAA,UACC,OAAO4B;AAAA,UACP,UAAUE;AAAA,UACV,SAASZ;AAAA,QAAA;AAAA,MAAA;AAAA,MAEX,gBAAAjC;AAAA,QAACyB;AAAA,QAAA;AAAA,UACC,OAAOmB;AAAA,UACP,UAAUE;AAAA,UACV,SAASZ;AAAA,QAAA;AAAA,MAAA;AAAA,IACX,EAAA,CACF,IAEA,gBAAArB,EAAAmC,GAAA,EACE,UAAA;AAAA,MAAA,gBAAAhD;AAAA,QAACC;AAAA,QAAA;AAAA,UACC,OAAO0C;AAAA,UACP,UAAUE;AAAA,UACV,SAASZ;AAAA,UACT,SAASE;AAAA,QAAA;AAAA,MAAA;AAAA,MAEX,gBAAAnC;AAAA,QAACqB;AAAA,QAAA;AAAA,UACC,OAAOuB;AAAA,UACP,UAAUE;AAAA,UACV,SAASZ;AAAA,UACT,SAASC;AAAA,QAAA;AAAA,MAAA;AAAA,IACX,EAAA,CACF,EAAA,CAEJ;AAAA,EAAA,GAEJ;AAEJ;ACnGO,SAASc,GACdxF,IAAuC,IACX;AAC5B,QAAM;AAAA,IACJ,cAAAkE,IAAe;AAAA,IACf,cAAAC,IAAe;AAAA,IACf,cAAAK,IAAe7B;AAAA,IACf,cAAA8B,IAAeZ;AAAA,EAAA,IACb7D,GAEE,CAACyF,GAAOC,CAAQ,IAAIX,EAAyBb,CAAY,GACzD,CAACyB,GAAOC,CAAQ,IAAIb,EAAyBZ,CAAY,GAEzD0B,IAAcC,EAAQ,MAAM5C,EAAeuC,CAAK,GAAG,CAACA,CAAK,CAAC,GAC1DM,IAAcD,EAAQ,MAAM/B,EAAe4B,CAAK,GAAG,CAACA,CAAK,CAAC,GAE1DK,IAAcF;AAAA,IAClB,OAAO;AAAA,MACL,YAAYL;AAAA,MACZ,YAAYE;AAAA,IAAA;AAAA,IAEd,CAACF,GAAOE,CAAK;AAAA,EAAA;AAGf,SAAO;AAAA,IACL,OAAAF;AAAA,IACA,UAAAC;AAAA,IACA,OAAAC;AAAA,IACA,UAAAC;AAAA,IACA,aAAAC;AAAA,IACA,aAAAE;AAAA,IACA,cAAAvB;AAAA,IACA,cAAAC;AAAA,IACA,aAAAuB;AAAA,EAAA;AAEJ;"}
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .td-swatch-group{display:flex;flex-direction:column;gap:8px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif}.td-swatch-header{display:flex;justify-content:space-between;align-items:baseline;font-size:13px;font-weight:500;color:#333}.td-swatch-title{text-transform:uppercase;letter-spacing:.05em;font-size:11px;font-weight:600;color:#666}.td-swatch-selected-name{font-weight:600;color:#111}.td-swatch-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}.td-swatch-pill{display:inline-flex;align-items:center;gap:8px;padding:6px 12px;border-radius:9999px;border:1px solid #e2e8f0;background:#fff;color:#1e293b;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-shadow:0 1px 2px #0000000a}.td-swatch-pill:hover{border-color:#cbd5e1;background:#f8fafc;transform:translateY(-1px)}.td-swatch-pill.active{border-color:#0f172a;background:#0f172a;color:#fff;box-shadow:0 2px 4px #0f172a33}.td-swatch-dot{position:relative;width:32px;height:32px;border-radius:50%;border:2px solid transparent;padding:2px;background:transparent;cursor:pointer;transition:all .15s ease-in-out;outline:none;display:inline-flex;align-items:center;justify-content:center}.td-swatch-dot:hover{transform:scale(1.1)}.td-swatch-dot.active{border-color:#0f172a;transform:scale(1.08)}.td-swatch-color-circle{width:100%;height:100%;border-radius:50%;border:1px solid rgba(0,0,0,.12);box-shadow:inset 0 1px 2px #fff6,0 1px 2px #00000014;display:inline-block;flex-shrink:0}.td-swatch-pill .td-swatch-color-circle{width:14px;height:14px}.td-select-wrapper{display:flex;flex-direction:column;gap:6px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif}.td-select{padding:8px 12px;border-radius:8px;border:1px solid #cbd5e1;background-color:#fff;font-size:14px;color:#1e293b;outline:none;cursor:pointer;transition:border-color .15s}.td-select:focus{border-color:#0f172a;box-shadow:0 0 0 2px #0f172a1a}.td-customizer-card{display:flex;flex-direction:column;gap:16px;background:#fff;border-radius:16px;border:1px solid #e2e8f0;padding:16px;box-shadow:0 4px 6px -1px #0000000d,0 2px 4px -2px #0000000d}.td-customizer-viewport{position:relative;width:100%;border-radius:12px;overflow:hidden;background:#f8fafc}.td-customizer-controls{display:flex;flex-direction:column;gap:14px}
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@thinkersteps_labs/3d-viewer-react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.1",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/lalitthinker/3d-jewelry-viewer.git",
8
+ "directory": "packages/react"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0",
29
+ "three": ">=0.150.0",
30
+ "@thinkersteps_labs/3d-viewer-core": "1.0.1"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.11.0",
34
+ "@types/react": "^18.2.0",
35
+ "@vitejs/plugin-react": "^4.2.0",
36
+ "react": "^18.2.0",
37
+ "three": "^0.160.0",
38
+ "vite": "^5.0.0",
39
+ "vite-plugin-dts": "^3.7.0",
40
+ "@thinkersteps_labs/3d-viewer-core": "1.0.1"
41
+ },
42
+ "scripts": {
43
+ "build": "vite build",
44
+ "typecheck": "tsc --noEmit"
45
+ }
6
46
  }