@wedeho/vue-table-map

A Vue 3 plugin for creating and displaying interactive table seating plans with drag-drop, zoom, pan, and multi-floor support
1,271
Install
npm install @wedeho/vue-table-map
Latest Version:1.2.2
Vue:3
License:MIT
Last Updated:Feb 9, 2026
Links: GitHub  ·  npm
Maintainer: julien.pardons

Vue Table Map

🪑 A Vue 3 plugin to create and display interactive table maps with drag-drop, zoom, pan and multi-floor support

npm version License: MIT

✨ Features

  • 🎯 Interactive table map creation - Add, move and resize tables
  • 🔄 Multiple shapes - Rectangle, square and round
  • 🪑 Chair configuration - Define the number and position of chairs per side
  • 🔄 Table rotation - Free rotation from -180° to +180°
  • 🏢 Multi-floor support - Manage multiple floors/levels (Ground floor, Floor 1, Basement, etc.)
  • 🔍 Zoom and Pan - Smooth navigation with mouse wheel and drag to move the view
  • 🎨 Customizable - Complete API with props, events, slots and exposed methods
  • 📱 Responsive - Infinite canvas with horizontal and vertical scrolling
  • 👀 Read-only mode - Display without editing capabilities
  • 💾 Import/Export - JSON configuration to save and load your plans

📦 Installation

npm install @julienp/vue-table-map

🚀 Basic Usage

Global Installation

import { createApp } from 'vue'
import VueTableMap from '@wedeho/vue-table-map'
import '@wedeho/vue-table-map/dist/style.css'

const app = createApp(App)
app.use(VueTableMap)
app.mount('#app')

Direct Component Import

<script setup>
import { ref } from 'vue'
import { TableMapCanvas } from '@julienp/vue-table-map'
import '@wedeho/vue-table-map/dist/style.css'

const tables = ref([])
</script>

<template>
  <TableMapCanvas
    v-model="tables"
    :readonly="false"
  />
</template>

📖 Documentation

Props

Prop Type Default Description
modelValue Array [] Tables to display (v-model)
initialTables Array [] Initial tables (alternative to v-model)
readonly Boolean false Read-only mode
width Number 1200 Canvas width
height Number 800 Canvas height
gridSize Number 20 Grid size (snap)
showToolbar Boolean false Show default toolbar
showZoomControls Boolean true Show zoom controls
showFloorSelector Boolean true Show floor selector
showInfoPanel Boolean false Show information panel
showEditPanel Boolean true Show edit panel
controlsPosition String 'top' Controls position ('top' or 'bottom')
theme String 'light' Color theme: 'light', 'dark' or 'auto' (follows prefers-color-scheme) — see Dark mode
maxZoom Number 5 Maximum zoom level
minZoom Number 0.1 Minimum zoom level

Note on responsiveness: The width and height props are used as default values, but the canvas automatically adapts to its container size (100% width and height). Use CSS to define the parent container dimensions — with a definite height: in a parent sized by its content, the canvas keeps growing.

Note on controls: Zoom and floor selection controls are positioned inside the canvas as floating elements, horizontally centered. Use the controlsPosition prop to place them at the top or bottom.

Events

Event Payload Description
@update:modelValue tables: Array Emitted when tables change (v-model)
@table-added table: Object Emitted when a table is added
@table-updated table: Object Emitted when a table is modified
@table-removed tableId: String Emitted when a table is removed
@table-selected table: Object Emitted when a table is selected
@table-click { table, event } Emitted when a table is tapped or clicked (readonly). A press that moves more than 10px pans the view and does not emit. event is the MouseEvent or TouchEvent
@floor-changed floor: Number Emitted when the floor changes

Exposed Methods (via ref)

<script setup>
import { ref } from 'vue'

const canvasRef = ref(null)

// Add a table
const addTable = () => {
  canvasRef.value?.addTable('rectangle')
}

// Remove the selected table
const removeTable = () => {
  canvasRef.value?.removeTable()
}

// Get all tables
const getTables = () => {
  return canvasRef.value?.getTables()
}
</script>

<template>
  <TableMapCanvas ref="canvasRef" />
</template>
List of Methods
  • addTable(shape) - Add a table ('rectangle', 'square', 'round')
  • removeTable() - Remove the selected table
  • updateTable(updates) - Update the selected table
  • selectTable(tableId) - Select a table
  • deselectTable() - Deselect the current table
  • setFloor(floor) - Change floor
  • clearTables() - Remove all tables
  • clearCurrentFloor() - Remove tables from the current floor
  • exportTables() - Export configuration
  • importTables(data) - Import a configuration
  • getTables() - Get all tables
  • getSelectedTable() - Get the selected table
  • getCurrentFloor() - Get the current floor
  • zoomIn() - Zoom in
  • zoomOut() - Zoom out
  • resetZoom() - Reset zoom

