@wedeho/vue-table-map
| Install | |
|---|---|
npm install @wedeho/vue-table-map |
|
| Latest Version: | 1.2.2 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Feb 9, 2026 |
| Links: | GitHub · npm |
Vue Table Map
🪑 A Vue 3 plugin to create and display interactive table maps with drag-drop, zoom, pan and multi-floor support
✨ 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 tableupdateTable(updates)- Update the selected tableselectTable(tableId)- Select a tabledeselectTable()- Deselect the current tablesetFloor(floor)- Change floorclearTables()- Remove all tablesclearCurrentFloor()- Remove tables from the current floorexportTables()- Export configurationimportTables(data)- Import a configurationgetTables()- Get all tablesgetSelectedTable()- Get the selected tablegetCurrentFloor()- Get the current floorzoomIn()- Zoom inzoomOut()- Zoom outresetZoom()- 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 controlsfloor-selector- Custom floor selectorinfo-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
Tiny and high performance zoom and pan library for Vue 3. It uses CSS Transforms...
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 | |
| 1.1.1 | ^3.3.0 | MIT | |
| 1.1.0 | ^3.3.0 | MIT | |
| 1.0.0 | ^3.3.0 | MIT |