sliced-areas 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # sliced-areas
2
2
 
3
- Blender-like “Areas” layout system as a Web Component with an optional Vue 3 wrapper.
3
+ Blender-like resizable and splittable areas layout system. Available as a Web Component with optional Vue 3 wrapper.
4
4
 
5
- ## Install
5
+ 📚 **[Documentation & Examples](https://pavel-voronin.github.io/sliced-areas/)**
6
+
7
+ ## Installation
6
8
 
7
9
  ```sh
8
10
  npm install sliced-areas
9
11
  ```
10
12
 
11
- ## Web Component Setup
13
+ ## Web Component Usage
12
14
 
13
15
  ```ts
14
16
  import 'sliced-areas'
@@ -16,24 +18,28 @@ import 'sliced-areas/styles.css'
16
18
  import type { AreasLayout, SlicedAreasElement } from 'sliced-areas'
17
19
 
18
20
  const layout: AreasLayout = {
19
- areas: [{ tag: 'viewport', rect: { left: 0, right: 1, top: 1, bottom: 0 } }],
21
+ areas: [{ tag: 'viewport', rect: { left: 0, right: 1, top: 1, bottom: 0 } }]
20
22
  }
21
23
 
22
24
  const areas = document.querySelector('sliced-areas') as SlicedAreasElement
23
- areas.setResolver((tag) => document.querySelector(`[data-area-tag="${tag}"]`))
25
+ areas.setResolver((tag) => {
26
+ const div = document.createElement('div')
27
+ div.textContent = `Content for: ${tag}`
28
+ return div
29
+ })
24
30
  areas.layout = layout
25
31
  ```
26
32
 
27
33
  ```html
28
- <sliced-areas style="height: 100%;"></sliced-areas>
29
- <div data-area-tag="viewport">Viewport content</div>
34
+ <sliced-areas style="height: 100vh;"></sliced-areas>
30
35
  ```
31
36
 
32
- ## Vue 3 Setup
37
+ **Important:** Resolver must return a fresh node each time. When areas share the same tag, reusing the same DOM element will move it to the last area.
38
+
39
+ ## Vue 3 Usage
33
40
 
34
41
  ```ts
35
42
  import { createApp } from 'vue'
36
- import App from './App.vue'
37
43
  import { SlicedAreasPlugin } from 'sliced-areas/vue'
38
44
  import 'sliced-areas/styles.css'
39
45
 
@@ -43,21 +49,36 @@ app.mount('#app')
43
49
  ```
44
50
 
45
51
  ```vue
52
+ <script setup lang="ts">
53
+ import { ref } from 'vue'
54
+ import type { AreasLayout } from 'sliced-areas/vue'
55
+
56
+ const layout = ref<AreasLayout>({
57
+ areas: [{ tag: 'viewport', rect: { left: 0, right: 1, top: 1, bottom: 0 } }]
58
+ })
59
+
60
+ const resolveArea = (tag: string) => {
61
+ const div = document.createElement('div')
62
+ div.textContent = `Content for: ${tag}`
63
+ return div
64
+ }
65
+
66
+ const onLayoutChange = (detail: { layout: AreasLayout }) => {
67
+ layout.value = detail.layout
68
+ }
69
+ </script>
70
+
46
71
  <template>
47
72
  <SlicedAreas
48
- class="layout-root"
49
73
  :layout="layout"
50
74
  :resolver="resolveArea"
51
75
  @layoutchange="onLayoutChange"
52
- @cornerclick="onCornerClick"
53
76
  />
54
77
  </template>
55
78
  ```
56
79
 
57
80
  ## Layout Format
58
81
 
59
- `AreasLayout` uses normalized coordinates (0..1) and tags for content lookup:
60
-
61
82
  ```ts
62
83
  type AreasLayout = {
63
84
  areas: Array<{
@@ -67,46 +88,13 @@ type AreasLayout = {
67
88
  }
68
89
  ```
69
90
 
70
- - Area ids are internal and auto-generated; only tags are persistent.
71
- - The layout always fills the component bounds.
72
-
73
- ## Gestures and Operations
74
-
75
- - Resize: drag splitters between areas.
76
- - Move/Split/Join/Replace: drag an area corner into another area.
77
- - Split orientation is inferred from drag direction.
78
- - Swap: hold `Ctrl` while dragging an area into another.
79
- - Maximize/Restore: use the API methods.
91
+ Coordinates are normalized (0 to 1). `top: 1` is top edge, `bottom: 0` is bottom edge.
80
92
 
81
93
  ## Events
82
94
 
83
- - `sliced-areas:layoutchange` `{ layout: AreasLayout }`
84
- - `sliced-areas:cornerclick` `{ areaId, corner, clientX, clientY }`
95
+ - `sliced-areas:layoutchange` / `@layoutchange` - Fired when layout changes
96
+ - `sliced-areas:cornerclick` / `@cornerclick` - Fired when corner is clicked
85
97
 
86
- Vue emits `layoutchange` and `cornerclick` with the same payloads.
98
+ ## License
87
99
 
88
- ## API (Element)
89
-
90
- ```ts
91
- element.layout: AreasLayout | null
92
- element.setResolver(resolver: (tag: string) => HTMLElement | null | undefined): void
93
- element.split(areaId, zone?, clientX?, clientY?): void
94
- element.join(sourceAreaId, targetAreaId): void
95
- element.replace(sourceAreaId, targetAreaId): void
96
- element.swap(sourceAreaId, targetAreaId): void
97
- element.move(sourceAreaId, targetAreaId, overlayRect, remainderRect): void
98
- element.close(areaId): void
99
- element.retag(areaId, tag): void
100
- element.maximize(areaId): void
101
- element.restore(): void
102
- ```
103
-
104
- ## Retagging from Area Content
105
-
106
- Dispatch a bubbling event from inside an area node to update its tag:
107
-
108
- ```ts
109
- node.dispatchEvent(
110
- new CustomEvent('sliced-areas:retag', { bubbles: true, detail: { tag: 'viewport' } }),
111
- )
112
- ```
100
+ MIT
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as registerSlicedAreasElement, t as SlicedAreasElement } from "./sliced-areas-DS_2BRnf.js";
1
+ import { n as registerSlicedAreasElement, t as SlicedAreasElement } from "./sliced-areas-rZ_V6Je7.js";
2
2
  export { SlicedAreasElement, registerSlicedAreasElement };