Slots

Customize the interface with available slots:

table-content

Customize the content displayed in each table:

<TableMapCanvas v-model="tables">
  <template #table-content="{ table, index }">
    <div style="text-align: center;">
      <strong>{{ table.name }}</strong>
      <div>{{ table.seats }} seats</div>
    </div>
  </template>
</TableMapCanvas>
toolbar

Replace the default toolbar:

<TableMapCanvas :show-toolbar="true">
  <template #toolbar="{ addTable, deleteSelected }">
    <button @click="addTable('rectangle')">Add Table</button>
    <button @click="deleteSelected">Delete</button>
  </template>
</TableMapCanvas>
Other Available Slots
  • zoom-controls - Custom zoom controls
  • floor-selector - Custom floor selector
  • info-panel - Custom information panel

Table Structure

{
  id: 'unique-id',
  name: 'Table 1',
  shape: 'rectangle', // 'rectangle' | 'square' | 'round'
  x: 100,
  y: 100,
  width: 140,
  height: 80,
  seats: 6,
  rotation: 0, // -180 to 180
  floor: 0, // 0 = Ground floor, 1+ = Floors, -1- = Basements
  chairLayoutTop: 2, // For rectangle/square
  chairLayoutRight: 1,
  chairLayoutBottom: 2,
  chairLayoutLeft: 1
}

💡 Examples

Example 1: Custom Interface

<script setup>
import { ref } from 'vue'
import { TableMapCanvas } from '@julienp/vue-table-map'

const canvasRef = ref(null)
const tables = ref([])

const addRectangle = () => canvasRef.value?.addTable('rectangle')
const addRound = () => canvasRef.value?.addTable('round')
const deleteTable = () => canvasRef.value?.removeTable()
const exportPlan = () => {
  const data = canvasRef.value?.exportTables()
  console.log('Export:', data)
}
</script>

<template>
  <div>
    <div class="toolbar">
      <button @click="addRectangle">➕ Rectangle</button>
      <button @click="addRound">➕ Round</button>
      <button @click="deleteTable">🗑️ Delete</button>
      <button @click="exportPlan">💾 Export</button>
    </div>

    <TableMapCanvas
      ref="canvasRef"
      v-model="tables"
      :show-toolbar="false"
      :show-edit-panel="false"
      @table-selected="table => console.log('Selected:', table)"
    />
  </div>
</template>

Example 2: Read-only Mode with Custom Content

<script setup>
import { ref } from 'vue'
import { TableMapCanvas } from '@julienp/vue-table-map'

const tables = ref([
  {
    id: '1',
    name: 'VIP Table',
    shape: 'round',
    x: 200,
    y: 200,
    width: 120,
    height: 120,
    seats: 8,
    floor: 0,
    isVip: true
  }
])

const handleTableClick = ({ table }) => {
  alert(`Table: ${table.name}\nSeats: ${table.seats}`)
}
</script>

<template>
  <TableMapCanvas
    v-model="tables"
    :readonly="true"
    @table-click="handleTableClick"
  >
    <template #table-content="{ table }">
      <div style="text-align: center; color: #1e293b;">
        <div style="font-weight: bold; font-size: 14px;">
          {{ table.name }}
        </div>
        <div style="font-size: 12px; color: #64748b;">
          {{ table.seats }} seats
        </div>
        <div v-if="table.isVip" style="color: gold; font-size: 16px;">
          ⭐
        </div>
      </div>
    </template>
  </TableMapCanvas>
</template>

Example 3: Multi-floor with Initial Data

<script setup>
import { ref } from 'vue'
import { TableMapCanvas } from '@julienp/vue-table-map'

const tables = ref([
  // Ground floor
  {
    id: '1',
    name: 'Table 1',
    shape: 'rectangle',
    x: 150,
    y: 150,
    width: 140,
    height: 80,
    seats: 6,
    floor: 0,
    chairLayoutTop: 2,
    chairLayoutRight: 1,
    chairLayoutBottom: 2,
    chairLayoutLeft: 1
  },
  // Floor 1
  {
    id: '2',
    name: 'Table 2',
    shape: 'round',
    x: 200,
    y: 200,
    width: 100,
    height: 100,
    seats: 6,
    floor: 1
  }
])

const handleFloorChange = (floor) => {
  console.log('Active floor:', floor)
}
</script>

