@object-ui/plugin-map 17.5.0 → 17.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("@object-ui/core"),require("@object-ui/react"),require("@object-ui/components"),require("zod"),require("react-map-gl/maplibre"),require("maplibre-gl/dist/maplibre-gl.css"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`@object-ui/core`,`@object-ui/react`,`@object-ui/components`,`zod`,`react-map-gl/maplibre`,`maplibre-gl/dist/maplibre-gl.css`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ObjectUIPluginMap={},e.React,e.ObjectUICore,e.ObjectUIReact,e.ObjectUIComponents,e.zod,e.react_map_gl_maplibre,e.maplibre_gl_dist_maplibre_gl_css,e.react_jsx_runtime))})(this,function(e,t,n,r,i,a,o,s,c){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var l=Object.create,u=Object.defineProperty,d=Object.getOwnPropertyDescriptor,f=Object.getOwnPropertyNames,p=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,h=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=f(t),a=0,o=i.length,s;a<o;a++)s=i[a],!m.call(e,s)&&s!==n&&u(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=d(t,s))||r.enumerable});return e},g=(e,t,n)=>(n=e==null?{}:l(p(e)),h(t||!e||!e.__esModule||!m.call(e,`default`)?u(n,`default`,{value:e,enumerable:!0}):n,e));t=g(t,1),o=g(o,1);var _=a.z.object({latitudeField:a.z.string().optional(),longitudeField:a.z.string().optional(),locationField:a.z.string().optional(),titleField:a.z.string().optional(),descriptionField:a.z.string().optional(),zoom:a.z.number().optional(),center:a.z.tuple([a.z.number(),a.z.number()]).optional(),style:a.z.string().optional()}),ee=`https://demotiles.maplibre.org/style.json`;function te(e){return e.data?e.data:e.staticData?{provider:`value`,items:e.staticData}:e.objectName?{provider:`object`,object:e.objectName}:null}function v(e){if(e){if(typeof e==`string`){let t=e.split(` `),n=t[0],r=t[1]?.toLowerCase()===`desc`?`desc`:`asc`;return{[n]:r}}if(Array.isArray(e))return e.reduce((e,t)=>(t.field&&t.order&&(e[t.field]=t.order),e),{})}}var y=()=>globalThis.process?.env?.NODE_ENV!==`production`,b=new Set;function x(e){if(!y())return;let t=e?.filter;if(!t||typeof t!=`object`||Array.isArray(t)||!Object.prototype.hasOwnProperty.call(t,`map`))return;let n=t.map;if(!n||typeof n!=`object`)return;let r;try{r=`${e.type??`map`}::${e.objectName??``}::${JSON.stringify(n)}`}catch{r=`${e.type??`map`}::${e.objectName??``}::<unserializable>`}b.has(r)||(b.add(r),console.warn("[ObjectMap] `filter.map` is no longer read as map configuration, so this map is rendering with the DEFAULT field names (`latitude` / `longitude` / `name`). `filter` is the query filter; the map config belongs under the declared `map` input — move it to `schema.map` (`{ type: 'object-map', map: { latitudeField, longitudeField, titleField } }`). The old spelling was never documented and could not survive a `dataSource` binding, whose merged filter is an `and` node with no `map` key at all (objectstack#7121). If `map` is genuinely a field you are filtering on, ignore this — the filter itself is passed through untouched. objectui#4034."))}function S(e){x(e);let t=e.style||e.mapStyle||e.map?.style;if(e.locationField||e.latitudeField)return{locationField:e.locationField,latitudeField:e.latitudeField,longitudeField:e.longitudeField,titleField:e.titleField||`name`,descriptionField:e.descriptionField,zoom:e.zoom,center:e.center,style:t};let n=e.map?e.map:null;if(n){let e=_.safeParse(n);return e.success||console.warn(`[ObjectMap] Invalid map configuration:`,e.error.format()),{...n,style:n.style||t}}return{latitudeField:`latitude`,longitudeField:`longitude`,locationField:`location`,titleField:`name`,descriptionField:`description`,zoom:10,center:[0,0],style:t}}function C(e,t){if(t.latitudeField&&t.longitudeField){let n=e[t.latitudeField],r=e[t.longitudeField];if(typeof n==`number`&&typeof r==`number`)return[n,r]}if(t.locationField){let n=e[t.locationField];if(typeof n==`object`&&n){let e=n.lat||n.latitude,t=n.lng||n.lon||n.longitude;if(typeof e==`number`&&typeof t==`number`)return[e,t]}if(typeof n==`string`){let e=n.split(`,`).map(e=>parseFloat(e.trim()));if(e.length===2&&!isNaN(e[0])&&!isNaN(e[1]))return[e[0],e[1]]}if(Array.isArray(n)&&n.length===2){let e=parseFloat(n[0]),t=parseFloat(n[1]);if(!isNaN(e)&&!isNaN(t))return[e,t]}}return null}function w(e,t,n=50){if(e.length<=1||t>=15)return e.map(e=>({id:e.id,coordinates:e.coordinates,markers:[e],isCluster:!1}));let r=n/2**t,i=new Map;e.forEach(e=>{let t=`${Math.floor(e.coordinates[0]/r)}:${Math.floor(e.coordinates[1]/r)}`;i.has(t)||i.set(t,[]),i.get(t).push(e)});let a=[];return i.forEach((e,t)=>{if(e.length===1)a.push({id:e[0].id,coordinates:e[0].coordinates,markers:e,isCluster:!1});else{let n=e.reduce((e,t)=>e+t.coordinates[0],0)/e.length,r=e.reduce((e,t)=>e+t.coordinates[1],0)/e.length;a.push({id:`cluster-${t}`,coordinates:[n,r],markers:e,isCluster:!0})}}),a}var T=({schema:e,dataSource:a,className:s,onMarkerClick:l,onRowClick:u,onEdit:d,onDelete:f,enableClustering:p,clusterRadius:m=50,...h})=>{let[g,_]=(0,t.useState)([]),[y,b]=(0,t.useState)(!0),[x,T]=(0,t.useState)(null),[E,D]=(0,t.useState)(null),[O,k]=(0,t.useState)(null),[A,ne]=(0,t.useState)(``),j=(0,i.useIsMobile)(),M=(0,t.useRef)(null),[N,P]=(0,t.useState)(null),[F,I]=(0,t.useState)(null),[L,R]=(0,t.useState)(!1),[z,B]=(0,t.useState)(null),V=(0,t.useCallback)(e=>{B(e?.error?.message||`Failed to load the map style/tiles.`)},[]),H=(0,t.useCallback)(()=>{if(typeof navigator>`u`||!navigator.geolocation){I(`Geolocation is not available in this browser.`);return}R(!0),I(null),navigator.geolocation.getCurrentPosition(e=>{let{longitude:t,latitude:n}=e.coords;P({lng:t,lat:n}),R(!1);try{M.current?.flyTo({center:[t,n],zoom:12,duration:800})}catch{}},e=>{R(!1),I(e.message||`Unable to retrieve location.`)},{enableHighAccuracy:!1,timeout:8e3,maximumAge:6e4})},[]),U=te(e),W=(0,t.useMemo)(()=>U,[JSON.stringify(U)]),G=S(e),K=W?.provider===`value`;(0,t.useEffect)(()=>{(async()=>{try{if(b(!0),h.data){let e=h.data;if(Array.isArray(e)){_(e),b(!1);return}}if(e.data){let t=e.data;if(Array.isArray(t)){_(t),b(!1);return}}if(K&&W?.provider===`value`){_(W.items),b(!1);return}if(!a||typeof a.find!=`function`)throw Error(`DataSource required for object/api providers`);if(W?.provider===`object`){let t=W.object,r=(0,n.buildExpandFields)(E?.fields),i=await a.find(t,{$filter:e.filter,$orderby:v(e.sort),...r.length>0?{$expand:r}:{}}),o=(0,n.extractRecords)(i);_(o)}else W?.provider===`api`&&(console.warn(`API provider not yet implemented for ObjectMap`),_([]));b(!1)}catch(e){T(e),b(!1)}})()},[W,a,K,e.filter,e.sort,E]),(0,t.useEffect)(()=>{!K&&a&&(async()=>{try{if(!a)return;let t=W?.provider===`object`?W.object:e.objectName;if(!t)return;let n=await a.getObjectSchema(t);D(n)}catch(e){console.error(`Failed to fetch object schema:`,e)}})()},[e.objectName,a,K,W]);let{markers:q,invalidCount:J}=(0,t.useMemo)(()=>{let e=0;return{markers:g.map((t,n)=>{let r=C(t,G);if(!r)return e++,null;let i=G.titleField?t[G.titleField]:`Marker`,a=G.descriptionField?t[G.descriptionField]:void 0,[o,s]=r;return!isFinite(o)||!isFinite(s)||o<-90||o>90||s<-180||s>180?(e++,null):{id:t.id||t._id||`marker-${n}`,title:i,description:a,coordinates:[s,o],data:t}}).filter(e=>e!==null),invalidCount:e}},[g,G]),Y=(0,t.useMemo)(()=>q.find(e=>e.id===O),[q,O]),[X,Z]=(0,t.useState)(G.zoom||3),Q=(0,r.useNavigationOverlay)({navigation:e.navigation,objectName:e.objectName,onRowClick:u}),$=(0,t.useMemo)(()=>{if(!A.trim())return q;let e=A.toLowerCase();return q.filter(t=>t.title?.toLowerCase().includes(e)||t.description?.toLowerCase().includes(e))},[q,A]),re=(0,t.useMemo)(()=>p??(e.enableClustering||$.length>100)?w($,X,m):$.map(e=>({id:e.id,coordinates:e.coordinates,markers:[e],isCluster:!1})),[$,X,p,m,e]),ie=(0,t.useMemo)(()=>{if(!$.length)return{longitude:G.center?.[1]||0,latitude:G.center?.[0]||0,zoom:G.zoom||2};let e=$.map(e=>e.coordinates[0]),t=$.map(e=>e.coordinates[1]),n=Math.min(...e),r=Math.max(...e),i=Math.min(...t),a=Math.max(...t);return{longitude:(n+r)/2,latitude:(i+a)/2,zoom:G.zoom||3}},[$,G]);return y?(0,c.jsx)(`div`,{className:(0,i.cn)(`min-w-0 overflow-hidden`,s),children:(0,c.jsx)(`div`,{className:`flex items-center justify-center h-96 bg-muted rounded-lg border`,children:(0,c.jsx)(`div`,{className:`text-muted-foreground`,children:`Loading map...`})})}):x?(0,c.jsx)(`div`,{className:(0,i.cn)(`min-w-0 overflow-hidden`,s),children:(0,c.jsx)(`div`,{className:`flex items-center justify-center h-96 bg-muted rounded-lg border`,children:(0,c.jsxs)(`div`,{className:`text-destructive`,children:[`Error: `,x.message]})})}):(0,c.jsxs)(`div`,{className:(0,i.cn)(`min-w-0 overflow-hidden`,s),children:[J>0&&(0,c.jsx)(`div`,{className:`mb-2 p-2 text-sm text-yellow-800 bg-yellow-50 border border-yellow-200 rounded`,children:`${J} record${J===1?``:`s`} with missing or invalid coordinates excluded from the map.`}),q.length>0&&(0,c.jsx)(`div`,{className:`mb-2`,children:(0,c.jsx)(`input`,{type:`text`,value:A,onChange:e=>ne(e.target.value),placeholder:`Search locations…`,className:`w-full px-3 py-2 text-sm border rounded-md bg-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring`})}),(0,c.jsxs)(`div`,{className:`relative border rounded-lg overflow-hidden bg-muted h-[300px] sm:h-[400px] md:h-[500px] lg:h-[600px] w-full`,children:[(0,c.jsxs)(o.default,{ref:e=>{M.current=e},initialViewState:ie,style:{width:`100%`,height:`100%`},mapStyle:G.style||ee,touchZoomRotate:!0,dragRotate:!0,touchPitch:!0,onZoom:e=>Z(Math.round(e.viewState.zoom)),onError:V,children:[z&&(0,c.jsxs)(`div`,{role:`alert`,className:`absolute inset-x-0 top-0 z-20 p-2 text-sm text-center text-amber-900 bg-amber-50 border-b border-amber-200`,children:[`Map failed to load (`,z,`). Markers below are still listed in the search box;`,` `,G.style?`check the configured map style URL.`:`configure a custom map style for production use.`]}),(0,c.jsx)(o.NavigationControl,{position:`top-right`,showCompass:!0,showZoom:!0}),(0,c.jsx)(`button`,{type:`button`,onClick:H,disabled:L,className:`absolute top-2 left-2 z-10 inline-flex items-center justify-center size-9 rounded-md bg-background/95 border shadow-sm hover:bg-background disabled:opacity-50`,"aria-label":`Show my location`,"data-testid":`map-geolocate`,children:(0,c.jsx)(`span`,{"aria-hidden":`true`,children:L?`⏳`:`🧭`})}),N&&(0,c.jsx)(o.Marker,{longitude:N.lng,latitude:N.lat,anchor:`center`,children:(0,c.jsx)(`div`,{className:`size-3 rounded-full bg-blue-500 ring-4 ring-blue-500/30 shadow`,"aria-label":`Your location`,"data-testid":`map-user-location`})}),re.map(e=>e.isCluster?(0,c.jsx)(o.Marker,{longitude:e.coordinates[0],latitude:e.coordinates[1],anchor:`center`,onClick:t=>{t.originalEvent.stopPropagation();try{let t=Math.min(20,Math.max(X+2,8));M.current?.flyTo({center:[e.coordinates[0],e.coordinates[1]],zoom:t,duration:600})}catch{}},children:(0,c.jsx)(`div`,{className:`flex items-center justify-center rounded-full bg-primary text-primary-foreground font-bold text-xs cursor-pointer hover:scale-110 transition-transform shadow-md`,style:{width:Math.min(48,24+e.markers.length*2),height:Math.min(48,24+e.markers.length*2)},title:`${e.markers.length} markers`,"data-testid":`map-cluster`,children:e.markers.length})},e.id):(0,c.jsx)(o.Marker,{longitude:e.coordinates[0],latitude:e.coordinates[1],anchor:`bottom`,onClick:t=>{t.originalEvent.stopPropagation();let n=e.markers[0];k(n.id),Q.handleClick(n.data),l?.(n.data)},children:(0,c.jsx)(`div`,{className:`text-2xl cursor-pointer hover:scale-110 transition-transform`,children:`📍`})},e.id)),Y&&!j&&(0,c.jsx)(o.Popup,{longitude:Y.coordinates[0],latitude:Y.coordinates[1],anchor:`top`,onClose:()=>k(null),closeOnClick:!1,children:(0,c.jsxs)(`div`,{className:`p-2 min-w-[150px] sm:min-w-[200px]`,children:[(0,c.jsx)(`h3`,{className:`font-bold text-sm mb-1`,children:Y.title}),Y.description&&(0,c.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:Y.description}),(0,c.jsxs)(`div`,{className:`mt-2 text-xs flex gap-2`,children:[d&&(0,c.jsx)(`button`,{type:`button`,className:`text-blue-500 hover:underline`,onClick:()=>d(Y.data),children:`Edit`}),f&&(0,c.jsx)(`button`,{type:`button`,className:`text-red-500 hover:underline`,onClick:()=>f(Y.data),children:`Delete`})]})]})})]}),Y&&j&&(0,c.jsxs)(`div`,{className:`absolute inset-x-0 bottom-0 z-30 bg-background border-t shadow-lg rounded-t-2xl p-4 pb-[max(env(safe-area-inset-bottom),1rem)]`,role:`dialog`,"aria-label":`Location details`,"data-testid":`map-mobile-record-sheet`,children:[(0,c.jsx)(`div`,{className:`mx-auto mb-2 h-1 w-10 rounded-full bg-muted`,"aria-hidden":`true`}),(0,c.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,c.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,c.jsx)(`h3`,{className:`text-sm font-semibold truncate`,children:Y.title}),Y.description&&(0,c.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground line-clamp-3`,children:Y.description})]}),(0,c.jsx)(`button`,{type:`button`,onClick:()=>k(null),className:`text-muted-foreground hover:text-foreground text-lg leading-none px-2`,"aria-label":`Close details`,"data-testid":`map-mobile-record-close`,children:`×`})]}),(0,c.jsxs)(`div`,{className:`mt-3 flex flex-wrap gap-2 text-xs`,children:[d&&(0,c.jsx)(`button`,{type:`button`,className:`px-3 py-1.5 rounded-md border bg-card hover:bg-accent`,onClick:()=>d(Y.data),children:`Edit`}),f&&(0,c.jsx)(`button`,{type:`button`,className:`px-3 py-1.5 rounded-md border border-destructive/30 text-destructive hover:bg-destructive/10`,onClick:()=>f(Y.data),children:`Delete`})]})]}),F&&(0,c.jsx)(`div`,{className:`absolute top-2 left-14 right-2 z-10 text-xs px-3 py-1.5 rounded-md bg-destructive/10 text-destructive border border-destructive/30`,children:F})]}),Q.isOverlay&&(0,c.jsx)(i.NavigationOverlay,{...Q,title:`Location Details`,children:e=>(0,c.jsx)(`div`,{className:`space-y-3`,children:Object.entries(e).map(([e,t])=>(0,c.jsxs)(`div`,{className:`flex flex-col`,children:[(0,c.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground uppercase tracking-wide`,children:e.replace(/_/g,` `)}),(0,c.jsx)(`span`,{className:`text-sm`,children:String(t??`—`)})]},e))})})]})},E={filter:!0,sort:!0},D=({schema:e,...t})=>{let{dataSource:n}=(0,r.useSchemaContext)()||{};return(0,c.jsx)(r.ElementDataSourceGate,{schema:e,mapping:E,dataSource:n,testId:`object-map`,errorTitle:`This map’s data source could not be resolved`,children:e=>(0,c.jsx)(T,{schema:e,dataSource:n,...t})})};n.ComponentRegistry.register(`object-map`,D,{namespace:`plugin-map`,label:`Object Map`,category:`view`,inputs:[{name:`objectName`,type:`string`,label:`Object Name`,required:!0},{name:`map`,type:`object`,label:`Map Config`,description:`latitudeField, longitudeField, titleField`}]}),n.ComponentRegistry.register(`map`,D,{namespace:`view`,label:`Map View`,category:`view`,inputs:[{name:`objectName`,type:`string`,label:`Object Name`,required:!0},{name:`map`,type:`object`,label:`Map Config`,description:`latitudeField, longitudeField, titleField`}]}),e.ObjectMap=T,e.ObjectMapRenderer=D});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("@object-ui/core"),require("@object-ui/react"),require("@object-ui/types/zod"),require("@object-ui/components"),require("@object-ui/permissions"),require("react-map-gl/maplibre"),require("maplibre-gl/dist/maplibre-gl.css"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`@object-ui/core`,`@object-ui/react`,`@object-ui/types/zod`,`@object-ui/components`,`@object-ui/permissions`,`react-map-gl/maplibre`,`maplibre-gl/dist/maplibre-gl.css`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ObjectUIPluginMap={},e.React,e.ObjectUICore,e.ObjectUIReact,e.ObjectUITypesZod,e.ObjectUIComponents,e._object_ui_permissions,e.react_map_gl_maplibre,e.maplibre_gl_dist_maplibre_gl_css,e.react_jsx_runtime))})(this,function(e,t,n,r,i,a,o,s,c,l){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var u=Object.create,d=Object.defineProperty,ee=Object.getOwnPropertyDescriptor,f=Object.getOwnPropertyNames,p=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,h=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=f(t),a=0,o=i.length,s;a<o;a++)s=i[a],!m.call(e,s)&&s!==n&&d(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=ee(t,s))||r.enumerable});return e},g=(e,t,n)=>(n=e==null?{}:u(p(e)),h(t||!e||!e.__esModule||!m.call(e,`default`)?d(n,`default`,{value:e,enumerable:!0}):n,e));t=g(t,1),s=g(s,1);function _(e){return e>=-180&&e<180?e:((e+180)%360+360)%360-180}var te=e=>Math.min(85,Math.max(-85,e));function ne(e){if(e.length===0)return null;let t=e.map(([e])=>_(e)).sort((e,t)=>e-t),n=e.map(([,e])=>te(e)),r=t.length,i=t[0]+360-t[r-1],a=0;for(let e=1;e<r;e++){let n=t[e]-t[e-1];n>i&&(i=n,a=e)}let o=t[a],s=t[(a+r-1)%r],c=a===0?s:s+360;return[[o,Math.min(...n)],[c,Math.max(...n)]]}function re(e){let[[t,n],[r,i]]=e;return{longitude:_((t+r)/2),latitude:(n+i)/2}}var ie=`https://demotiles.maplibre.org/style.json`,v=Object.keys(i.ObjectMapConfigSchema.shape).filter(e=>e!==`style`);function ae(e){return(0,n.resolveRecordSourceConfig)(e,`view-data`)}var y=()=>globalThis.process?.env?.NODE_ENV!==`production`,b=new Set;function x(e){if(!y())return;let t=e.filter;if(!t||typeof t!=`object`||Array.isArray(t)||!Object.prototype.hasOwnProperty.call(t,`map`))return;let n=t.map;if(!n||typeof n!=`object`)return;let r;try{r=`${e.type??`map`}::${e.objectName??``}::${JSON.stringify(n)}`}catch{r=`${e.type??`map`}::${e.objectName??``}::<unserializable>`}b.has(r)||(b.add(r),console.warn("[ObjectMap] `filter.map` is no longer read as map configuration, so this map has NO coordinate binding and renders the \"Map configuration required\" refusal (objectui#8169 — the `latitude` / `longitude` / `location` defaults are gone). `filter` is the query filter; the map config belongs under the declared `map` input — move it to `schema.map` (`{ type: 'object-map', map: { latitudeField, longitudeField, titleField } }`). The old spelling was never documented and could not survive a `dataSource` binding, whose merged filter is an `and` node with no `map` key at all (objectstack#7121). If `map` is genuinely a field you are filtering on, ignore this — the filter itself is passed through untouched. objectui#4034."))}var S=new Set;function C(e){if(!y())return;let t=e.style;if(typeof t!=`string`||t===``)return;let n=`${e.type??`map`}::${e.objectName??``}::${t}`;S.has(n)||(S.add(n),console.warn(`[ObjectMap] A top-level \`style\` is NOT read as a map style, so this map is rendering with the DEFAULT public demo tiles and \`${t}\` was dropped. \`style\` is the base schema's INLINE CSS key (a record of CSS properties), which every node may carry; the map style is \`mapStyle\` — named that way precisely to avoid this collision. Write \`mapStyle: '${t}'\` at the top level, or \`map: { style: '${t}' }\` in the declared config block. On a view, write that same declared \`map: { style: ... }\`: ObjectView / ListView flatten \`options.map\` through a whitelist of the keys \`ObjectMapConfigSchema\` declares, and that whitelist carries \`style\` out under the flat spelling \`mapStyle\` (objectui#9950), so the declared block arrives and is read. \`mapStyle\` is the TOP-LEVEL spelling only — inside the \`map\` block it is undeclared, and the flatten drops it. objectui#5017.`))}var w=new Set;function T(e){if(!y())return;let t=v.filter(t=>e[t]!==void 0);if(t.length===0)return;let n=`${e.type??`map`}::${e.objectName??``}::${t.join(`,`)}`;w.has(n)||(w.add(n),console.warn(`[ObjectMap] The \`map\` block configures this map, so these top-level keys are IGNORED: ${t.map(e=>`\`${e}\``).join(`, `)}. The \`map\` block is the authoring shape; the flat top-level spelling is the internal form ObjectView/ListView produce when they flatten \`options.map\`, and what the author wrote outranks it. Move anything you still need into \`map\`, or drop the \`map\` block. objectui#5018.`))}function oe(e){x(e),C(e);let t=e.mapStyle||e.map?.style,n=e.map??null;if(n){let r=i.ObjectMapConfigSchema.safeParse(n);return r.success||console.warn(`[ObjectMap] Invalid map configuration:`,r.error.format()),T(e),{...n,style:t}}return e.locationField||e.latitudeField?{locationField:e.locationField,latitudeField:e.latitudeField,longitudeField:e.longitudeField,titleField:e.titleField,descriptionField:e.descriptionField,zoom:e.zoom,center:e.center,style:t}:{style:t}}function se(e){return!!e.locationField||!!(e.latitudeField&&e.longitudeField)}function ce(e,t){if(t.latitudeField&&t.longitudeField){let n=e[t.latitudeField],r=e[t.longitudeField];if(typeof n==`number`&&typeof r==`number`)return[n,r]}if(t.locationField){let n=e[t.locationField];if(typeof n==`object`&&n){let e=n.lat||n.latitude,t=n.lng||n.lon||n.longitude;if(typeof e==`number`&&typeof t==`number`)return[e,t]}if(typeof n==`string`){let e=n.split(`,`).map(e=>parseFloat(e.trim()));if(e.length===2&&!isNaN(e[0])&&!isNaN(e[1]))return[e[0],e[1]]}if(Array.isArray(n)&&n.length===2){let e=parseFloat(n[0]),t=parseFloat(n[1]);if(!isNaN(e)&&!isNaN(t))return[e,t]}}return null}function le(e,t,n=50){if(e.length<=1||t>=15)return e.map(e=>({id:e.id,coordinates:e.coordinates,markers:[e],isCluster:!1}));let r=n/2**t,i=new Map;e.forEach(e=>{let t=`${Math.floor(e.coordinates[0]/r)}:${Math.floor(e.coordinates[1]/r)}`;i.has(t)||i.set(t,[]),i.get(t).push(e)});let a=[];return i.forEach((e,t)=>{if(e.length===1)a.push({id:e[0].id,coordinates:e[0].coordinates,markers:e,isCluster:!1});else{let n=e.reduce((e,t)=>e+t.coordinates[0],0)/e.length,r=e.reduce((e,t)=>e+t.coordinates[1],0)/e.length;a.push({id:`cluster-${t}`,coordinates:[n,r],markers:e,isCluster:!0})}}),a}var E=({schema:e,dataSource:i,className:c,data:u,onMarkerClick:d,onRowClick:ee,onEdit:f,onDelete:p,enableClustering:m,clusterRadius:h=50})=>{let[g,_]=(0,t.useState)([]),[te,v]=(0,t.useState)(!0),[y,b]=(0,t.useState)(null),[x,S]=(0,t.useState)(null),[C,w]=(0,t.useState)(null),[T,E]=(0,t.useState)(``),D=(0,a.useIsMobile)(),O=(0,t.useRef)(null),[k,ue]=(0,t.useState)(null),[de,A]=(0,t.useState)(null),[fe,j]=(0,t.useState)(!1),[pe,me]=(0,t.useState)(null),he=(0,t.useCallback)(e=>{me(e?.error?.message||`Failed to load the map style/tiles.`)},[]),ge=(0,t.useCallback)(()=>{if(typeof navigator>`u`||!navigator.geolocation){A(`Geolocation is not available in this browser.`);return}j(!0),A(null),navigator.geolocation.getCurrentPosition(e=>{let{longitude:t,latitude:n}=e.coords;ue({lng:t,lat:n}),j(!1);try{O.current?.flyTo({center:[t,n],zoom:12,duration:800})}catch{}},e=>{j(!1),A(e.message||`Unable to retrieve location.`)},{enableHighAccuracy:!1,timeout:8e3,maximumAge:6e4})},[]),M=(0,t.useMemo)(()=>ae(e),[e]),N=(0,t.useMemo)(()=>oe(e),[e]),P=M?.provider===`value`,F=M?.provider,I=M?.provider===`object`?M.object:void 0,L=M?.provider===`value`?M.items:void 0,_e=(0,n.resolveRecordSourceObjectName)(e,M),{ready:ve,def:R}=(0,r.useSettledSchema)(_e??``,P?void 0:i),z=(0,o.usePermissions)(),B=!Array.isArray(u)&&F===`object`,V=(0,r.useDataInvalidation)(B?I:void 0),ye=(0,t.useRef)(V),be=(0,t.useRef)(null),H=(0,t.useRef)(0),xe=(0,r.useFilterScope)(),U=(0,r.useResolvedFilter)(e.filter,xe);(0,t.useEffect)(()=>{if(B&&!ve){v(!0);return}let t=[u,F,I,L,i,P,U,e.sort,R,z],r=be.current,a=r!==null&&r.length===t.length&&t.every((e,t)=>Object.is(e,r[t])),o=V!==ye.current;ye.current=V;let s=o&&a,c=++H.current,l=()=>H.current===c,d=(e,n)=>{l()&&(_(e),S(n),b(null),be.current=t)};return(async()=>{try{if(s||v(!0),Array.isArray(u)){d(u,null);return}if(P&&F===`value`){let t=await new n.ValueDataSource({items:L??[]}).find(``,{$filter:U,$orderby:(0,n.convertSortToQueryParams)(e.sort),...(0,n.nonGridRowCeilingQuery)()}),r=(0,n.applyNonGridRowCeiling)(t);d(r.rows,r);return}if(!i||typeof i.find!=`function`)throw Error(`DataSource required for object/api providers`);if(F===`object`){let t=I,r=(0,n.buildExpandFields)(R?.fields),a=z?.isLoaded?r.filter(e=>z.checkField(t,e,`read`)):r,o=await i.find(t,{$filter:U,$orderby:(0,n.convertSortToQueryParams)(e.sort),...(0,n.nonGridRowCeilingQuery)(),...a.length>0?{$expand:a}:{}}),s=(0,n.applyNonGridRowCeiling)(o);d(s.rows,s)}else F===`api`&&(console.warn(`API provider not yet implemented for ObjectMap`),d([],null))}catch(e){s?console.error(`[ObjectMap] Failed to refresh data:`,e):l()&&b(e)}finally{l()&&v(!1)}})(),()=>{H.current+=1}},[u,F,I,L,i,P,U,e.sort,ve,R,z,V,B]);let{markers:W,invalidCount:G}=(0,t.useMemo)(()=>{let e=0;return{markers:g.map((t,r)=>{let i=ce(t,N);if(!i)return e++,null;let a=(0,n.getRecordDisplayName)(R,t,{titleField:N.titleField,fallback:`Marker`}),o=(0,n.recordDisplayValueAt)(t,N.descriptionField),[s,c]=i;return!isFinite(s)||!isFinite(c)||s<-90||s>90||c<-180||c>180?(e++,null):{id:t.id||t._id||`marker-${r}`,title:a,description:o,coordinates:[c,s],data:t}}).filter(e=>e!==null),invalidCount:e}},[g,N,R]),K=(0,t.useMemo)(()=>W.find(e=>e.id===C),[W,C]),[q,Se]=(0,t.useState)(N.zoom||3),Ce=(0,t.useCallback)(e=>{try{let t=e?.target?.getZoom?.();typeof t==`number`&&Number.isFinite(t)&&Se(Math.round(t))}catch{}},[]),J=(0,r.useNavigationOverlay)({navigation:e.navigation,objectName:e.objectName,onRowClick:ee}),Y=(0,t.useMemo)(()=>{if(!T.trim())return W;let e=T.toLowerCase();return W.filter(t=>t.title?.toLowerCase().includes(e)||t.description?.toLowerCase().includes(e))},[W,T]),we=(0,t.useMemo)(()=>m??(e.enableClustering||Y.length>100)?le(Y,q,h):Y.map(e=>({id:e.id,coordinates:e.coordinates,markers:[e],isCluster:!1})),[Y,q,m,h,e]),X=(0,t.useMemo)(()=>ne(Y.map(e=>e.coordinates)),[Y]),Z=typeof N.center?.[0]==`number`?N.center[0]:void 0,Q=typeof N.center?.[1]==`number`?N.center[1]:void 0,$=typeof N.zoom==`number`?N.zoom:void 0,Te=Z!==void 0||Q!==void 0||$!==void 0,Ee=(0,t.useMemo)(()=>{if(X&&!Te)return{bounds:X,fitBoundsOptions:{padding:48,maxZoom:12}};let e=X?re(X):{longitude:0,latitude:0};return{longitude:Q??e.longitude,latitude:Z??e.latitude,zoom:$??(X?3:2)}},[X,Te,Q,Z,$]);return se(N)?te?(0,l.jsx)(`div`,{className:(0,a.cn)(`min-w-0 overflow-hidden`,c),children:(0,l.jsx)(`div`,{className:`flex items-center justify-center h-96 bg-muted rounded-lg border`,children:(0,l.jsx)(`div`,{className:`text-muted-foreground`,children:`Loading map...`})})}):y?(0,l.jsx)(`div`,{className:(0,a.cn)(`min-w-0 overflow-hidden`,c),children:(0,l.jsx)(`div`,{className:`flex items-center justify-center h-96 bg-muted rounded-lg border`,children:(0,l.jsxs)(`div`,{className:`text-destructive`,children:[`Error: `,y.message]})})}):(0,l.jsxs)(`div`,{className:(0,a.cn)(`min-w-0 overflow-hidden`,c),children:[G>0&&(0,l.jsx)(`div`,{className:`mb-2 p-2 text-sm text-yellow-800 bg-yellow-50 border border-yellow-200 rounded`,children:`${G} record${G===1?``:`s`} with missing or invalid coordinates excluded from the map.`}),W.length>0&&(0,l.jsx)(`div`,{className:`mb-2`,children:(0,l.jsx)(`input`,{type:`text`,value:T,onChange:e=>E(e.target.value),placeholder:`Search locations…`,className:`w-full px-3 py-2 text-sm border rounded-md bg-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring`})}),(0,l.jsxs)(`div`,{className:`relative border rounded-lg overflow-hidden bg-muted h-[300px] sm:h-[400px] md:h-[500px] lg:h-[600px] w-full`,children:[(0,l.jsxs)(s.default,{ref:e=>{O.current=e},initialViewState:Ee,style:{width:`100%`,height:`100%`},mapStyle:N.style||ie,touchZoomRotate:!0,dragRotate:!0,touchPitch:!0,onLoad:Ce,onZoom:e=>Se(Math.round(e.viewState.zoom)),onError:he,children:[pe&&(0,l.jsxs)(`div`,{role:`alert`,className:`absolute inset-x-0 top-0 z-20 p-2 text-sm text-center text-amber-900 bg-amber-50 border-b border-amber-200`,children:[`Map failed to load (`,pe,`). Markers below are still listed in the search box;`,` `,N.style?`check the configured map style URL.`:`configure a custom map style for production use.`]}),(0,l.jsx)(s.NavigationControl,{position:`top-right`,showCompass:!0,showZoom:!0}),(0,l.jsx)(`button`,{type:`button`,onClick:ge,disabled:fe,className:`absolute top-2 left-2 z-10 inline-flex items-center justify-center size-9 rounded-md bg-background/95 border shadow-sm hover:bg-background disabled:opacity-50`,"aria-label":`Show my location`,"data-testid":`map-geolocate`,children:(0,l.jsx)(`span`,{"aria-hidden":`true`,children:fe?`⏳`:`🧭`})}),k&&(0,l.jsx)(s.Marker,{longitude:k.lng,latitude:k.lat,anchor:`center`,children:(0,l.jsx)(`div`,{className:`size-3 rounded-full bg-blue-500 ring-4 ring-blue-500/30 shadow`,"aria-label":`Your location`,"data-testid":`map-user-location`})}),we.map(e=>e.isCluster?(0,l.jsx)(s.Marker,{longitude:e.coordinates[0],latitude:e.coordinates[1],anchor:`center`,onClick:t=>{t.originalEvent.stopPropagation();try{let t=Math.min(20,Math.max(q+2,8));O.current?.flyTo({center:[e.coordinates[0],e.coordinates[1]],zoom:t,duration:600})}catch{}},children:(0,l.jsx)(`div`,{className:`flex items-center justify-center rounded-full bg-primary text-primary-foreground font-bold text-xs cursor-pointer hover:scale-110 transition-transform shadow-md`,style:{width:Math.min(48,24+e.markers.length*2),height:Math.min(48,24+e.markers.length*2)},title:`${e.markers.length} markers`,"data-testid":`map-cluster`,children:e.markers.length})},e.id):(0,l.jsx)(s.Marker,{longitude:e.coordinates[0],latitude:e.coordinates[1],anchor:`bottom`,onClick:t=>{t.originalEvent.stopPropagation();let n=e.markers[0];w(n.id),J.handleClick(n.data),d?.(n.data)},children:(0,l.jsx)(`div`,{className:`text-2xl cursor-pointer hover:scale-110 transition-transform`,children:`📍`})},e.id)),K&&!D&&(0,l.jsx)(s.Popup,{longitude:K.coordinates[0],latitude:K.coordinates[1],anchor:`top`,onClose:()=>w(null),closeOnClick:!1,children:(0,l.jsxs)(`div`,{className:`p-2 min-w-[150px] sm:min-w-[200px]`,children:[(0,l.jsx)(`h3`,{className:`font-bold text-sm mb-1`,children:K.title}),K.description&&(0,l.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:K.description}),(0,l.jsxs)(`div`,{className:`mt-2 text-xs flex gap-2`,children:[f&&(0,l.jsx)(`button`,{type:`button`,className:`text-blue-500 hover:underline`,onClick:()=>f(K.data),children:`Edit`}),p&&(0,l.jsx)(`button`,{type:`button`,className:`text-red-500 hover:underline`,onClick:()=>p(K.data),children:`Delete`})]})]})})]}),K&&D&&(0,l.jsxs)(`div`,{className:`absolute inset-x-0 bottom-0 z-30 bg-background border-t shadow-lg rounded-t-2xl p-4 pb-[max(env(safe-area-inset-bottom),1rem)]`,role:`dialog`,"aria-label":`Location details`,"data-testid":`map-mobile-record-sheet`,children:[(0,l.jsx)(`div`,{className:`mx-auto mb-2 h-1 w-10 rounded-full bg-muted`,"aria-hidden":`true`}),(0,l.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,l.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,l.jsx)(`h3`,{className:`text-sm font-semibold truncate`,children:K.title}),K.description&&(0,l.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground line-clamp-3`,children:K.description})]}),(0,l.jsx)(`button`,{type:`button`,onClick:()=>w(null),className:`text-muted-foreground hover:text-foreground text-lg leading-none px-2`,"aria-label":`Close details`,"data-testid":`map-mobile-record-close`,children:`×`})]}),(0,l.jsxs)(`div`,{className:`mt-3 flex flex-wrap gap-2 text-xs`,children:[f&&(0,l.jsx)(`button`,{type:`button`,className:`px-3 py-1.5 rounded-md border bg-card hover:bg-accent`,onClick:()=>f(K.data),children:`Edit`}),p&&(0,l.jsx)(`button`,{type:`button`,className:`px-3 py-1.5 rounded-md border border-destructive/30 text-destructive hover:bg-destructive/10`,onClick:()=>p(K.data),children:`Delete`})]})]}),de&&(0,l.jsx)(`div`,{className:`absolute top-2 left-14 right-2 z-10 text-xs px-3 py-1.5 rounded-md bg-destructive/10 text-destructive border border-destructive/30`,children:de})]}),x&&(0,l.jsx)(r.NonGridRowCeilingNote,{result:x}),J.isOverlay&&(0,l.jsx)(a.NavigationOverlay,{...J,title:`Location Details`,children:e=>(0,l.jsx)(`div`,{className:`space-y-3`,children:Object.entries(e).map(([e,t])=>(0,l.jsxs)(`div`,{className:`flex flex-col`,children:[(0,l.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground uppercase tracking-wide`,children:e.replace(/_/g,` `)}),(0,l.jsx)(`span`,{className:`text-sm`,children:String(t??`—`)})]},e))})})]}):(0,l.jsx)(`div`,{className:(0,a.cn)(`min-w-0 overflow-hidden`,c),children:(0,l.jsx)(`div`,{className:`flex items-center justify-center h-96 bg-muted rounded-lg border p-4 text-center`,"data-testid":`map-missing-location-binding`,role:`alert`,children:(0,l.jsxs)(`div`,{className:`text-destructive`,children:[`Map configuration required — declare `,(0,l.jsx)(`code`,{className:`font-mono`,children:`map.locationField`}),` `,`or `,(0,l.jsx)(`code`,{className:`font-mono`,children:`map.latitudeField`}),` +`,` `,(0,l.jsx)(`code`,{className:`font-mono`,children:`map.longitudeField`})]})})})},D={filter:!0,sort:!0},O=(0,n.elementDataSourceBlock)(({schema:e,...t})=>{let{dataSource:n}=(0,r.useSchemaContext)()||{};return(0,l.jsx)(r.ElementDataSourceGate,{schema:e,mapping:D,dataSource:n,testId:`object-map`,errorTitle:`This map’s data source could not be resolved`,children:e=>(0,l.jsx)(E,{schema:e,dataSource:n,...t})})});n.ComponentRegistry.register(`object-map`,O,{namespace:`plugin-map`,label:`Object Map`,category:`view`,inputs:[{name:`objectName`,type:`string`,description:"ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`, or the node’s `dataSource` binding; the `object-map` schema refuses a block that declares none of them."},{name:`map`,type:`object`,description:'The field configuration: `latitudeField` / `longitudeField` (or one `locationField` holding a `"lat,lng"` string, a `{ lat, lng }` object or a `[lat, lng]` pair) place each record’s marker, and a record with no readable coordinates is not plotted; `titleField` and `descriptionField` name what the marker popup shows; `zoom` and `center` (`[latitude, longitude]`) set the initial camera, which otherwise fits the markers; `style` is a MapLibre style URL, read after the node’s `mapStyle`.'},{name:`data`,type:`object`,description:"A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a map carrying one never reaches `staticData` and never queries `objectName`. `{ provider: 'value', items }` plots those rows and `{ provider: 'object', object }` queries that object, both narrowed by `filter` and ordered by `sort`. The `api` provider is not implemented on the map and plots no markers. A bare array is not this key’s shape and is not a record source: the map falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`."},{name:`staticData`,type:`array`,description:"Inline records, read SECOND on the record-source ladder: a `data` configuration wins and this key is then never reached, while `objectName` is read AFTER it, so a map carrying both plots these rows and never queries that object. `filter` and `sort` narrow and order these rows exactly as they do fetched ones."},{name:`filter`,type:`array`,description:"Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the markers the map plots — fetched rows and inline (`staticData`, `{ provider: 'value' }`) rows alike. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape."},{name:`sort`,type:`array`,description:"Marker order in `[{ field, order }]` form, ordering the rows the map plots. Lowered to `$orderby` on the same query."},{name:`mapStyle`,type:`string`,description:"MapLibre style URL or spec, overriding the public demo tiles. Read before `map.style`; NOT the base node `style`, which is an inline CSS record"},{name:`navigation`,type:`object`,description:"What a marker click opens — the `{ mode, size, openNewTab, preventNavigation }` block a list view declares. With the key ABSENT a click opens nothing: this renderer supplies no drawer default. `mode` is an overlay (`drawer`, `modal`, `split`, `popover`), `new_window`, `page` or `none`, and a block written without `mode` takes the spec’s `page` default. `drawer`, `modal` and `popover` open the marker’s record in that overlay, but `split` opens nothing, because the map hands the split shell no main panel; `new_window` opens the record page in a new tab; `none` opens nothing. `page` opens the record page of the map’s `objectName` through the record navigator the host publishes (the console publishes one on its custom pages, record pages and list views); under a host that publishes none, such as an embedded renderer, or on a map that names no `objectName`, there is no record page to open and the click opens nothing. `preventNavigation: true` opens nothing whatever the mode, `openNewTab: true` opens the record page in a new tab and outranks every mode except `none`, and `size` sets the overlay width. A click handler from a parent view outranks the whole key."},{name:`enableClustering`,type:`boolean`,description:"Group nearby markers into one numbered cluster. Absent, the map clusters only above 100 markers; `false` turns clustering off at any count."}]}),e.ObjectMap=E,e.ObjectMapRenderer=O});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-map",
3
- "version": "17.5.0",
3
+ "version": "17.7.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Map visualization plugin for Object UI",
@@ -24,15 +24,13 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@objectstack/spec": "^17.0.0-rc.6",
28
- "lucide-react": "^1.29.0",
29
- "maplibre-gl": "^6.2.0",
27
+ "maplibre-gl": "^6.4.1",
30
28
  "react-map-gl": "^8.1.2",
