v2.6.0

<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

PropertyAttributeTypeDefaultDescription
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

EventDetailDescription
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

MethodSignatureDescription
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

SlotDescription
header Above the plan, beside the floor tabs (a date picker, say).

CSS shadow parts

PartDescription
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

PropertyDescription
--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.