<bmx-floor-plan>
Desks, meeting rooms, seats, parking bays and lockers on a floor plan: their live state - available, yours, booked, in use, reserved - shown by colour and by pattern, chosen by click, tap or keyboard, and handed to the page to book. Filters by kind, amenities, size and availability; zones (neighbourhoods) tinted underneath; several floors; zoom and pan with wheel, pinch, buttons and keys; and a list of the same spaces beside the map that is a full alternative to it.
16 properties · 6 events · 12 methods · 7 parts
Example
Choose up to three desks or rooms, then Book.
Show markup
<bmx-floor-plan id="ex-fp" selection-mode="multiple" max-selection="3" style="--bmx-floor-plan-height: 30rem"></bmx-floor-plan>
<p class="note" id="ex-fp-out" role="status" style="margin: 12px 0 0">Choose up to three desks or rooms, then Book.</p>
<script type="module">
await customElements.whenDefined('bmx-floor-plan');
const fp = document.getElementById('ex-fp');
const walls = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 700"><g fill="none" stroke="#9aa0a6" stroke-width="6"><rect x="10" y="10" width="1180" height="680"/><path d="M760 10v260M760 340v350M10 380h300M380 380h380"/></g><g fill="#9aa0a6" font-family="sans-serif" font-size="22"><text x="40" y="660">Kitchen</text><text x="1050" y="660">Lifts</text></g></svg>`;
const spaces = [];
let n = 1;
for (let row = 0; row < 3; row++) for (let col = 0; col < 8; col++) {
const x = 60 + col * 82, y = 60 + row * 100;
spaces.push({ id: `desk-${n}`, name: `Desk ${n}`, kind: 'desk', x, y, w: 70, h: 40, zone: col < 4 ? 'fin' : 'eng', amenities: n % 3 ? ['monitor'] : ['monitor', 'standing'] });
n++;
}
for (let col = 0; col < 6; col++) spaces.push({ id: `desk-${n}`, name: `Desk ${n}`, kind: 'desk', x: 60 + col * 82, y: 430, w: 70, h: 40, zone: 'quiet', amenities: ['monitor'] }), n++;
spaces.push({ id: 'room-a', name: 'Kingfisher', kind: 'room', capacity: 8, x: 800, y: 40, w: 360, h: 200, amenities: ['video', 'whiteboard'] });
spaces.push({ id: 'room-b', name: 'Wren', kind: 'room', capacity: 4, x: 800, y: 370, w: 170, h: 140, amenities: ['video'] });
spaces.push({ id: 'room-c', name: 'Heron', kind: 'room', capacity: 12, points: [[990,370],[1160,370],[1160,560],[1060,560],[990,500]] });
for (let i = 0; i < 5; i++) spaces.push({ id: `seat-${i+1}`, name: `Sofa seat ${i+1}`, kind: 'seat', cx: 560 + i * 44, cy: 600, r: 16 });
spaces.push({ id: 'p1', name: 'Print room', kind: 'area', bookable: false, x: 600, y: 430, w: 130, h: 80 });
fp.plan = { floors: [
{ id: 'g', name: 'Ground floor', width: 1200, height: 700, background: { svg: walls }, zones: [
{ id: 'fin', name: 'Finance', x: 40, y: 40, w: 330, h: 300, color: '#bbdefb' },
{ id: 'eng', name: 'Engineering', x: 370, y: 40, w: 330, h: 300, color: '#c8e6c9' },
{ id: 'quiet', name: 'Quiet zone', x: 40, y: 410, w: 500, h: 80, color: '#fff9c4' } ], spaces },
{ id: '1', name: 'First floor', width: 1200, height: 700, spaces: [{ id: 'f-desk-1', name: 'Desk F1', kind: 'desk', x: 100, y: 100, w: 70, h: 40 }] },
] };
fp.availability = { 'desk-2': 'booked', 'desk-3': { state: 'booked', holder: { name: 'Ana Silva' } }, 'desk-7': 'occupied', 'desk-10': { state: 'mine', holder: { name: 'Sam Reviewer' }, note: 'All day' }, 'desk-15': 'reserved', 'room-b': { state: 'occupied', note: 'until 14:00' }, 'desk-26': 'booked' };
const out = document.getElementById('ex-fp-out');
fp.addEventListener('bmxBook', e => (out.textContent = `Booking ${e.detail.spaces.map(s => s.name).join(', ')} - the page sends this to its own booking service.`));
</script>
<bmx-floor-plan id="office" selection-mode="multiple" max-selection="4"></bmx-floor-plan>
<script>
office.plan = await api.floorPlan('london'); // floors, zones, spaces
office.availability = await api.availability(date); // { 'desk-14': 'booked', ... }
office.addEventListener('bmxBook', e => api.book(e.detail.value, date));
</script>
The plan is plain JSON the page keeps: { floors: [{ id, name, width, height, background, zones, spaces }] }. A floor's drawing can be an image
or SVG by URL, or SVG markup (cleaned of scripts and outside links), and
importSvg turns a drawing's marked shapes (data-space) into spaces.
Availability is separate and can be replaced or merged at any time
(setAvailability), so a live feed can keep the plan current. Nothing is
stored or sent.
mode="edit" draws and edits the plan: desks and rooms by dragging, seats
by clicking, outlines point by point; move, resize, duplicate, delete,
snap to a grid, undo and redo, and a property panel - every change in
bmxPlanChange.
Keyboard: the map is one tab stop; arrows move to the nearest space in that direction, Enter or Space chooses it, Escape clears the choice, + and
- zoom, 0 fits, Ctrl+arrows pan. In edit mode arrows move the selected spaces (Shift ten times as far), Delete deletes, Ctrl+D duplicates, Ctrl+Z and Ctrl+Y undo and redo.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
availability |
availability |
Record<string, BmxSpaceState | BmxSpaceAvailability> | BmxSpaceAvailability[] | string |
{} |
Live states: { id: state | { state, holder, note } }, an array of { id, state, ... }, or JSON. |
colorBy |
color-by |
'state' | 'zone' | 'kind' |
'state' |
What colours a space: its state, its zone, or its kind. |
display |
display |
'map' | 'list' | 'both' |
'both' |
What is shown: the map, the list, or both side by side. Mutable. |
filter |
filter |
BmxFloorFilter | string |
{} |
The filter, as an object or JSON: { kinds, amenities, minCapacity, availableOnly, zone, query }. Mutable: the toolbar changes it. |
floor |
floor |
string |
— | The floor shown, by id. Default: the first. Mutable. |
hideLegend |
hide-legend |
boolean |
false |
Leave out the key to the states. |
hideToolbar |
hide-toolbar |
boolean |
false |
Leave out the toolbar. |
labels |
labels |
'auto' | 'always' | 'never' |
'auto' |
When names are written on the plan: when there is room, always, or never. |
maxSelection |
max-selection |
number |
— | The most that can be chosen in multiple mode. |
mode |
mode |
'view' | 'edit' |
'view' |
view to look and choose, edit to draw and change the plan. |
plan |
plan |
BmxFloorPlanData | string |
{ version: 1, floors: [] } |
The plan: { floors: [...] }, a single floor, or JSON. Mutable: edits write it back. |
selectionMode |
selection-mode |
'none' | 'single' | 'multiple' |
'single' |
How many spaces can be chosen: none, one, or several. |
snapTo |
snap-to |
number |
0 |
Grid step for drawing and moving in edit mode, in plan units. 0 for none. |
spaceColor |
property only | BmxFloorSpaceColor |
— | Colours a space, in place of the built-in colours (from script). |
strings |
strings |
Partial<BmxFloorPlanStrings> | string |
— | Words to show instead of the English ones, as an object or JSON. |
value |
value |
string[] | string |
[] |
The chosen space ids. Mutable. |
Events
| Event | Detail | Description |
|---|---|---|
bmxBook |
BmxFloorSelectionDetail |
Fired when the reader presses Book: book these. |
bmxFloorChange |
{ floor: string; } |
Fired when the floor shown changes. |
bmxPlanChange |
BmxFloorPlanChangeDetail |
Fired after every edit (edit mode): save the plan. |
bmxSelectionChange |
BmxFloorSelectionDetail |
Fired after the chosen spaces change. |
bmxSpaceFocus |
{ space: BmxFloorSpace | null; } |
Fired when a space gets keyboard focus or is pointed at. |
bmxSpaceSelect |
BmxFloorSpaceSelectDetail |
Fired before a space is chosen or let go. Cancel it to refuse. |
Methods
| Method | Signature | Description |
|---|---|---|
clearSelection |
clearSelection() => Promise<void> |
Clear the choice. |
exportJson |
exportJson() => Promise<string> |
The plan as JSON. |
exportSvg |
exportSvg() => Promise<string> |
The floor shown, as a standalone SVG drawing with states and names (for printing or a report). |
fit |
fit() => Promise<void> |
Show the whole floor. |
getPlan |
getPlan() => Promise<BmxFloorPlanData> |
The plan, as it stands (with edits). |
importSvg |
importSvg(svg: string, options?: BmxFloorImportOptions) => Promise<number> |
Bring in an SVG drawing as a floor's background, turning its marked shapes into spaces. Resolves how many spaces were added, or -1 if the text was not SVG. |
redo |
redo() => Promise<boolean> |
Redo the last undone edit. |
select |
select(ids: string[]) => Promise<void> |
Choose spaces (replacing the choice), ignoring any that cannot be chosen. |
setAvailability |
setAvailability(entries: Record<string, BmxSpaceState | BmxSpaceAvailability> | BmxSpaceAvailability[] | string, merge?: boolean) => Promise<void> |
Replace the live states, or merge them into the ones there (merge). |
undo |
undo() => Promise<boolean> |
Undo the last edit. Resolves whether there was one. |
zoomBy |
zoomBy(factor: number) => Promise<void> |
Zoom by a factor (above 1 closer) about the centre. |
zoomToSpace |
zoomToSpace(id: string) => Promise<boolean> |
Show a space - changing floor if it is on another - and make it the one in focus. Resolves whether it was found. |
Slots
| Slot | Description |
|---|---|
header |
Above the plan, beside the floor tabs (a date picker, say). |
CSS shadow parts
| Part | Description |
|---|---|
details |
The card for the space in focus. |
legend |
The key to the states. |
list |
The list of spaces. |
map |
The box the plan is drawn in. |
space |
A space; also space-selected and space-active. |
toolbar |
The row of floor tabs, filters and view buttons. |
tools |
The editing tools. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-floor-plan-available |
The colour of an available space. |
--bmx-floor-plan-booked |
The colour of a booked space. |
--bmx-floor-plan-floor |
The floor's background. |
--bmx-floor-plan-height |
The height of the map. |
--bmx-floor-plan-mine |
The colour of the reader's own space. |
--bmx-floor-plan-occupied |
The colour of a space in use. |
--bmx-floor-plan-reserved |
The colour of a reserved space. |
--bmx-floor-plan-selected |
The ring around a chosen space. |
--bmx-floor-plan-side-width |
The width of the details and list column. |
--bmx-floor-plan-unavailable |
The colour of a space that cannot be booked. |