@@ -122,7 +122,7 @@ var INTERNAL_ATTR = "data-sliced-areas-internal", AUTO_ATTR = "data-sliced-areas
122
122
  continue;
123
123
  }
124
124
  let i = this.areaTags.get(e.id) ?? e.id, a = this.areaResolver(i);
125
- a && (a.dataset.areaId = e.id, a.setAttribute(AUTO_ATTR, "true"), this.resolvedNodes.set(e.id, a), n.set(e.id, a));
125
+ a && (this.assertFreshResolvedNode(e.id, i, a), a.dataset.areaId = e.id, a.setAttribute(AUTO_ATTR, "true"), this.resolvedNodes.set(e.id, a), n.set(e.id, a));
126
126
  }
127
127
  r = Object.values(this.graph.areas).filter((e) => !n.has(e.id));
128
128
  }
@@ -177,6 +177,11 @@ var INTERNAL_ATTR = "data-sliced-areas-internal", AUTO_ATTR = "data-sliced-areas
177
177
  }
178
178
  return n;
179
179
  }
180
+ assertFreshResolvedNode(e, t, n) {
181
+ let r = n.dataset.areaId;
182
+ if (r && r !== e) throw Error(`Resolver must return a fresh element per area. Got an element already assigned to "${r}" for tag "${t}".`);
183
+ for (let [r, i] of this.resolvedNodes.entries()) if (i === n && r !== e) throw Error(`Resolver must return a fresh element per area. Got the same element for "${r}" and "${e}" (tag "${t}").`);
184
+ }
180
185
  ensureAreaNode(n, r, i = !0) {
181
186
  if (Array.from(this.querySelectorAll(`[data-area-id="${n}"]`)).find((t) => !t.hasAttribute(INTERNAL_ATTR))) return;
182
187
  this.inheritAreaTag(n, r);
@@ -189,7 +194,7 @@ var INTERNAL_ATTR = "data-sliced-areas-internal", AUTO_ATTR = "data-sliced-areas
189
194
  if (a && this.areaResolver) {
190
195
  let e = this.areaResolver(a);
191
196
  if (e) {
192
- e.dataset.areaId = n, e.setAttribute(AUTO_ATTR, "true"), this.appendChild(e);
197
+ this.assertFreshResolvedNode(n, a, e), e.dataset.areaId = n, e.setAttribute(AUTO_ATTR, "true"), this.appendChild(e);
193
198
  return;
194
199
  }
195
200
  }
@@ -1732,4 +1737,4 @@ const registerSlicedAreasElement = () => {
1732
1737
  registerSlicedAreasElement();
1733
1738
  export { registerSlicedAreasElement as n, SlicedAreasElement as t };
1734
1739
 
1735
- //# sourceMappingURL=sliced-areas-DS_2BRnf.js.map
1740
+ //# sourceMappingURL=sliced-areas-rZ_V6Je7.js.map