31
- "zod": "^4.4.3",
32
- "@object-ui/components": "17.5.0",
33
- "@object-ui/core": "17.5.0",
34
- "@object-ui/react": "17.5.0",
35
- "@object-ui/types": "17.5.0"
29
+ "@object-ui/components": "17.7.0",
30
+ "@object-ui/core": "17.7.0",
31
+ "@object-ui/permissions": "17.7.0",
32
+ "@object-ui/react": "17.7.0",
33
+ "@object-ui/types": "17.7.0"
36
34
  },
37
35
  "peerDependencies": {
38
36
  "react": "^18.0.0 || ^19.0.0",
@@ -44,7 +42,9 @@
44
42
  "@vitejs/plugin-react": "^6.0.5",
45
43
  "typescript": "^6.0.3",
46
44
  "vite": "^8.2.1",
47
- "vite-plugin-dts": "^5.0.3"
45
+ "vite-plugin-dts": "^5.0.3",
46
+ "@object-ui/plugin-list": "17.7.0",
47
+ "@object-ui/sdui-parser": "17.7.0"
48
48
  },
49
49
  "keywords": [
50
50
  "objectui",
@@ -71,8 +71,8 @@
71
71
  ],
72
72
  "scripts": {
73
73
  "build": "vite build",
74
- "test": "vitest run",
75
- "test:watch": "vitest",
74
+ "test": "vitest run --root ../.. packages/plugin-map/",
75
+ "test:watch": "vitest --root ../.. packages/plugin-map/",
76
76
  "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
77
77
  "lint": "eslint ."
78
78
  }