<template>
  <TableMapCanvas
    v-model="tables"
    @floor-changed="handleFloorChange"
  />
</template>

🎮 Controls

Edit Mode

  • Move a table: Click and drag
  • Resize: Use the handles on corners and edges
  • Select: Click on a table
  • Zoom: Mouse wheel or +/- buttons
  • Pan: Drag the background (when no table selected) or Shift+Drag
  • Edit: Double-click or "Edit" button when selected

Read-only Mode

  • Zoom: Mouse wheel
  • Position: The plan is shown where it was drawn, as in edit mode — the view only moves to keep every table reachable
  • Pan: Click (or touch) and drag beyond 10px — even when the gesture starts on a table
  • Table click: Tap or click, emits @table-click

🌙 Dark mode

<TableMapCanvas :initial-tables="tables" readonly theme="dark" />

theme="auto" follows the system preference (prefers-color-scheme) and switches live. The default stays 'light'.

Content rendered in the table-content slot inherits --tm-table-text: leave its color unset (use opacity for secondary lines) and it stays readable in both themes.

Theme tokens

Every color is a CSS custom property. Override any of them with style on the component — it takes precedence over both themes:

<TableMapCanvas
  theme="dark"
  :style="{ '--tm-canvas': '#0a0f17', '--tm-accent': '#2492ff' }"
/>
Token Light Dark Used for
--tm-bg #f8fafc #020617 Container background
--tm-canvas #ffffff #0f172a Canvas background
--tm-surface #ffffff #1e293b Toolbar, floating controls, panels, handles
--tm-surface-muted #f1f5f9 #334155 Zoom, floor and small buttons
--tm-surface-hover #e2e8f0 #475569 Hovered buttons
--tm-border #cbd5e1 #334155 Canvas and button borders
--tm-border-soft #e2e8f0 #334155 Dividers, field borders
--tm-border-strong #94a3b8 #64748b Hovered borders
--tm-grid #e2e8f0 #1e293b Grid lines
--tm-ring transparent #334155 Outline of floating surfaces
--tm-text #334155 #f1f5f9 Labels
--tm-text-muted #475569 #cbd5e1 Secondary text
--tm-text-subtle #94a3b8 #94a3b8 Help text
--tm-accent / --tm-accent-hover #3b82f6 / #2563eb same Selection, active floor, buttons
--tm-on-accent #ffffff same Text on accent, success and danger fills
--tm-success / --tm-success-hover #10b981 / #059669 same Add floor, chair controls
--tm-danger / --tm-danger-hover #ef4444 / #dc2626 same Destructive buttons
--tm-table-fill #ffffff #1e293b Tables
--tm-table-stroke #94a3b8 #64748b Table outline
--tm-table-selected-fill #e0f2fe #1e3a8a Selected table (edit mode)
--tm-table-text #1e293b #f1f5f9 Table label, inherited by table-content
--tm-table-hover-filter brightness(0.95) brightness(1.25) Hovered table
--tm-chair-fill / --tm-chair-stroke #8b4513 / #654321 #6b4a32 / #a47148 Chairs

🎨 Style Customization

The plugin uses scoped CSS. Prefer the theme tokens above; for anything else you can override styles:

/* Customize tables */
.table-element {
  /* your styles */
}

/* Customize chairs */
.chair-element {
  /* your styles */
}

🛠️ Development

# Install dependencies
npm install

# Development with Hot Module Replacement
npm run dev

# Build library
npm run build:lib

# Build demo
npm run build

npm publish --access public

📝 License

MIT © Julien PARDONS / Wedeho

🤝 Contributing

Contributions are welcome! Feel free to open an issue or pull request.

🐛 Support

To report a bug or request a feature, please create an issue.

Related Packages

vue-panzoom

Vue plugin to zoom/pan dom elements

201,156 96
simple-panzoom

保留 vue3-sketch-ruler 实际用到的功能,移除 SVG、IE 与多指捏合等未用特性,提升性...

1,539 303
vue-openseadragon

OpenSeaDragon component for Vue.js

3,936 29
vue-zoomable

Tiny and high performance zoom and pan library for Vue 3. It uses CSS Transforms...

145,993 37
vue-zoomable-cute

Tiny and high performance zoom and pan library for Vue 3. It uses CSS Transforms...

5,653 37

Version History

Version Released Vue License
1.2.2 ^3.3.0 MIT
1.2.1 ^3.3.0 MIT
1.2.0 ^3.3.0 MIT
1.1.4 ^3.3.0 MIT
1.1.3 ^3.3.0 MIT