qrlayout-ui 1.0.7 → 1.0.9
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 +11 -13
- package/dist/index.d.ts +4 -0
- package/dist/qrlayout-ui.css +1 -1
- package/dist/qrlayout-ui.js +135 -58
- package/dist/qrlayout-ui.umd.js +39 -4
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A framework-agnostic, embeddable UI for designing sticker layouts with QR codes. Part of the [QR Layout Tool](https://github.com/shashi089/qr-code-layout-generate-tool).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- **[ Svelte Live Demo](https://qr-layout-designer-svelte.netlify.app/)**
|
|
7
|
-
- **[ Vue Live Demo](https://qr-layout-designer-vue.netlify.app/)**
|
|
5
|
+
## 🚀 Live Demos & Examples
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
| Framework | Live Demo | Source Code |
|
|
8
|
+
| :--- | :--- | :--- |
|
|
9
|
+
| **React** | [Live Demo](https://qr-layout-designer.netlify.app/) | [Source](https://github.com/shashi089/qr-code-layout-generate-tool/tree/main/examples/react-demo) |
|
|
10
|
+
| **Svelte 5** | [Live Demo](https://qr-layout-designer-svelte.netlify.app/) | [Source](https://github.com/shashi089/qr-code-layout-generate-tool/tree/main/examples/svelte-demo) |
|
|
11
|
+
| **Vue 3** | [Live Demo](https://qr-layout-designer-vue.netlify.app/) | [Source](https://github.com/shashi089/qr-code-layout-generate-tool/tree/main/examples/vue-demo) |
|
|
14
12
|
|
|
15
13
|

|
|
16
14
|
|
|
@@ -102,9 +100,9 @@ const designer = new QRLayoutDesigner({
|
|
|
102
100
|
});
|
|
103
101
|
```
|
|
104
102
|
|
|
105
|
-
###
|
|
103
|
+
### 4. Cleanup
|
|
106
104
|
|
|
107
|
-
|
|
105
|
+
Destroy the instance when the component unmounts to prevent memory leaks:
|
|
108
106
|
|
|
109
107
|
```javascript
|
|
110
108
|
designer.destroy();
|
|
@@ -115,9 +113,9 @@ designer.destroy();
|
|
|
115
113
|
| Option | Type | Description |
|
|
116
114
|
|---|---|---|
|
|
117
115
|
| `element` | `HTMLElement` | **Required**. The DOM element to mount the designer into. |
|
|
118
|
-
| `entitySchemas` | `Record<string, Schema>` |
|
|
119
|
-
| `initialLayout` | `StickerLayout` | The initial layout state to load. |
|
|
120
|
-
| `onSave` | `(layout) => void` | Callback triggered when the "Save Layout" button is clicked. |
|
|
116
|
+
| `entitySchemas` | `Record<string, Schema>` | Data entity definitions for `{{field}}` placeholder binding. |
|
|
117
|
+
| `initialLayout` | `StickerLayout` | The initial layout state to load on mount. |
|
|
118
|
+
| `onSave` | `(layout: StickerLayout) => void` | Callback triggered when the "Save Layout" button is clicked. |
|
|
121
119
|
|
|
122
120
|
## Integration Examples
|
|
123
121
|
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare class QRLayoutDesigner {
|
|
|
23
23
|
private selectedElementId;
|
|
24
24
|
private isDarkMode;
|
|
25
25
|
private pxPerUnit;
|
|
26
|
+
private isDragging;
|
|
26
27
|
private printer;
|
|
27
28
|
private onSaveCallback?;
|
|
28
29
|
private canvas;
|
|
@@ -32,6 +33,7 @@ export declare class QRLayoutDesigner {
|
|
|
32
33
|
private propContent;
|
|
33
34
|
private leftSidebar;
|
|
34
35
|
private rightSidebar;
|
|
36
|
+
private sampleDataContainer;
|
|
35
37
|
private inputs;
|
|
36
38
|
constructor(options: DesignerOptions);
|
|
37
39
|
private init;
|
|
@@ -41,6 +43,8 @@ export declare class QRLayoutDesigner {
|
|
|
41
43
|
private syncInputsFromLayout;
|
|
42
44
|
private bindEvents;
|
|
43
45
|
updatePreview(): Promise<void>;
|
|
46
|
+
private showSampleDataModal;
|
|
47
|
+
private renderSampleDataEditor;
|
|
44
48
|
private renderElementsList;
|
|
45
49
|
private selectElement;
|
|
46
50
|
private renderPropertyPanel;
|
package/dist/qrlayout-ui.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.qrlayout-designer{--primary-color: #6366f1;--primary-hover: #4f46e5;--bg-color: #f1f5f9;--panel-bg: #ffffff;--panel-bg-alt: #f8fafc;--text-primary: #0f172a;--text-secondary: #64748b;--border-color: #e2e8f0;--input-bg: #ffffff;--danger-color: #ef4444;--success-color: #10b981;--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / .05);--shadow-md: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);font-family:Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;background-color:var(--bg-color);color:var(--text-primary);display:flex;flex-direction:column;overflow:hidden;width:100%;height:100%;box-sizing:border-box}.qrlayout-designer.dark-mode{--bg-color: #0f172a;--panel-bg: #1e293b;--panel-bg-alt: #334155;--text-primary: #f8fafc;--text-secondary: #94a3b8;--border-color: #334155;--input-bg: #1e293b}.qrlayout-designer ::-webkit-scrollbar{width:8px;height:8px}.qrlayout-designer ::-webkit-scrollbar-track{background:transparent}.qrlayout-designer ::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:4px}.qrlayout-designer ::-webkit-scrollbar-thumb:hover{background:var(--text-secondary)}.qrlayout-designer *{box-sizing:border-box}.qrlayout-designer header{height:64px;background-color:var(--panel-bg);border-bottom:1px solid var(--border-color);display:flex;align-items:center;padding:0 24px;justify-content:space-between;box-shadow:var(--shadow-sm);z-index:10;flex-shrink:0}.qrlayout-designer h1{font-size:1.25rem;font-weight:700;margin:0;display:flex;align-items:center;gap:12px}.qrlayout-designer .logo-icon{width:32px;height:32px;background:linear-gradient(135deg,var(--primary-color),var(--primary-hover));border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff}.qrlayout-designer .main-container{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative}.qrlayout-designer .list-view{padding:32px;flex-direction:column;max-width:1200px;margin:0 auto;width:100%}.qrlayout-designer .view-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.qrlayout-designer .table-container{background-color:var(--panel-bg);border:1px solid var(--border-color);border-radius:12px;overflow:hidden;box-shadow:var(--shadow-md)}.qrlayout-designer table{width:100%;border-collapse:collapse;text-align:left}.qrlayout-designer th{background-color:var(--panel-bg-alt);padding:12px 24px;font-size:.75rem;text-transform:uppercase;font-weight:600;color:var(--text-secondary);border-bottom:1px solid var(--border-color)}.qrlayout-designer td{padding:16px 24px;font-size:.875rem;border-bottom:1px solid var(--border-color)}.qrlayout-designer tr:last-child td{border-bottom:none}.qrlayout-designer tr:hover td{background-color:var(--panel-bg-alt)}.qrlayout-designer .edit-view{display:flex;height:100%}.qrlayout-designer .sidebar{width:320px;background-color:var(--panel-bg);border-right:1px solid var(--border-color);display:flex;flex-direction:column;overflow-y:auto}.qrlayout-designer .sidebar-right{width:320px;background-color:var(--panel-bg);border-left:1px solid var(--border-color);display:flex;flex-direction:column;overflow-y:auto}.qrlayout-designer .sidebar-section{padding:16px;border-bottom:1px solid var(--border-color)}.qrlayout-designer .sidebar-section:last-child{border-bottom:none}.qrlayout-designer .sidebar-title{font-size:.75rem;text-transform:uppercase;color:var(--text-secondary);font-weight:700;letter-spacing:.05em;margin-bottom:16px;display:flex;justify-content:space-between;align-items:center}.qrlayout-designer .preview-area{flex:1;background-color:var(--bg-color);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;padding:40px;background-image:radial-gradient(var(--border-color) 1px,transparent 1px);background-size:24px 24px;overflow:auto}.qrlayout-designer .canvas-wrapper{background:#fff;box-shadow:var(--shadow-lg);position:relative}.qrlayout-designer .form-group{margin-bottom:16px}.qrlayout-designer .form-group label{display:block;font-size:.75rem;color:var(--text-secondary);margin-bottom:6px;font-weight:500}.qrlayout-designer .form-row{display:flex;gap:12px}.qrlayout-designer input,.qrlayout-designer select,.qrlayout-designer textarea{width:100%;background-color:var(--input-bg);border:1px solid var(--border-color);color:var(--text-primary);padding:8px 12px;border-radius:6px;font-size:.875rem;transition:all .2s}.qrlayout-designer input:focus,.qrlayout-designer select:focus,.qrlayout-designer textarea:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 2px #6366f133}.qrlayout-designer .color-picker-wrapper{display:flex;align-items:center;gap:8px}.qrlayout-designer .color-preview{width:24px;height:24px;border-radius:4px;border:1px solid var(--border-color)}.qrlayout-designer .btn{display:inline-flex;align-items:center;justify-content:center;padding:8px 16px;border-radius:6px;font-size:.875rem;font-weight:600;cursor:pointer;transition:all .2s;border:1px solid transparent;gap:8px;height:36px}.qrlayout-designer .btn-primary{background-color:var(--primary-color);color:#fff}.qrlayout-designer .btn-primary:hover{background-color:var(--primary-hover)}.qrlayout-designer .btn-secondary{background-color:var(--panel-bg);border-color:var(--border-color);color:var(--text-primary)}.qrlayout-designer .btn-secondary:hover{background-color:var(--panel-bg-alt)}.qrlayout-designer .btn-outline{background-color:transparent;border-color:var(--border-color);color:var(--text-primary)}.qrlayout-designer .btn-outline:hover{background-color:#80808014}.qrlayout-designer .btn-danger{background-color:#ef44441a;color:var(--danger-color)}.qrlayout-designer .btn-danger:hover{background-color:#ef444433}.qrlayout-designer .btn-icon{width:32px;padding:0;height:32px}.qrlayout-designer .btn-block{width:100%}.qrlayout-designer .btn-sm{padding:4px 8px;font-size:.75rem;height:28px}.qrlayout-designer .element-list{display:flex;flex-direction:column;gap:4px}.qrlayout-designer .element-item{padding:8px 12px;border-radius:6px;display:flex;justify-content:space-between;align-items:center;cursor:pointer;transition:all .2s}.qrlayout-designer .element-item:hover{background-color:var(--panel-bg-alt)}.qrlayout-designer .element-item.active{background-color:#6366f11a;color:var(--primary-color)}.qrlayout-designer .element-info{display:flex;flex-direction:column}.qrlayout-designer .element-name{font-size:.8125rem;font-weight:600}.qrlayout-designer .element-sub{font-size:.6875rem;color:var(--text-secondary)}.qrlayout-designer .toggle-group{display:flex;border:1px solid var(--border-color);border-radius:6px;overflow:hidden;width:fit-content}.qrlayout-designer .toggle-btn{padding:6px 10px;background:var(--panel-bg);border:none;cursor:pointer;color:var(--text-secondary);border-right:1px solid var(--border-color);display:flex;align-items:center;justify-content:center}.qrlayout-designer .toggle-btn:last-child{border-right:none}.qrlayout-designer .toggle-btn.active{background:var(--panel-bg-alt);color:var(--primary-color)}.qrlayout-designer .field-buttons{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.qrlayout-designer .field-pill{font-size:.625rem;padding:2px 6px;border-radius:4px;background:var(--panel-bg-alt);border:1px solid var(--border-color);cursor:pointer;color:var(--text-secondary)}.qrlayout-designer .field-pill:hover{border-color:var(--primary-color);color:var(--primary-color)}.qrlayout-designer .editor-item{position:absolute;border:2px solid transparent;box-sizing:border-box;cursor:move}.qrlayout-designer .editor-item.selected{border:2px solid var(--primary-color);background:#6366f10d}.qrlayout-designer .resize-handle{position:absolute;width:8px;height:8px;background:#fff;border:2px solid var(--primary-color);border-radius:50%;bottom:-5px;right:-5px;cursor:nwse-resize;z-index:10}.qrlayout-designer .toolbar{position:absolute;top:24px;right:24px;display:flex;gap:12px;z-index:20}.qrlayout-designer .sidebar-toggle{display:none;background:var(--panel-bg);border:1px solid var(--border-color);color:var(--text-primary);width:40px;height:40px;border-radius:8px;align-items:center;justify-content:center;cursor:pointer;position:absolute;top:12px;z-index:100;box-shadow:var(--shadow-md);transition:all .3s cubic-bezier(.4,0,.2,1)}.qrlayout-designer .sidebar-toggle:hover{background-color:var(--panel-bg-alt);border-color:var(--primary-color);color:var(--primary-color)}.qrlayout-designer #toggle-left{left:12px}.qrlayout-designer #toggle-right{right:12px}@media(max-width:768px){.qrlayout-designer header{padding:16px;height:auto;flex-direction:column;gap:12px;align-items:flex-start}.qrlayout-designer header h1{font-size:1.125rem}.qrlayout-designer .logo-icon{width:28px;height:28px;font-size:.75rem}.qrlayout-designer header .btn{padding:6px 10px;font-size:.75rem;height:32px}.qrlayout-designer .sidebar,.qrlayout-designer .sidebar-right{position:fixed;top:64px;bottom:0;z-index:50;width:100%;max-width:320px;box-shadow:var(--shadow-lg);transition:transform .3s cubic-bezier(.4,0,.2,1)}.qrlayout-designer .sidebar{left:0;transform:translate(-100%)}.qrlayout-designer .sidebar-right{right:0;transform:translate(100%)}.qrlayout-designer .sidebar.show,.qrlayout-designer .sidebar-right.show{transform:translate(0)}.qrlayout-designer .sidebar-toggle{display:flex}.qrlayout-designer .preview-area{padding:20px}.qrlayout-designer .canvas-wrapper{max-width:100%;overflow:auto}@media(max-width:480px){.qrlayout-designer header h1 span{display:none}.qrlayout-designer .sidebar-toggle{width:36px;height:36px;top:8px}.qrlayout-designer #toggle-left{left:8px}.qrlayout-designer #toggle-right{right:8px}.qrlayout-designer .preview-area{padding:10px}}}
|
|
1
|
+
.qrlayout-designer{--primary-color: #6366f1;--primary-hover: #4f46e5;--bg-color: #f1f5f9;--panel-bg: #ffffff;--panel-bg-alt: #f8fafc;--text-primary: #0f172a;--text-secondary: #64748b;--border-color: #e2e8f0;--input-bg: #ffffff;--danger-color: #ef4444;--success-color: #10b981;--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / .05);--shadow-md: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);font-family:Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;background-color:var(--bg-color);color:var(--text-primary);display:flex;flex-direction:column;overflow:hidden;width:100%;height:100%;box-sizing:border-box}.qrlayout-designer.dark-mode{--bg-color: #0f172a;--panel-bg: #1e293b;--panel-bg-alt: #334155;--text-primary: #f8fafc;--text-secondary: #94a3b8;--border-color: #334155;--input-bg: #1e293b}.qrlayout-designer ::-webkit-scrollbar{width:8px;height:8px}.qrlayout-designer ::-webkit-scrollbar-track{background:transparent}.qrlayout-designer ::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:4px}.qrlayout-designer ::-webkit-scrollbar-thumb:hover{background:var(--text-secondary)}.qrlayout-designer *{box-sizing:border-box}.qrlayout-designer header{height:64px;background-color:var(--panel-bg);border-bottom:1px solid var(--border-color);display:flex;align-items:center;padding:0 24px;justify-content:space-between;box-shadow:var(--shadow-sm);z-index:10;flex-shrink:0}.qrlayout-designer h1{font-size:1.25rem;font-weight:700;margin:0;display:flex;align-items:center;gap:12px}.qrlayout-designer .logo-icon{width:32px;height:32px;background:linear-gradient(135deg,var(--primary-color),var(--primary-hover));border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff}.qrlayout-designer .main-container{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative}.qrlayout-designer .list-view{padding:32px;flex-direction:column;max-width:1200px;margin:0 auto;width:100%}.qrlayout-designer .view-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.qrlayout-designer .table-container{background-color:var(--panel-bg);border:1px solid var(--border-color);border-radius:12px;overflow:hidden;box-shadow:var(--shadow-md)}.qrlayout-designer table{width:100%;border-collapse:collapse;text-align:left}.qrlayout-designer th{background-color:var(--panel-bg-alt);padding:12px 24px;font-size:.75rem;text-transform:uppercase;font-weight:600;color:var(--text-secondary);border-bottom:1px solid var(--border-color)}.qrlayout-designer td{padding:16px 24px;font-size:.875rem;border-bottom:1px solid var(--border-color)}.qrlayout-designer tr:last-child td{border-bottom:none}.qrlayout-designer tr:hover td{background-color:var(--panel-bg-alt)}.qrlayout-designer .edit-view{display:flex;height:100%}.qrlayout-designer .sidebar{width:320px;background-color:var(--panel-bg);border-right:1px solid var(--border-color);display:flex;flex-direction:column;overflow-y:auto}.qrlayout-designer .sidebar-right{width:320px;background-color:var(--panel-bg);border-left:1px solid var(--border-color);display:flex;flex-direction:column;overflow-y:auto}.qrlayout-designer .sidebar-section{padding:16px;border-bottom:1px solid var(--border-color)}.qrlayout-designer .sidebar-section:last-child{border-bottom:none}.qrlayout-designer .sidebar-title{font-size:.75rem;text-transform:uppercase;color:var(--text-secondary);font-weight:700;letter-spacing:.05em;margin-bottom:16px;display:flex;justify-content:space-between;align-items:center}.qrlayout-designer .preview-area{flex:1;background-color:var(--bg-color);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;padding:40px;background-image:radial-gradient(var(--border-color) 1px,transparent 1px);background-size:24px 24px;overflow:auto}.qrlayout-designer .canvas-wrapper{background:#fff;box-shadow:var(--shadow-lg);position:relative}.qrlayout-designer .form-group{margin-bottom:16px}.qrlayout-designer .form-group label{display:block;font-size:.75rem;color:var(--text-secondary);margin-bottom:6px;font-weight:500}.qrlayout-designer .form-row{display:flex;gap:12px}.qrlayout-designer input,.qrlayout-designer select,.qrlayout-designer textarea{width:100%;background-color:var(--input-bg);border:1px solid var(--border-color);color:var(--text-primary);padding:8px 12px;border-radius:6px;font-size:.875rem;transition:all .2s}.qrlayout-designer input:focus,.qrlayout-designer select:focus,.qrlayout-designer textarea:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 2px #6366f133}.qrlayout-designer .color-picker-wrapper{display:flex;align-items:center;gap:8px}.qrlayout-designer .color-preview{width:24px;height:24px;border-radius:4px;border:1px solid var(--border-color)}.qrlayout-designer .btn{display:inline-flex;align-items:center;justify-content:center;padding:8px 16px;border-radius:6px;font-size:.875rem;font-weight:600;cursor:pointer;transition:all .2s;border:1px solid transparent;gap:8px;height:36px}.qrlayout-designer .btn-primary{background-color:var(--primary-color);color:#fff}.qrlayout-designer .btn-primary:hover{background-color:var(--primary-hover)}.qrlayout-designer .btn-secondary{background-color:var(--panel-bg);border-color:var(--border-color);color:var(--text-primary)}.qrlayout-designer .btn-secondary:hover{background-color:var(--panel-bg-alt)}.qrlayout-designer .btn-outline{background-color:transparent;border-color:var(--border-color);color:var(--text-primary)}.qrlayout-designer .btn-outline:hover{background-color:#80808014}.qrlayout-designer .btn-danger{background-color:#ef44441a;color:var(--danger-color)}.qrlayout-designer .btn-danger:hover{background-color:#ef444433}.qrlayout-designer .btn-icon{width:32px;padding:0;height:32px}.qrlayout-designer .btn-block{width:100%}.qrlayout-designer .btn-sm{padding:4px 8px;font-size:.75rem;height:28px}.qrlayout-designer .element-list{display:flex;flex-direction:column;gap:4px}.qrlayout-designer .element-item{padding:8px 12px;border-radius:6px;display:flex;justify-content:space-between;align-items:center;cursor:pointer;transition:all .2s}.qrlayout-designer .element-item:hover{background-color:var(--panel-bg-alt)}.qrlayout-designer .element-item.active{background-color:#6366f11a;color:var(--primary-color)}.qrlayout-designer .element-info{display:flex;flex-direction:column}.qrlayout-designer .element-name{font-size:.8125rem;font-weight:600}.qrlayout-designer .element-sub{font-size:.6875rem;color:var(--text-secondary)}.qrlayout-designer .sample-data-list{display:flex;flex-direction:column;gap:12px}.qrlayout-designer .sample-data-list .form-group label{display:flex;justify-content:space-between;align-items:center}.qrlayout-designer .sample-data-list label code{font-family:monospace;font-size:.625rem;opacity:.7;background:var(--panel-bg-alt);padding:2px 4px;border-radius:4px;border:1px solid var(--border-color)}.qrlayout-designer .modal-overlay{position:fixed;inset:0;background:#0f172a80;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:1000;opacity:0;pointer-events:none;transition:all .3s ease}.qrlayout-designer .modal-overlay.show{opacity:1;pointer-events:auto}.qrlayout-designer .modal-content{background:var(--panel-bg);width:90%;max-width:600px;border-radius:16px;box-shadow:var(--shadow-lg);transform:translateY(20px);transition:all .3s ease;overflow:hidden;border:1px solid var(--border-color)}.qrlayout-designer .modal-overlay.show .modal-content{transform:translateY(0)}.qrlayout-designer .modal-header{padding:20px 24px;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;align-items:center}.qrlayout-designer .modal-header h3{margin:0;font-size:1.25rem;font-weight:700}.qrlayout-designer .btn-close{background:none;border:none;font-size:1.5rem;color:var(--text-secondary);cursor:pointer;padding:4px;line-height:1}.qrlayout-designer .modal-body{padding:24px;max-height:60vh;overflow-y:auto}.qrlayout-designer .modal-footer{padding:16px 24px;border-top:1px solid var(--border-color);display:flex;justify-content:flex-end;background:var(--panel-bg-alt)}.qrlayout-designer .sample-data-grid-container{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:20px}.qrlayout-designer .toggle-group{display:flex;border:1px solid var(--border-color);border-radius:6px;overflow:hidden;width:fit-content}.qrlayout-designer .toggle-btn{padding:6px 10px;background:var(--panel-bg);border:none;cursor:pointer;color:var(--text-secondary);border-right:1px solid var(--border-color);display:flex;align-items:center;justify-content:center}.qrlayout-designer .toggle-btn:last-child{border-right:none}.qrlayout-designer .toggle-btn.active{background:var(--panel-bg-alt);color:var(--primary-color)}.qrlayout-designer .field-buttons{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.qrlayout-designer .field-pill{font-size:.625rem;padding:2px 6px;border-radius:4px;background:var(--panel-bg-alt);border:1px solid var(--border-color);cursor:pointer;color:var(--text-secondary)}.qrlayout-designer .field-pill:hover{border-color:var(--primary-color);color:var(--primary-color)}.qrlayout-designer .editor-item{position:absolute;border:2px solid transparent;box-sizing:border-box;cursor:move}.qrlayout-designer .editor-item.selected{border:2px solid var(--primary-color);background:#6366f10d}.qrlayout-designer .resize-handle{position:absolute;width:8px;height:8px;background:#fff;border:2px solid var(--primary-color);border-radius:50%;bottom:-5px;right:-5px;cursor:nwse-resize;z-index:10}.qrlayout-designer .toolbar{position:absolute;top:24px;right:24px;display:flex;gap:12px;z-index:20}.qrlayout-designer .sidebar-toggle{display:none;background:var(--panel-bg);border:1px solid var(--border-color);color:var(--text-primary);width:40px;height:40px;border-radius:8px;align-items:center;justify-content:center;cursor:pointer;position:absolute;top:12px;z-index:100;box-shadow:var(--shadow-md);transition:all .3s cubic-bezier(.4,0,.2,1)}.qrlayout-designer .sidebar-toggle:hover{background-color:var(--panel-bg-alt);border-color:var(--primary-color);color:var(--primary-color)}.qrlayout-designer #toggle-left{left:12px}.qrlayout-designer #toggle-right{right:12px}@media(max-width:768px){.qrlayout-designer header{padding:16px;height:auto;flex-direction:column;gap:12px;align-items:flex-start}.qrlayout-designer header h1{font-size:1.125rem}.qrlayout-designer .logo-icon{width:28px;height:28px;font-size:.75rem}.qrlayout-designer header .btn{padding:6px 10px;font-size:.75rem;height:32px}.qrlayout-designer .sidebar,.qrlayout-designer .sidebar-right{position:fixed;top:64px;bottom:0;z-index:50;width:100%;max-width:320px;box-shadow:var(--shadow-lg);transition:transform .3s cubic-bezier(.4,0,.2,1)}.qrlayout-designer .sidebar{left:0;transform:translate(-100%)}.qrlayout-designer .sidebar-right{right:0;transform:translate(100%)}.qrlayout-designer .sidebar.show,.qrlayout-designer .sidebar-right.show{transform:translate(0)}.qrlayout-designer .sidebar-toggle{display:flex}.qrlayout-designer .preview-area{padding:20px}.qrlayout-designer .canvas-wrapper{max-width:100%;overflow:auto}@media(max-width:480px){.qrlayout-designer header h1 span{display:none}.qrlayout-designer .sidebar-toggle{width:36px;height:36px;top:8px}.qrlayout-designer #toggle-left{left:8px}.qrlayout-designer #toggle-right{right:8px}.qrlayout-designer .preview-area{padding:10px}}}
|
package/dist/qrlayout-ui.js
CHANGED
|
@@ -2,7 +2,7 @@ import { StickerPrinter as v } from "qrlayout-core";
|
|
|
2
2
|
import { StickerPrinter as w } from "qrlayout-core";
|
|
3
3
|
class m {
|
|
4
4
|
constructor(e) {
|
|
5
|
-
this.selectedElementId = null, this.isDarkMode = !1, this.pxPerUnit = 1, this.container = e.element, this.printer = new v(), this.onSaveCallback = e.onSave, this.entitySchemas = e.entitySchemas || {}, this.currentLayout = e.initialLayout || {
|
|
5
|
+
this.selectedElementId = null, this.isDarkMode = !1, this.pxPerUnit = 1, this.isDragging = !1, this.container = e.element, this.printer = new v(), this.onSaveCallback = e.onSave, this.entitySchemas = e.entitySchemas || {}, this.currentLayout = e.initialLayout || {
|
|
6
6
|
id: "layout-" + Date.now(),
|
|
7
7
|
name: "New Layout",
|
|
8
8
|
targetEntity: "",
|
|
@@ -14,7 +14,7 @@ class m {
|
|
|
14
14
|
}, this.init();
|
|
15
15
|
}
|
|
16
16
|
init() {
|
|
17
|
-
this.renderTemplate(), this.cacheDOM(), this.renderEntityOptions(), this.syncInputsFromLayout(), this.bindEvents(), this.renderElementsList(), this.updatePreview();
|
|
17
|
+
this.renderTemplate(), this.cacheDOM(), this.renderEntityOptions(), this.syncInputsFromLayout(), this.bindEvents(), this.renderSampleDataEditor(), this.renderElementsList(), this.updatePreview();
|
|
18
18
|
}
|
|
19
19
|
renderTemplate() {
|
|
20
20
|
this.container.classList.add("qrlayout-designer"), this.container.innerHTML = `
|
|
@@ -85,6 +85,14 @@ class m {
|
|
|
85
85
|
</div>
|
|
86
86
|
<div data-el="elements-container" class="element-list" style="margin-top: 8px;"></div>
|
|
87
87
|
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Sample Data Trigger -->
|
|
90
|
+
<div class="sidebar-section" style="margin-top: auto; border-top: 1px solid var(--border-color); border-bottom: none;">
|
|
91
|
+
<button class="btn btn-outline btn-block" data-action="edit-sample-data" style="gap: 10px;">
|
|
92
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><path d="M8 13h2"/><path d="M8 17h2"/><path d="M14 13h2"/><path d="M14 17h2"/></svg>
|
|
93
|
+
Edit Sample Data
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
88
96
|
</aside>
|
|
89
97
|
|
|
90
98
|
<!-- CENTER: CANVAS -->
|
|
@@ -108,6 +116,25 @@ class m {
|
|
|
108
116
|
</aside>
|
|
109
117
|
</div>
|
|
110
118
|
</div>
|
|
119
|
+
|
|
120
|
+
<!-- MODAL FOR SAMPLE DATA -->
|
|
121
|
+
<div class="modal-overlay" data-el="sample-data-modal">
|
|
122
|
+
<div class="modal-content">
|
|
123
|
+
<div class="modal-header">
|
|
124
|
+
<h3>Edit Sample Data</h3>
|
|
125
|
+
<button class="btn-close" data-action="close-modal">×</button>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="modal-body">
|
|
128
|
+
<p style="font-size: 0.8125rem; color: var(--text-secondary); margin-bottom: 20px;">
|
|
129
|
+
Update the values below to see how they appear on your layout in real-time.
|
|
130
|
+
</p>
|
|
131
|
+
<div data-el="sample-data-container" class="sample-data-grid"></div>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="modal-footer">
|
|
134
|
+
<button class="btn btn-primary" data-action="close-modal">Done Editing</button>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
111
138
|
`;
|
|
112
139
|
}
|
|
113
140
|
cacheDOM() {
|
|
@@ -121,16 +148,17 @@ class m {
|
|
|
121
148
|
labelWidth: e('[data-label="width"]'),
|
|
122
149
|
labelHeight: e('[data-label="height"]'),
|
|
123
150
|
bg: t("bg"),
|
|
124
|
-
bgPreview: e('[data-el="bg-preview"]')
|
|
125
|
-
|
|
151
|
+
bgPreview: e('[data-el="bg-preview"]'),
|
|
152
|
+
sampleData: e('[data-el="sample-data-container"]')
|
|
153
|
+
}, this.sampleDataContainer = this.inputs.sampleData;
|
|
126
154
|
}
|
|
127
155
|
renderEntityOptions() {
|
|
128
156
|
const e = this.inputs.entity;
|
|
129
157
|
for (; e.options.length > 1; )
|
|
130
158
|
e.remove(1);
|
|
131
159
|
Object.keys(this.entitySchemas).forEach((t) => {
|
|
132
|
-
const i = this.entitySchemas[t],
|
|
133
|
-
|
|
160
|
+
const i = this.entitySchemas[t], a = document.createElement("option");
|
|
161
|
+
a.value = t, a.text = i.label || t, e.add(a);
|
|
134
162
|
});
|
|
135
163
|
}
|
|
136
164
|
syncInputsFromLayout() {
|
|
@@ -139,19 +167,25 @@ class m {
|
|
|
139
167
|
bindEvents() {
|
|
140
168
|
this.container.querySelector('[data-action="toggle-theme"]')?.addEventListener("click", (t) => {
|
|
141
169
|
this.isDarkMode = !this.isDarkMode, this.container.classList.toggle("dark-mode", this.isDarkMode);
|
|
142
|
-
const i = t.currentTarget,
|
|
143
|
-
this.isDarkMode ? (
|
|
170
|
+
const i = t.currentTarget, a = i.querySelector(".sun-icon"), s = i.querySelector(".moon-icon");
|
|
171
|
+
this.isDarkMode ? (a.style.display = "block", s.style.display = "none") : (a.style.display = "none", s.style.display = "block");
|
|
144
172
|
}), this.container.querySelector('[data-action="export-json"]')?.addEventListener("click", () => {
|
|
145
|
-
const t = new Blob([JSON.stringify(this.currentLayout, null, 2)], { type: "application/json" }), i = URL.createObjectURL(t),
|
|
146
|
-
|
|
173
|
+
const t = new Blob([JSON.stringify(this.currentLayout, null, 2)], { type: "application/json" }), i = URL.createObjectURL(t), a = document.createElement("a");
|
|
174
|
+
a.href = i, a.download = `${this.currentLayout.name.toLowerCase().replace(/ /g, "-")}.json`, a.click();
|
|
147
175
|
}), this.container.querySelector('[data-action="save"]')?.addEventListener("click", () => {
|
|
148
176
|
this.onSaveCallback && this.onSaveCallback(this.currentLayout);
|
|
177
|
+
}), this.container.querySelector('[data-action="edit-sample-data"]')?.addEventListener("click", () => {
|
|
178
|
+
this.showSampleDataModal();
|
|
179
|
+
}), this.container.querySelectorAll('[data-action="close-modal"]').forEach((t) => {
|
|
180
|
+
t.addEventListener("click", () => {
|
|
181
|
+
this.container.querySelector('[data-el="sample-data-modal"]')?.classList.remove("show");
|
|
182
|
+
});
|
|
149
183
|
}), this.container.querySelector("#toggle-left")?.addEventListener("click", () => {
|
|
150
184
|
this.leftSidebar.classList.toggle("show");
|
|
151
185
|
}), this.container.querySelector("#toggle-right")?.addEventListener("click", () => {
|
|
152
186
|
this.rightSidebar.classList.toggle("show");
|
|
153
187
|
}), this.inputs.entity.onchange = (t) => {
|
|
154
|
-
this.currentLayout.targetEntity = t.target.value, this.renderPropertyPanel(), this.updatePreview();
|
|
188
|
+
this.currentLayout.targetEntity = t.target.value, this.renderSampleDataEditor(), this.renderPropertyPanel(), this.updatePreview();
|
|
155
189
|
}, this.inputs.name.oninput = (t) => this.currentLayout.name = t.target.value, this.inputs.width.oninput = (t) => {
|
|
156
190
|
this.currentLayout.width = parseFloat(t.target.value) || 100, this.updatePreview();
|
|
157
191
|
}, this.inputs.height.oninput = (t) => {
|
|
@@ -175,9 +209,44 @@ class m {
|
|
|
175
209
|
async updatePreview() {
|
|
176
210
|
if (!this.canvas || !this.currentLayout) return;
|
|
177
211
|
const e = this.currentLayout.targetEntity && this.entitySchemas[this.currentLayout.targetEntity] ? this.entitySchemas[this.currentLayout.targetEntity].sampleData : {};
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
212
|
+
if (!this.isDragging) {
|
|
213
|
+
await this.printer.renderToCanvas(this.currentLayout, e, this.canvas);
|
|
214
|
+
const t = this.canvas.getBoundingClientRect();
|
|
215
|
+
t.width > 0 && this.currentLayout.width > 0 && (this.pxPerUnit = t.width / this.currentLayout.width);
|
|
216
|
+
}
|
|
217
|
+
this.updateEditorOverlay();
|
|
218
|
+
}
|
|
219
|
+
showSampleDataModal() {
|
|
220
|
+
this.renderSampleDataEditor(), this.container.querySelector('[data-el="sample-data-modal"]')?.classList.add("show");
|
|
221
|
+
}
|
|
222
|
+
renderSampleDataEditor() {
|
|
223
|
+
if (!this.sampleDataContainer) return;
|
|
224
|
+
const e = this.currentLayout.targetEntity;
|
|
225
|
+
if (!e || !this.entitySchemas[e]) {
|
|
226
|
+
this.sampleDataContainer.innerHTML = `
|
|
227
|
+
<div style="font-size: 0.75rem; color: var(--text-secondary); padding: 12px; background: var(--panel-bg-alt); border-radius: 8px; border: 1px dashed var(--border-color); text-align: center; display: flex; flex-direction: column; gap: 8px; align-items: center;">
|
|
228
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.5;"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
229
|
+
<span>Select an entity above to see fields</span>
|
|
230
|
+
</div>
|
|
231
|
+
`;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const t = this.entitySchemas[e];
|
|
235
|
+
this.sampleDataContainer.innerHTML = "";
|
|
236
|
+
const i = document.createElement("div");
|
|
237
|
+
i.className = "sample-data-grid-container", t.fields.forEach((a) => {
|
|
238
|
+
const s = document.createElement("div");
|
|
239
|
+
s.className = "form-group", s.style.margin = "0";
|
|
240
|
+
const r = document.createElement("label");
|
|
241
|
+
r.style.display = "flex", r.style.justifyContent = "space-between", r.innerHTML = `
|
|
242
|
+
<span>${a.label || a.name}</span>
|
|
243
|
+
<code style="font-size: 0.625rem; opacity: 0.6; background: var(--panel-bg-alt); padding: 1px 4px; border-radius: 3px;">{{${a.name}}}</code>
|
|
244
|
+
`;
|
|
245
|
+
const n = document.createElement("input");
|
|
246
|
+
n.type = "text", n.value = t.sampleData[a.name] || "", n.placeholder = `Enter sample ${a.name}...`, n.style.fontSize = "0.8125rem", n.oninput = (l) => {
|
|
247
|
+
t.sampleData[a.name] = l.target.value, this.updatePreview();
|
|
248
|
+
}, s.appendChild(r), s.appendChild(n), i.appendChild(s);
|
|
249
|
+
}), this.sampleDataContainer.appendChild(i);
|
|
181
250
|
}
|
|
182
251
|
renderElementsList() {
|
|
183
252
|
this.elementsContainer.innerHTML = "", this.currentLayout.elements.forEach((e) => {
|
|
@@ -200,7 +269,7 @@ class m {
|
|
|
200
269
|
return;
|
|
201
270
|
}
|
|
202
271
|
this.container.offsetWidth <= 768 ? e && (e.style.display = "flex") : e && (e.style.display = "none");
|
|
203
|
-
const t = this.currentLayout.elements.find((
|
|
272
|
+
const t = this.currentLayout.elements.find((n) => n.id === this.selectedElementId);
|
|
204
273
|
if (!t) return;
|
|
205
274
|
this.propertyPanel.style.display = "block", this.propContent.innerHTML = `
|
|
206
275
|
<div class="form-group">
|
|
@@ -253,63 +322,71 @@ class m {
|
|
|
253
322
|
</div>
|
|
254
323
|
` : ""}
|
|
255
324
|
`;
|
|
256
|
-
const i = this.propContent.querySelector('[data-el="field-suggestions"]'),
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
t.content += `{{${
|
|
261
|
-
}, i.appendChild(
|
|
325
|
+
const i = this.propContent.querySelector('[data-el="field-suggestions"]'), a = this.currentLayout.targetEntity ? this.entitySchemas[this.currentLayout.targetEntity] : null;
|
|
326
|
+
a && i && a.fields.forEach((n) => {
|
|
327
|
+
const l = document.createElement("div");
|
|
328
|
+
l.className = "field-pill", l.innerText = `+ ${n.label}`, l.onclick = () => {
|
|
329
|
+
t.content += `{{${n.name}}}`, this.renderPropertyPanel(), this.updatePreview();
|
|
330
|
+
}, i.appendChild(l);
|
|
262
331
|
});
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
t.qrSeparator =
|
|
332
|
+
const s = this.propContent.querySelector("#prop-qr-separator");
|
|
333
|
+
s && s.addEventListener("input", (n) => {
|
|
334
|
+
t.qrSeparator = n.target.value, this.updatePreview();
|
|
266
335
|
});
|
|
267
|
-
const
|
|
268
|
-
const c = this.propContent.querySelector(`[data-prop="${
|
|
269
|
-
c && c.addEventListener("input", (
|
|
270
|
-
const h =
|
|
271
|
-
|
|
336
|
+
const r = (n, l, d = !1, o) => {
|
|
337
|
+
const c = this.propContent.querySelector(`[data-prop="${n}"]`);
|
|
338
|
+
c && c.addEventListener("input", (u) => {
|
|
339
|
+
const h = u.target.value, p = d ? parseFloat(h) || 0 : h;
|
|
340
|
+
o ? (t.style || (t.style = {}), t.style[o] = p) : t[l] = p, this.updatePreview();
|
|
272
341
|
});
|
|
273
342
|
};
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
t.style || (t.style = {}), t.style.textAlign =
|
|
343
|
+
r("content-val", "content"), r("x", "x", !0), r("y", "y", !0), r("w", "w", !0), r("h", "h", !0), r("fontSize", "style", !0, "fontSize"), r("fontWeight", "style", !1, "fontWeight"), this.propContent.querySelectorAll(".prop-align-h").forEach((n) => {
|
|
344
|
+
n.addEventListener("click", () => {
|
|
345
|
+
t.style || (t.style = {}), t.style.textAlign = n.dataset.val, this.renderPropertyPanel(), this.updatePreview();
|
|
277
346
|
});
|
|
278
|
-
}), this.propContent.querySelectorAll(".prop-align-v").forEach((
|
|
279
|
-
|
|
280
|
-
t.style || (t.style = {}), t.style.verticalAlign =
|
|
347
|
+
}), this.propContent.querySelectorAll(".prop-align-v").forEach((n) => {
|
|
348
|
+
n.addEventListener("click", () => {
|
|
349
|
+
t.style || (t.style = {}), t.style.verticalAlign = n.dataset.val, this.renderPropertyPanel(), this.updatePreview();
|
|
281
350
|
});
|
|
282
351
|
});
|
|
283
352
|
}
|
|
284
353
|
updateEditorOverlay() {
|
|
285
|
-
!this.editorOverlay || !this.canvas
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
354
|
+
if (!this.editorOverlay || !this.canvas) return;
|
|
355
|
+
this.isDragging || (this.editorOverlay.style.width = this.canvas.style.width, this.editorOverlay.style.height = this.canvas.style.height);
|
|
356
|
+
const e = new Set(this.currentLayout.elements.map((t) => t.id));
|
|
357
|
+
this.editorOverlay.querySelectorAll(".editor-item").forEach((t) => {
|
|
358
|
+
e.has(t.dataset.id) || t.remove();
|
|
359
|
+
}), this.currentLayout.elements.forEach((t) => {
|
|
360
|
+
let i = this.editorOverlay.querySelector(`.editor-item[data-id="${t.id}"]`);
|
|
361
|
+
if (!i) {
|
|
362
|
+
i = document.createElement("div"), i.className = "editor-item", i.dataset.id = t.id;
|
|
363
|
+
const a = document.createElement("div");
|
|
364
|
+
a.className = "resize-handle", i.appendChild(a), a.onmousedown = (s) => {
|
|
365
|
+
s.preventDefault(), s.stopPropagation(), this.startElementResize(s, t, i);
|
|
366
|
+
}, i.onmousedown = (s) => {
|
|
367
|
+
s.target.classList.contains("resize-handle") || (s.preventDefault(), this.selectElement(t.id), this.startElementDrag(s, t, i));
|
|
368
|
+
}, this.editorOverlay.appendChild(i);
|
|
292
369
|
}
|
|
293
|
-
t.
|
|
294
|
-
|
|
295
|
-
}, this.editorOverlay.appendChild(t);
|
|
296
|
-
}));
|
|
370
|
+
i.classList.toggle("selected", this.selectedElementId === t.id), i.style.left = `${t.x * this.pxPerUnit}px`, i.style.top = `${t.y * this.pxPerUnit}px`, i.style.width = `${t.w * this.pxPerUnit}px`, i.style.height = `${t.h * this.pxPerUnit}px`;
|
|
371
|
+
});
|
|
297
372
|
}
|
|
298
|
-
startElementResize(e, t) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
373
|
+
startElementResize(e, t, i) {
|
|
374
|
+
this.isDragging = !0;
|
|
375
|
+
const a = e.clientX, s = e.clientY, r = t.w, n = t.h, l = (o) => {
|
|
376
|
+
t.w = Math.max(1, r + (o.clientX - a) / this.pxPerUnit), t.h = Math.max(1, n + (o.clientY - s) / this.pxPerUnit), i.style.width = `${t.w * this.pxPerUnit}px`, i.style.height = `${t.h * this.pxPerUnit}px`, this.renderPropertyPanel();
|
|
377
|
+
}, d = () => {
|
|
378
|
+
this.isDragging = !1, this.updatePreview(), this.renderPropertyPanel(), window.removeEventListener("mousemove", l), window.removeEventListener("mouseup", d);
|
|
303
379
|
};
|
|
304
|
-
window.addEventListener("mousemove",
|
|
380
|
+
window.addEventListener("mousemove", l), window.addEventListener("mouseup", d);
|
|
305
381
|
}
|
|
306
|
-
startElementDrag(e, t) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
382
|
+
startElementDrag(e, t, i) {
|
|
383
|
+
this.isDragging = !0;
|
|
384
|
+
const a = e.clientX, s = e.clientY, r = t.x, n = t.y, l = (o) => {
|
|
385
|
+
t.x = r + (o.clientX - a) / this.pxPerUnit, t.y = n + (o.clientY - s) / this.pxPerUnit, i.style.left = `${t.x * this.pxPerUnit}px`, i.style.top = `${t.y * this.pxPerUnit}px`, this.renderPropertyPanel();
|
|
386
|
+
}, d = () => {
|
|
387
|
+
this.isDragging = !1, this.updatePreview(), this.renderPropertyPanel(), window.removeEventListener("mousemove", l), window.removeEventListener("mouseup", d);
|
|
311
388
|
};
|
|
312
|
-
window.addEventListener("mousemove",
|
|
389
|
+
window.addEventListener("mousemove", l), window.addEventListener("mouseup", d);
|
|
313
390
|
}
|
|
314
391
|
destroy() {
|
|
315
392
|
this.container.innerHTML = "", this.container.classList.remove("qrlayout-designer");
|
package/dist/qrlayout-ui.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(o,
|
|
1
|
+
(function(o,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("qrlayout-core")):typeof define=="function"&&define.amd?define(["exports","qrlayout-core"],h):(o=typeof globalThis<"u"?globalThis:o||self,h(o.QRLayoutUI={},o.QRLayoutCore))})(this,(function(o,h){"use strict";class y{constructor(e){this.selectedElementId=null,this.isDarkMode=!1,this.pxPerUnit=1,this.isDragging=!1,this.container=e.element,this.printer=new h.StickerPrinter,this.onSaveCallback=e.onSave,this.entitySchemas=e.entitySchemas||{},this.currentLayout=e.initialLayout||{id:"layout-"+Date.now(),name:"New Layout",targetEntity:"",width:100,height:60,unit:"mm",backgroundColor:"#ffffff",elements:[]},this.init()}init(){this.renderTemplate(),this.cacheDOM(),this.renderEntityOptions(),this.syncInputsFromLayout(),this.bindEvents(),this.renderSampleDataEditor(),this.renderElementsList(),this.updatePreview()}renderTemplate(){this.container.classList.add("qrlayout-designer"),this.container.innerHTML=`
|
|
2
2
|
<header>
|
|
3
3
|
<div data-el="header-left"></div>
|
|
4
4
|
<div style="display: flex; gap: 12px; align-items: center;">
|
|
@@ -66,6 +66,14 @@
|
|
|
66
66
|
</div>
|
|
67
67
|
<div data-el="elements-container" class="element-list" style="margin-top: 8px;"></div>
|
|
68
68
|
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Sample Data Trigger -->
|
|
71
|
+
<div class="sidebar-section" style="margin-top: auto; border-top: 1px solid var(--border-color); border-bottom: none;">
|
|
72
|
+
<button class="btn btn-outline btn-block" data-action="edit-sample-data" style="gap: 10px;">
|
|
73
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><path d="M8 13h2"/><path d="M8 17h2"/><path d="M14 13h2"/><path d="M14 17h2"/></svg>
|
|
74
|
+
Edit Sample Data
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
69
77
|
</aside>
|
|
70
78
|
|
|
71
79
|
<!-- CENTER: CANVAS -->
|
|
@@ -89,12 +97,39 @@
|
|
|
89
97
|
</aside>
|
|
90
98
|
</div>
|
|
91
99
|
</div>
|
|
92
|
-
|
|
100
|
+
|
|
101
|
+
<!-- MODAL FOR SAMPLE DATA -->
|
|
102
|
+
<div class="modal-overlay" data-el="sample-data-modal">
|
|
103
|
+
<div class="modal-content">
|
|
104
|
+
<div class="modal-header">
|
|
105
|
+
<h3>Edit Sample Data</h3>
|
|
106
|
+
<button class="btn-close" data-action="close-modal">×</button>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="modal-body">
|
|
109
|
+
<p style="font-size: 0.8125rem; color: var(--text-secondary); margin-bottom: 20px;">
|
|
110
|
+
Update the values below to see how they appear on your layout in real-time.
|
|
111
|
+
</p>
|
|
112
|
+
<div data-el="sample-data-container" class="sample-data-grid"></div>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="modal-footer">
|
|
115
|
+
<button class="btn btn-primary" data-action="close-modal">Done Editing</button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
`}cacheDOM(){const e=i=>this.container.querySelector(i),t=i=>this.container.querySelector(`[data-input="${i}"]`);this.canvas=e('[data-el="preview-canvas"]'),this.editorOverlay=e('[data-el="editor-overlay"]'),this.elementsContainer=e('[data-el="elements-container"]'),this.propertyPanel=e('[data-el="property-panel"]'),this.propContent=e('[data-el="prop-content"]'),this.leftSidebar=e(".sidebar"),this.rightSidebar=e(".sidebar-right"),this.inputs={entity:t("entity"),name:t("name"),width:t("width"),height:t("height"),unit:t("unit"),labelWidth:e('[data-label="width"]'),labelHeight:e('[data-label="height"]'),bg:t("bg"),bgPreview:e('[data-el="bg-preview"]'),sampleData:e('[data-el="sample-data-container"]')},this.sampleDataContainer=this.inputs.sampleData}renderEntityOptions(){const e=this.inputs.entity;for(;e.options.length>1;)e.remove(1);Object.keys(this.entitySchemas).forEach(t=>{const i=this.entitySchemas[t],a=document.createElement("option");a.value=t,a.text=i.label||t,e.add(a)})}syncInputsFromLayout(){this.inputs.entity.value=this.currentLayout.targetEntity||"",this.inputs.name.value=this.currentLayout.name,this.inputs.width.value=String(this.currentLayout.width),this.inputs.height.value=String(this.currentLayout.height),this.inputs.unit.value=this.currentLayout.unit,this.inputs.labelWidth.innerText=`Width (${this.currentLayout.unit})`,this.inputs.labelHeight.innerText=`Height (${this.currentLayout.unit})`,this.inputs.bg.value=this.currentLayout.backgroundColor||"#ffffff",this.inputs.bgPreview.style.backgroundColor=this.inputs.bg.value}bindEvents(){this.container.querySelector('[data-action="toggle-theme"]')?.addEventListener("click",t=>{this.isDarkMode=!this.isDarkMode,this.container.classList.toggle("dark-mode",this.isDarkMode);const i=t.currentTarget,a=i.querySelector(".sun-icon"),s=i.querySelector(".moon-icon");this.isDarkMode?(a.style.display="block",s.style.display="none"):(a.style.display="none",s.style.display="block")}),this.container.querySelector('[data-action="export-json"]')?.addEventListener("click",()=>{const t=new Blob([JSON.stringify(this.currentLayout,null,2)],{type:"application/json"}),i=URL.createObjectURL(t),a=document.createElement("a");a.href=i,a.download=`${this.currentLayout.name.toLowerCase().replace(/ /g,"-")}.json`,a.click()}),this.container.querySelector('[data-action="save"]')?.addEventListener("click",()=>{this.onSaveCallback&&this.onSaveCallback(this.currentLayout)}),this.container.querySelector('[data-action="edit-sample-data"]')?.addEventListener("click",()=>{this.showSampleDataModal()}),this.container.querySelectorAll('[data-action="close-modal"]').forEach(t=>{t.addEventListener("click",()=>{this.container.querySelector('[data-el="sample-data-modal"]')?.classList.remove("show")})}),this.container.querySelector("#toggle-left")?.addEventListener("click",()=>{this.leftSidebar.classList.toggle("show")}),this.container.querySelector("#toggle-right")?.addEventListener("click",()=>{this.rightSidebar.classList.toggle("show")}),this.inputs.entity.onchange=t=>{this.currentLayout.targetEntity=t.target.value,this.renderSampleDataEditor(),this.renderPropertyPanel(),this.updatePreview()},this.inputs.name.oninput=t=>this.currentLayout.name=t.target.value,this.inputs.width.oninput=t=>{this.currentLayout.width=parseFloat(t.target.value)||100,this.updatePreview()},this.inputs.height.oninput=t=>{this.currentLayout.height=parseFloat(t.target.value)||60,this.updatePreview()},this.inputs.unit.onchange=t=>{this.currentLayout.unit=t.target.value,this.inputs.labelWidth.innerText=`Width (${this.currentLayout.unit})`,this.inputs.labelHeight.innerText=`Height (${this.currentLayout.unit})`,this.updatePreview()},this.inputs.bg.oninput=t=>{this.currentLayout.backgroundColor=t.target.value,this.inputs.bgPreview.style.backgroundColor=this.currentLayout.backgroundColor,this.updatePreview()},this.container.querySelector('[data-action="add-text"]')?.addEventListener("click",()=>{const t="t"+Date.now();this.currentLayout.elements.push({id:t,type:"text",x:10,y:10,w:40,h:10,content:"New Text"}),this.selectElement(t),this.updatePreview()}),this.container.querySelector('[data-action="add-qr"]')?.addEventListener("click",()=>{const t="q"+Date.now();this.currentLayout.elements.push({id:t,type:"qr",x:5,y:5,w:20,h:20,content:"{{id}}"}),this.selectElement(t),this.updatePreview()}),this.container.querySelector('[data-action="delete-element"]')?.addEventListener("click",()=>{this.currentLayout.elements=this.currentLayout.elements.filter(t=>t.id!==this.selectedElementId),this.selectElement(null),this.updatePreview()}),new ResizeObserver(()=>{this.container.offsetWidth>768&&(this.leftSidebar.classList.remove("show"),this.rightSidebar.classList.remove("show")),this.renderPropertyPanel()}).observe(this.container)}async updatePreview(){if(!this.canvas||!this.currentLayout)return;const e=this.currentLayout.targetEntity&&this.entitySchemas[this.currentLayout.targetEntity]?this.entitySchemas[this.currentLayout.targetEntity].sampleData:{};if(!this.isDragging){await this.printer.renderToCanvas(this.currentLayout,e,this.canvas);const t=this.canvas.getBoundingClientRect();t.width>0&&this.currentLayout.width>0&&(this.pxPerUnit=t.width/this.currentLayout.width)}this.updateEditorOverlay()}showSampleDataModal(){this.renderSampleDataEditor(),this.container.querySelector('[data-el="sample-data-modal"]')?.classList.add("show")}renderSampleDataEditor(){if(!this.sampleDataContainer)return;const e=this.currentLayout.targetEntity;if(!e||!this.entitySchemas[e]){this.sampleDataContainer.innerHTML=`
|
|
120
|
+
<div style="font-size: 0.75rem; color: var(--text-secondary); padding: 12px; background: var(--panel-bg-alt); border-radius: 8px; border: 1px dashed var(--border-color); text-align: center; display: flex; flex-direction: column; gap: 8px; align-items: center;">
|
|
121
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.5;"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
122
|
+
<span>Select an entity above to see fields</span>
|
|
123
|
+
</div>
|
|
124
|
+
`;return}const t=this.entitySchemas[e];this.sampleDataContainer.innerHTML="";const i=document.createElement("div");i.className="sample-data-grid-container",t.fields.forEach(a=>{const s=document.createElement("div");s.className="form-group",s.style.margin="0";const r=document.createElement("label");r.style.display="flex",r.style.justifyContent="space-between",r.innerHTML=`
|
|
125
|
+
<span>${a.label||a.name}</span>
|
|
126
|
+
<code style="font-size: 0.625rem; opacity: 0.6; background: var(--panel-bg-alt); padding: 1px 4px; border-radius: 3px;">{{${a.name}}}</code>
|
|
127
|
+
`;const n=document.createElement("input");n.type="text",n.value=t.sampleData[a.name]||"",n.placeholder=`Enter sample ${a.name}...`,n.style.fontSize="0.8125rem",n.oninput=l=>{t.sampleData[a.name]=l.target.value,this.updatePreview()},s.appendChild(r),s.appendChild(n),i.appendChild(s)}),this.sampleDataContainer.appendChild(i)}renderElementsList(){this.elementsContainer.innerHTML="",this.currentLayout.elements.forEach(e=>{const t=document.createElement("div");t.className=`element-item ${this.selectedElementId===e.id?"active":""}`,t.innerHTML=`
|
|
93
128
|
<div class="element-info">
|
|
94
129
|
<span class="element-name">${e.type.toUpperCase()}</span>
|
|
95
130
|
<span class="element-sub">${String(e.content).substring(0,20)}</span>
|
|
96
131
|
</div>
|
|
97
|
-
`,t.onclick=()=>this.selectElement(e.id),this.elementsContainer.appendChild(t)})}selectElement(e){this.selectedElementId=e,this.renderElementsList(),this.renderPropertyPanel(),this.updateEditorOverlay(),e&&this.container.offsetWidth<=768&&this.rightSidebar.classList.add("show")}renderPropertyPanel(){const e=this.container.querySelector("#toggle-right");if(!this.selectedElementId){this.propertyPanel.style.display="none",e&&(e.style.display="none");return}this.container.offsetWidth<=768?e&&(e.style.display="flex"):e&&(e.style.display="none");const t=this.currentLayout.elements.find(
|
|
132
|
+
`,t.onclick=()=>this.selectElement(e.id),this.elementsContainer.appendChild(t)})}selectElement(e){this.selectedElementId=e,this.renderElementsList(),this.renderPropertyPanel(),this.updateEditorOverlay(),e&&this.container.offsetWidth<=768&&this.rightSidebar.classList.add("show")}renderPropertyPanel(){const e=this.container.querySelector("#toggle-right");if(!this.selectedElementId){this.propertyPanel.style.display="none",e&&(e.style.display="none");return}this.container.offsetWidth<=768?e&&(e.style.display="flex"):e&&(e.style.display="none");const t=this.currentLayout.elements.find(n=>n.id===this.selectedElementId);if(!t)return;this.propertyPanel.style.display="block",this.propContent.innerHTML=`
|
|
98
133
|
<div class="form-group">
|
|
99
134
|
${t.type==="qr"?`
|
|
100
135
|
<label>Field Separator</label>
|
|
@@ -144,4 +179,4 @@
|
|
|
144
179
|
</div>
|
|
145
180
|
</div>
|
|
146
181
|
`:""}
|
|
147
|
-
`;const i=this.propContent.querySelector('[data-el="field-suggestions"]'),
|
|
182
|
+
`;const i=this.propContent.querySelector('[data-el="field-suggestions"]'),a=this.currentLayout.targetEntity?this.entitySchemas[this.currentLayout.targetEntity]:null;a&&i&&a.fields.forEach(n=>{const l=document.createElement("div");l.className="field-pill",l.innerText=`+ ${n.label}`,l.onclick=()=>{t.content+=`{{${n.name}}}`,this.renderPropertyPanel(),this.updatePreview()},i.appendChild(l)});const s=this.propContent.querySelector("#prop-qr-separator");s&&s.addEventListener("input",n=>{t.qrSeparator=n.target.value,this.updatePreview()});const r=(n,l,c=!1,d)=>{const p=this.propContent.querySelector(`[data-prop="${n}"]`);p&&p.addEventListener("input",g=>{const u=g.target.value,v=c?parseFloat(u)||0:u;d?(t.style||(t.style={}),t.style[d]=v):t[l]=v,this.updatePreview()})};r("content-val","content"),r("x","x",!0),r("y","y",!0),r("w","w",!0),r("h","h",!0),r("fontSize","style",!0,"fontSize"),r("fontWeight","style",!1,"fontWeight"),this.propContent.querySelectorAll(".prop-align-h").forEach(n=>{n.addEventListener("click",()=>{t.style||(t.style={}),t.style.textAlign=n.dataset.val,this.renderPropertyPanel(),this.updatePreview()})}),this.propContent.querySelectorAll(".prop-align-v").forEach(n=>{n.addEventListener("click",()=>{t.style||(t.style={}),t.style.verticalAlign=n.dataset.val,this.renderPropertyPanel(),this.updatePreview()})})}updateEditorOverlay(){if(!this.editorOverlay||!this.canvas)return;this.isDragging||(this.editorOverlay.style.width=this.canvas.style.width,this.editorOverlay.style.height=this.canvas.style.height);const e=new Set(this.currentLayout.elements.map(t=>t.id));this.editorOverlay.querySelectorAll(".editor-item").forEach(t=>{e.has(t.dataset.id)||t.remove()}),this.currentLayout.elements.forEach(t=>{let i=this.editorOverlay.querySelector(`.editor-item[data-id="${t.id}"]`);if(!i){i=document.createElement("div"),i.className="editor-item",i.dataset.id=t.id;const a=document.createElement("div");a.className="resize-handle",i.appendChild(a),a.onmousedown=s=>{s.preventDefault(),s.stopPropagation(),this.startElementResize(s,t,i)},i.onmousedown=s=>{s.target.classList.contains("resize-handle")||(s.preventDefault(),this.selectElement(t.id),this.startElementDrag(s,t,i))},this.editorOverlay.appendChild(i)}i.classList.toggle("selected",this.selectedElementId===t.id),i.style.left=`${t.x*this.pxPerUnit}px`,i.style.top=`${t.y*this.pxPerUnit}px`,i.style.width=`${t.w*this.pxPerUnit}px`,i.style.height=`${t.h*this.pxPerUnit}px`})}startElementResize(e,t,i){this.isDragging=!0;const a=e.clientX,s=e.clientY,r=t.w,n=t.h,l=d=>{t.w=Math.max(1,r+(d.clientX-a)/this.pxPerUnit),t.h=Math.max(1,n+(d.clientY-s)/this.pxPerUnit),i.style.width=`${t.w*this.pxPerUnit}px`,i.style.height=`${t.h*this.pxPerUnit}px`,this.renderPropertyPanel()},c=()=>{this.isDragging=!1,this.updatePreview(),this.renderPropertyPanel(),window.removeEventListener("mousemove",l),window.removeEventListener("mouseup",c)};window.addEventListener("mousemove",l),window.addEventListener("mouseup",c)}startElementDrag(e,t,i){this.isDragging=!0;const a=e.clientX,s=e.clientY,r=t.x,n=t.y,l=d=>{t.x=r+(d.clientX-a)/this.pxPerUnit,t.y=n+(d.clientY-s)/this.pxPerUnit,i.style.left=`${t.x*this.pxPerUnit}px`,i.style.top=`${t.y*this.pxPerUnit}px`,this.renderPropertyPanel()},c=()=>{this.isDragging=!1,this.updatePreview(),this.renderPropertyPanel(),window.removeEventListener("mousemove",l),window.removeEventListener("mouseup",c)};window.addEventListener("mousemove",l),window.addEventListener("mouseup",c)}destroy(){this.container.innerHTML="",this.container.classList.remove("qrlayout-designer")}}Object.defineProperty(o,"StickerPrinter",{enumerable:!0,get:()=>h.StickerPrinter}),o.QRLayoutDesigner=y,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qrlayout-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "A framework-agnostic visual designer UI for QR code and label layouts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qr-code",
|
|
7
7
|
"qr-layout",
|
|
8
|
+
"qr-layout-designer",
|
|
8
9
|
"react-qrcode",
|
|
9
10
|
"vue-qrcode",
|
|
10
11
|
"svelte-qrcode",
|
|
11
12
|
"angular-qrcode",
|
|
12
13
|
"drag-drop",
|
|
14
|
+
"drag-and-drop",
|
|
13
15
|
"ui-component",
|
|
14
16
|
"low-code",
|
|
15
17
|
"visual-editor",
|
|
16
18
|
"label-designer",
|
|
17
|
-
"
|
|
19
|
+
"label-printing",
|
|
20
|
+
"sticker-designer",
|
|
21
|
+
"typescript",
|
|
22
|
+
"framework-agnostic"
|
|
18
23
|
],
|
|
19
24
|
"author": "Shashidhar Naik <shashidharnaik8@gmail.com>",
|
|
20
25
|
"license": "MIT",
|
|
@@ -26,7 +31,9 @@
|
|
|
26
31
|
"private": false,
|
|
27
32
|
"type": "module",
|
|
28
33
|
"files": [
|
|
29
|
-
"dist"
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
30
37
|
],
|
|
31
38
|
"main": "./dist/qrlayout-ui.umd.js",
|
|
32
39
|
"module": "./dist/qrlayout-ui.js",
|