Appearance
HTML elements
@jbaysolutions/floorplan-elements is the library as standard HTML elements (web components). They work in plain HTML pages, Angular, Svelte, Vue 2, or anything else that renders HTML. Vue is bundled inside the package: your app doesn't install it, import it or need to know about it.
Every feature works the same as in the other packages: floors, markers, pools, themes, add-on packs. Plans use the same JSON format. The component pages list every option, event and method: Editor, 2D viewer, Markers and 3D viewer.
Using React or Vue 3?
React apps should use @jbaysolutions/floorplan-react, which wraps these elements as React components. Vue 3 apps should use @jbaysolutions/floorplan-vue: it uses your app's Vue and is smaller.
At a glance
| On the elements | |
|---|---|
| Elements | <floorplan-editor>, <floorplan-viewer>, <floorplan-viewer-3d> |
| Import from | @jbaysolutions/floorplan-elements; the 3D element from @jbaysolutions/floorplan-elements/3d |
| Stylesheet | Added to the page when the first element appears |
| Data in | Properties (el.plan = plan); simple values also as kebab-case attributes |
| Events out | DOM events with a single detail object: plan-click, change… |
| Methods | Called on the element: el.fit(), el.focusMarker('ap-1') |
| Custom markers | The markerRenderer property: returns an HTML string or a DOM node |
Install
bash
npm install @jbaysolutions/floorplan-elements
npm install three # only for <floorplan-viewer-3d>
npm install @jbaysolutions/floorplan-pack-garden # optional add-on packImporting the package defines the elements:
js
import '@jbaysolutions/floorplan-elements' // defines <floorplan-editor> and <floorplan-viewer>
import '@jbaysolutions/floorplan-elements/3d' // defines <floorplan-viewer-3d> (loads three.js)Defining is skipped for a tag that is already defined, so importing twice is safe.
The stylesheet
The stylesheet is added to the page automatically when the first element appears. It goes first in <head>, so your own CSS can override it. The elements render without a shadow root, so the library's styles and your CSS apply to them as to any other part of the page.
To link it yourself instead (for example with a strict Content Security Policy), link @jbaysolutions/floorplan-elements/style.css and call injectStyles(false) right after importing, before the elements first render:
js
import { injectStyles } from '@jbaysolutions/floorplan-elements'
injectStyles(false)Size
Elements are blocks that fill the size you give them (minimum 240 px). Set a height, as with any block:
css
floorplan-viewer { height: 600px; }Properties and attributes
Set data on the element as properties. Objects and arrays must be properties:
js
const viewer = document.querySelector('floorplan-viewer')
viewer.plan = plan // a FloorPlan
viewer.markers = markers // Marker[]
viewer.highlightRooms = ['r12']Simple values (strings, numbers, booleans) also work as attributes, in kebab-case:
html
<floorplan-viewer theme="paper" show-grid markers-draggable marker-size="36"></floorplan-viewer>
<floorplan-viewer-3d mode="exploded" auto-rotate></floorplan-viewer-3d>Property names are the components' options in camelCase (showGrid, markersDraggable…). The editor's plan property is plan (v-model in the Vue package).
Values the user changes are kept current on the element: editor.plan, viewer.floor, viewer3d.mode.
Events
The elements dispatch standard DOM events. They bubble, and each event's detail is a single object:
| Element | Event | detail |
|---|---|---|
| viewers | plan-click | { x, y, room, floor, originalEvent } |
| viewers | marker-click | { marker, originalEvent } |
<floorplan-viewer> | marker-drag, marker-move | { marker, x, y, room, floor } |
| viewers | room-hover | { room } (or { room: null }) |
| all | floor-change | { floor } (the floor id) |
<floorplan-viewer-3d> | mode-change | { mode } |
<floorplan-editor> | change | { plan } after every completed edit |
<floorplan-editor> | selection-change | { selection } |
js
viewer.addEventListener('plan-click', (e) => {
const { x, y, room, floor } = e.detail
})The viewers never change your data: on marker-move, update the marker in your own array and set viewer.markers again.
Methods
The elements have the same methods as the components, called on the element: viewer.fit(), viewer.focusMarker('ap-1'), viewer3d.snapshot(), editor.undo(), editor.addFloor('above')…
One rename: the viewers' focus(x, y) is focusPoint(x, y) on the elements, so the browser's own element.focus() keeps working.
Custom markers
The Vue marker slot becomes a markerRenderer property on both viewers: a function returning an HTML string or a DOM node.
js
viewer.markerRenderer = (marker, { selected }) =>
`<div class="reading ${selected ? 'on' : ''}">${marker.data.temp}°</div>`The second argument is { selected, dragging, zoom } in 2D and { selected, occluded } in 3D. The function is called again when the marker or its state changes. Your content is anchored at the marker's point by its top-left corner: centre it with transform: translate(-50%, -50%).
WARNING
An HTML string is inserted as it is. Escape any text that comes from users, or build a DOM node instead.
Add-on packs and helpers
Register packs with the functions exported by this package (they register into the elements' own copy of the library):
js
import { registerPack } from '@jbaysolutions/floorplan-elements'
import { registerPack3D } from '@jbaysolutions/floorplan-elements/3d'
import { garden } from '@jbaysolutions/floorplan-pack-garden'
import { garden3D } from '@jbaysolutions/floorplan-pack-garden/3d'
registerPack(garden)
registerPack3D(garden3D)The data helpers are exported too: createEmptyPlan, normalizePlan, createFloor, polygonArea, formatArea, FURNITURE_CATALOG, POOL_FINISHES and the others listed in Data model, plus THEMES_3D from /3d. See Add-on packs for what the packs contain.
TypeScript
Types are included: the data types (FloorPlan, Marker, PlanClickEvent…), the element interfaces (FloorplanEditor, FloorplanViewer, and FloorplanViewer3D from /3d), the event maps (FloorplanViewerEventMap…) and MarkerRenderer. The element names are added to HTMLElementTagNameMap, so queries and listeners are typed:
ts
const viewer = document.querySelector('floorplan-viewer')! // FloorplanViewer
viewer.addEventListener('marker-move', (e) => {
e.detail.marker // typed
})Plain HTML (no bundler)
Use an import map to resolve the packages, for example from a CDN that serves npm packages or from your own server:
html
<script type="importmap">
{
"imports": {
"@jbaysolutions/floorplan-elements": "/vendor/floorplan-elements/dist/elements.js",
"@jbaysolutions/floorplan-elements/3d": "/vendor/floorplan-elements/dist/3d.js",
"three": "/vendor/three/build/three.module.js",
"three/": "/vendor/three/"
}
}
</script>
<floorplan-viewer id="plan" theme="mist" style="height: 600px"></floorplan-viewer>
<script type="module">
import { normalizePlan } from '@jbaysolutions/floorplan-elements'
const el = document.getElementById('plan')
el.plan = normalizePlan(await (await fetch('/api/plan')).json())
el.addEventListener('plan-click', (e) => console.log(e.detail.x, e.detail.y, e.detail.room?.name))
</script>Only the 3D element needs three.
In other frameworks
The elements work wherever you can set properties and listen to DOM events.
ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import '@jbaysolutions/floorplan-elements'
@Component({
selector: 'app-plan',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<floorplan-viewer [plan]="plan" [markers]="markers" theme="paper" style="display:block;height:600px"
(plan-click)="pick($event.detail)"></floorplan-viewer>`,
})
export class PlanComponent { /* plan, markers, pick() */ }vue
<template>
<floorplan-viewer :plan.prop="plan" :markers.prop="markers" style="height: 600px"
@plan-click="onClick($event.detail)"></floorplan-viewer>
</template>
<script>
// once, where you create the app:
// import '@jbaysolutions/floorplan-elements'
// Vue.config.ignoredElements = [/^floorplan-/]
export default { /* data: plan, markers; methods: onClick */ }
</script>Angular: allow custom elements in the component (or module) with CUSTOM_ELEMENTS_SCHEMA, then bind properties with [...] and events with (...) as usual.
Vue 2: Vue 2 apps can't use @jbaysolutions/floorplan-vue (it needs Vue 3), but they can use the elements: their own Vue 3 is sealed inside and never meets the app's Vue 2. Tell Vue 2 to leave the tags alone with Vue.config.ignoredElements, and bind objects with the .prop modifier.
Svelte, Solid, Lit and others use their normal property and event syntax.
React: use @jbaysolutions/floorplan-react. To use the elements directly instead, React 19 passes objects and arrays to them as properties and listens with on plus the exact event name (onplan-click); React 18 needs a ref to set properties and add listeners.