@kritzel/vue-engine 0.4.31
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/LICENSE.md +50 -0
- package/README.md +95 -0
- package/dist/components.d.ts +3 -0
- package/dist/components.js +67 -0
- package/dist/composables.d.ts +9 -0
- package/dist/composables.js +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +6 -0
- package/dist/vue-component-lib/utils.d.ts +16 -0
- package/dist/vue-component-lib/utils.js +188 -0
- package/dist/vue-dynamic-object-renderer.d.ts +14 -0
- package/dist/vue-dynamic-object-renderer.js +75 -0
- package/package.json +43 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Software License Agreement
|
|
2
|
+
|
|
3
|
+
**IMPORTANT NOTICE:** This is a source-available, non-commercial license.
|
|
4
|
+
* **Hobbyists & Personal Use:** 100% Free. You are welcome to use, modify, and experiment with this software for personal, hobby, or educational projects.
|
|
5
|
+
* **Commercial & Professional Use:** Strictly Prohibited. If you want to use this software in a corporate environment, for professional client work, or as part of a revenue-generating product, you must obtain a commercial license.
|
|
6
|
+
|
|
7
|
+
To purchase a commercial license, please visit: https://kritzel.io
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## PolyForm Noncommercial License 1.0.0
|
|
12
|
+
|
|
13
|
+
### Acceptance
|
|
14
|
+
By using or distributing this software, you accept this license.
|
|
15
|
+
|
|
16
|
+
### User
|
|
17
|
+
You is the person or organization who accepts this license. You can accept this license for only yourself or for an organization.
|
|
18
|
+
|
|
19
|
+
### Software
|
|
20
|
+
Software is the software that includes this license text.
|
|
21
|
+
|
|
22
|
+
### Practice
|
|
23
|
+
Practice is to use, copy, modify, distribute, or make derivative works of the Software.
|
|
24
|
+
|
|
25
|
+
### Grant
|
|
26
|
+
You may Practice the Software for noncommercial purposes.
|
|
27
|
+
|
|
28
|
+
### Noncommercial Purposes
|
|
29
|
+
Noncommercial purposes are purposes that are not commercial purposes.
|
|
30
|
+
|
|
31
|
+
### Commercial Purposes
|
|
32
|
+
Commercial purposes are purposes intended for or directed toward commercial advantage or monetary compensation. Commercial purposes include, but are not limited to, using the Software to provide services to others for a fee, using the Software as part of a product sold or licensed to others, or using the Software inside a business or for professional work.
|
|
33
|
+
|
|
34
|
+
### Redistribution
|
|
35
|
+
You may redistribute copies of the Software, with or without modification, provided that you include this license text with every copy.
|
|
36
|
+
|
|
37
|
+
### Notices
|
|
38
|
+
You must ensure that any copy of the Software you distribute includes all copyright notices, trademark notices, and other legal notices included in the Software.
|
|
39
|
+
|
|
40
|
+
### No Other Rights
|
|
41
|
+
This license does not grant any patent rights or trademark rights.
|
|
42
|
+
|
|
43
|
+
### Termination
|
|
44
|
+
If you violate any term of this license, your rights under this license terminate immediately.
|
|
45
|
+
|
|
46
|
+
### Defensive Patent Claims
|
|
47
|
+
If you make a patent claim against anyone alleging that the Software infringes a patent, your rights under this license terminate immediately.
|
|
48
|
+
|
|
49
|
+
### No Warranty
|
|
50
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Kritzel Engine for Vue
|
|
2
|
+
|
|
3
|
+
Build infinite canvas and collaborative whiteboard experiences in Vue with Kritzel's native wrapper and TypeScript-first API.
|
|
4
|
+
|
|
5
|
+
Kritzel is a framework-agnostic infinite canvas and collaborative whiteboard engine. It provides web components that work natively in any framework, with a first-class Vue wrapper for idiomatic template integration.
|
|
6
|
+
|
|
7
|
+
Kritzel handles the hard parts of canvas-based applications — infinite pan and zoom, hit-testing, CRDT-based collaboration, undo and redo, and cross-browser rendering — so you can focus on your product's domain logic.
|
|
8
|
+
|
|
9
|
+
**Full documentation:** [kritzel.io/docs/vue/getting-started/quick-start](https://kritzel.io/docs/vue/getting-started/quick-start)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @kritzel/vue-engine
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Render Kritzel in your component:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { KritzelEngine } from '@kritzel/vue-engine'
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<KritzelEngine style="display: block; width: 100%; height: 100vh" />
|
|
28
|
+
</template>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This renders the lower-level engine canvas without the editor toolbar shell.
|
|
32
|
+
|
|
33
|
+
## Optional Enhancements
|
|
34
|
+
|
|
35
|
+
### Persist Canvas State
|
|
36
|
+
|
|
37
|
+
By default, the engine uses an empty sync provider list, so canvas state is reset on reload. To persist objects locally across page reloads, configure `syncConfig` explicitly with `IndexedDBSyncProvider`.
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { KritzelEngine, IndexedDBSyncProvider } from '@kritzel/vue-engine'
|
|
42
|
+
|
|
43
|
+
const syncConfig = {
|
|
44
|
+
providers: [IndexedDBSyncProvider],
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<KritzelEngine :syncConfig="syncConfig" style="display: block; width: 100%; height: 100vh" />
|
|
50
|
+
</template>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Apply Full Viewport Styling
|
|
54
|
+
|
|
55
|
+
To ensure the Kritzel engine occupies the entire viewport and prevents unwanted scrolling, add the following CSS to your global stylesheet (e.g. `styles.css`):
|
|
56
|
+
|
|
57
|
+
```css
|
|
58
|
+
html,
|
|
59
|
+
body,
|
|
60
|
+
#app {
|
|
61
|
+
width: 100dvw;
|
|
62
|
+
height: 100dvh;
|
|
63
|
+
margin: 0;
|
|
64
|
+
padding: 0;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Include Mobile Viewport Meta Tag
|
|
70
|
+
|
|
71
|
+
For optimal rendering and responsiveness on mobile devices, add this meta tag within the `<head>` section of your `index.html`:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<meta
|
|
75
|
+
name="viewport"
|
|
76
|
+
content="viewport-fit=cover, width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0, interactive-widget=resizes-content"
|
|
77
|
+
/>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Fundamentals
|
|
81
|
+
|
|
82
|
+
Kritzel is composed of a small set of core concepts that work together to power your canvas experience:
|
|
83
|
+
|
|
84
|
+
- **Components** — Two entry points, a batteries-included editor and a headless engine, to match any integration style.
|
|
85
|
+
- **Tools** — State-machine interaction handlers that define how users draw, select, and manipulate the canvas.
|
|
86
|
+
- **Objects** — The visual building blocks on the canvas, such as paths, shapes, text, and images with shared spatial properties.
|
|
87
|
+
- **Workspaces** — Containers that manage an entire canvas collection, view state, and editor configuration.
|
|
88
|
+
- **Theming** — Built-in light and dark themes with full CSS variable customization to match your brand.
|
|
89
|
+
- **Collaboration** — Real-time multi-user sync powered by Yjs CRDTs with pluggable transport providers.
|
|
90
|
+
|
|
91
|
+
Learn more in the [full documentation](https://kritzel.io/docs/vue/getting-started/basic-usage).
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
See the [Kritzel License](https://kritzel.io/license). Kritzel is free for personal, hobby, and educational use. Commercial use requires a license — visit [kritzel.io](https://kritzel.io) for details.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/* tslint:disable */
|
|
3
|
+
/* auto-generated vue proxies */
|
|
4
|
+
import { defineContainer } from '@stencil/vue-output-target/runtime';
|
|
5
|
+
import { defineCustomElement as defineKritzelEngine } from '@kritzel/engine/dist/components/kritzel-engine.js';
|
|
6
|
+
export const KritzelEngine = /*@__PURE__*/ defineContainer('kritzel-engine', defineKritzelEngine, [
|
|
7
|
+
'workspaces',
|
|
8
|
+
'editorId',
|
|
9
|
+
'activeWorkspaceId',
|
|
10
|
+
'syncConfig',
|
|
11
|
+
'assetStorageConfig',
|
|
12
|
+
'user',
|
|
13
|
+
'globalContextMenuItems',
|
|
14
|
+
'objectContextMenuItems',
|
|
15
|
+
'scaleMax',
|
|
16
|
+
'scaleMin',
|
|
17
|
+
'cursorTarget',
|
|
18
|
+
'lockDrawingScale',
|
|
19
|
+
'theme',
|
|
20
|
+
'licenseKey',
|
|
21
|
+
'themes',
|
|
22
|
+
'locale',
|
|
23
|
+
'locales',
|
|
24
|
+
'fallbackLocale',
|
|
25
|
+
'viewportBoundaryLeft',
|
|
26
|
+
'viewportBoundaryRight',
|
|
27
|
+
'viewportBoundaryTop',
|
|
28
|
+
'viewportBoundaryBottom',
|
|
29
|
+
'debugInfo',
|
|
30
|
+
'isPanningEnabled',
|
|
31
|
+
'isZoomingEnabled',
|
|
32
|
+
'isEngineReady',
|
|
33
|
+
'activeToolChange',
|
|
34
|
+
'workspacesChange',
|
|
35
|
+
'activeWorkspaceChange',
|
|
36
|
+
'longpress',
|
|
37
|
+
'objectsChange',
|
|
38
|
+
'objectsAdded',
|
|
39
|
+
'objectsRemoved',
|
|
40
|
+
'objectsUpdated',
|
|
41
|
+
'objectsSelectionChange',
|
|
42
|
+
'undoStateChange',
|
|
43
|
+
'objectsInViewportChange',
|
|
44
|
+
'viewportChange',
|
|
45
|
+
'awarenessChange',
|
|
46
|
+
'notificationsChange',
|
|
47
|
+
'contextMenuStateChange',
|
|
48
|
+
'isLoadingChange'
|
|
49
|
+
], [
|
|
50
|
+
'isEngineReady',
|
|
51
|
+
'activeToolChange',
|
|
52
|
+
'workspacesChange',
|
|
53
|
+
'activeWorkspaceChange',
|
|
54
|
+
'longpress',
|
|
55
|
+
'objectsChange',
|
|
56
|
+
'objectsAdded',
|
|
57
|
+
'objectsRemoved',
|
|
58
|
+
'objectsUpdated',
|
|
59
|
+
'objectsSelectionChange',
|
|
60
|
+
'undoStateChange',
|
|
61
|
+
'objectsInViewportChange',
|
|
62
|
+
'viewportChange',
|
|
63
|
+
'awarenessChange',
|
|
64
|
+
'notificationsChange',
|
|
65
|
+
'contextMenuStateChange',
|
|
66
|
+
'isLoadingChange'
|
|
67
|
+
]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the native Kritzel engine element from a specific Vue template ref.
|
|
3
|
+
*
|
|
4
|
+
* Because `<KritzelEngine>` is a Vue component (not a raw custom element),
|
|
5
|
+
* a template ref points to the component instance rather than the DOM element.
|
|
6
|
+
* Pass the same ref that is bound in your template to make sure the resolved
|
|
7
|
+
* engine always belongs to that exact component instance.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getEngineRef(componentRef: string): import("vue").ComputedRef<HTMLKritzelEngineElement>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { computed, useTemplateRef, } from "vue";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the native Kritzel engine element from a specific Vue template ref.
|
|
4
|
+
*
|
|
5
|
+
* Because `<KritzelEngine>` is a Vue component (not a raw custom element),
|
|
6
|
+
* a template ref points to the component instance rather than the DOM element.
|
|
7
|
+
* Pass the same ref that is bound in your template to make sure the resolved
|
|
8
|
+
* engine always belongs to that exact component instance.
|
|
9
|
+
*/
|
|
10
|
+
export function getEngineRef(componentRef) {
|
|
11
|
+
const engineComponent = useTemplateRef(componentRef);
|
|
12
|
+
return computed(() => {
|
|
13
|
+
var _a;
|
|
14
|
+
const value = engineComponent.value;
|
|
15
|
+
if (!value) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return "$el" in value
|
|
19
|
+
? ((_a = value.$el) !== null && _a !== void 0 ? _a : null)
|
|
20
|
+
: value;
|
|
21
|
+
});
|
|
22
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './components';
|
|
2
|
+
export * from './composables';
|
|
3
|
+
export * from './plugin';
|
|
4
|
+
export * from './vue-dynamic-object-renderer';
|
|
5
|
+
export { KritzelBaseObject, KritzelText, KritzelPath, KritzelImage, KritzelLine, KritzelShape, KritzelGroup, KritzelDynamicObject, KritzelDynamicObjectRendererRegistry, KritzelFontRegistry, KritzelBaseTool, KritzelBrushTool, KritzelTextTool, KritzelLineTool, KritzelShapeTool, KritzelEraserTool, KritzelSelectionTool, KritzelImageTool, KritzelWorkspace, InMemorySyncProvider, IndexedDBSyncProvider, BroadcastSyncProvider, WebSocketSyncProvider, HocuspocusSyncProvider, KritzelThemeManager, KritzelLocalizationManager, KritzelLicenseManager, ShapeType, KritzelAlignment, lightTheme, darkTheme, EN_LOCALE, DE_LOCALE, FR_LOCALE, DEFAULT_BRUSH_CONFIG, DEFAULT_TEXT_CONFIG, hasRemoteSyncProvider, } from '@kritzel/engine';
|
|
6
|
+
export type { HTMLKritzelEngineElement, KritzelToolbarItem, KritzelBrushToolConfig, KritzelLineToolConfig, KritzelShapeToolConfig, KritzelTextToolConfig, KritzelTextConfig, KritzelShapeConfig, KritzelLineConfig, KritzelPathConfig, KritzelImageConfig, KritzelGroupConfig, KritzelDynamicObjectConfig, KritzelWorkspaceConfig, KritzelFontDefinition, KritzelFontRegistration, KritzelRegisteredFont, KritzelFontMap, KritzelSyncConfig, KritzelTheme, KritzelViewportState, ContextMenuItem, KritzelObjectExportFormat, KritzelObjectExportContext, KritzelExportable, ThemeAwareColor, ThemeName, KritzelLocale, LocaleCode, KritzelTerms, KritzelTermKey, KritzelTermVars, KritzelLoginConfig, KritzelLoginProvider, LoginEvent, EditorIsReadyEvent, ActiveWorkspaceChangeEvent, ObjectsAddedEvent, ObjectsRemovedEvent, ObjectsUpdatedEvent, ObjectsInViewportChangeEvent, KritzelUndoState, IKritzelIsPublicChangeEvent, KritzelEngineState, KritzelDebugInfo, IKritzelUser, } from '@kritzel/engine';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './components';
|
|
2
|
+
export * from './composables';
|
|
3
|
+
export * from './plugin';
|
|
4
|
+
export * from './vue-dynamic-object-renderer';
|
|
5
|
+
export { KritzelBaseObject, KritzelText, KritzelPath, KritzelImage, KritzelLine, KritzelShape, KritzelGroup, KritzelDynamicObject, KritzelDynamicObjectRendererRegistry, KritzelFontRegistry, KritzelBaseTool, KritzelBrushTool, KritzelTextTool, KritzelLineTool, KritzelShapeTool, KritzelEraserTool, KritzelSelectionTool, KritzelImageTool, KritzelWorkspace, InMemorySyncProvider, IndexedDBSyncProvider, BroadcastSyncProvider, WebSocketSyncProvider, HocuspocusSyncProvider, KritzelThemeManager, KritzelLocalizationManager, KritzelLicenseManager, ShapeType, KritzelAlignment, lightTheme, darkTheme, EN_LOCALE, DE_LOCALE, FR_LOCALE, DEFAULT_BRUSH_CONFIG, DEFAULT_TEXT_CONFIG, hasRemoteSyncProvider, } from '@kritzel/engine';
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ComponentLibrary: any;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface InputProps<T> {
|
|
2
|
+
modelValue?: T;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Create a callback to define a Vue component wrapper around a Web Component.
|
|
6
|
+
*
|
|
7
|
+
* @prop name - The component tag name (i.e. `ion-button`)
|
|
8
|
+
* @prop componentProps - An array of properties on the
|
|
9
|
+
* component. These usually match up with the @Prop definitions
|
|
10
|
+
* in each component's TSX file.
|
|
11
|
+
* @prop customElement - An option custom element instance to pass
|
|
12
|
+
* to customElements.define. Only set if `includeImportCustomElements: true` in your config.
|
|
13
|
+
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
|
14
|
+
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes (i.e. ionChange)
|
|
15
|
+
*/
|
|
16
|
+
export declare const defineContainer: <Props, VModelType = string | number | boolean>(name: string, defineCustomElement: any, componentProps?: string[], modelProp?: string, modelUpdateEvent?: string) => import("vue").DefineSetupFnComponent<Props & InputProps<VModelType>, {}, {}, Props & InputProps<VModelType> & {}, import("vue").PublicProps>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// It's easier and safer for Volar to disable typechecking and let the return type inference do its job.
|
|
3
|
+
import { defineComponent, getCurrentInstance, h, inject, ref, withDirectives } from 'vue';
|
|
4
|
+
const UPDATE_VALUE_EVENT = 'update:modelValue';
|
|
5
|
+
const MODEL_VALUE = 'modelValue';
|
|
6
|
+
const ROUTER_LINK_VALUE = 'routerLink';
|
|
7
|
+
const NAV_MANAGER = 'navManager';
|
|
8
|
+
const ROUTER_PROP_PREFIX = 'router';
|
|
9
|
+
const ARIA_PROP_PREFIX = 'aria';
|
|
10
|
+
/**
|
|
11
|
+
* Starting in Vue 3.1.0, all properties are
|
|
12
|
+
* added as keys to the props object, even if
|
|
13
|
+
* they are not being used. In order to correctly
|
|
14
|
+
* account for both value props and v-model props,
|
|
15
|
+
* we need to check if the key exists for Vue <3.1.0
|
|
16
|
+
* and then check if it is not undefined for Vue >= 3.1.0.
|
|
17
|
+
* See https://github.com/vuejs/vue-next/issues/3889
|
|
18
|
+
*/
|
|
19
|
+
const EMPTY_PROP = Symbol();
|
|
20
|
+
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };
|
|
21
|
+
const getComponentClasses = (classes) => {
|
|
22
|
+
return (classes === null || classes === void 0 ? void 0 : classes.split(' ')) || [];
|
|
23
|
+
};
|
|
24
|
+
const getElementClasses = (ref, componentClasses, defaultClasses = []) => {
|
|
25
|
+
var _a;
|
|
26
|
+
return [...Array.from(((_a = ref.value) === null || _a === void 0 ? void 0 : _a.classList) || []), ...defaultClasses].filter((c, i, self) => !componentClasses.has(c) && self.indexOf(c) === i);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Create a callback to define a Vue component wrapper around a Web Component.
|
|
30
|
+
*
|
|
31
|
+
* @prop name - The component tag name (i.e. `ion-button`)
|
|
32
|
+
* @prop componentProps - An array of properties on the
|
|
33
|
+
* component. These usually match up with the @Prop definitions
|
|
34
|
+
* in each component's TSX file.
|
|
35
|
+
* @prop customElement - An option custom element instance to pass
|
|
36
|
+
* to customElements.define. Only set if `includeImportCustomElements: true` in your config.
|
|
37
|
+
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
|
38
|
+
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes (i.e. ionChange)
|
|
39
|
+
*/
|
|
40
|
+
export const defineContainer = (name, defineCustomElement, componentProps = [], modelProp, modelUpdateEvent) => {
|
|
41
|
+
/**
|
|
42
|
+
* Create a Vue component wrapper around a Web Component.
|
|
43
|
+
* Note: The `props` here are not all properties on a component.
|
|
44
|
+
* They refer to whatever properties are set on an instance of a component.
|
|
45
|
+
*/
|
|
46
|
+
if (defineCustomElement !== undefined) {
|
|
47
|
+
defineCustomElement();
|
|
48
|
+
}
|
|
49
|
+
const Container = defineComponent((props, { attrs, slots, emit }) => {
|
|
50
|
+
var _a;
|
|
51
|
+
let modelPropValue = props[modelProp];
|
|
52
|
+
const containerRef = ref();
|
|
53
|
+
const classes = new Set(getComponentClasses(attrs.class));
|
|
54
|
+
/**
|
|
55
|
+
* This directive is responsible for updating any reactive
|
|
56
|
+
* reference associated with v-model on the component.
|
|
57
|
+
* This code must be run inside of the "created" callback.
|
|
58
|
+
* Since the following listener callbacks as well as any potential
|
|
59
|
+
* event callback defined in the developer's app are set on
|
|
60
|
+
* the same element, we need to make sure the following callbacks
|
|
61
|
+
* are set first so they fire first. If the developer's callback fires first
|
|
62
|
+
* then the reactive reference will not have been updated yet.
|
|
63
|
+
*/
|
|
64
|
+
const vModelDirective = {
|
|
65
|
+
created: (el) => {
|
|
66
|
+
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
|
|
67
|
+
eventsNames.forEach((eventName) => {
|
|
68
|
+
el.addEventListener(eventName.toLowerCase(), (e) => {
|
|
69
|
+
/**
|
|
70
|
+
* Only update the v-model binding if the event's target is the element we are
|
|
71
|
+
* listening on. For example, Component A could emit ionChange, but it could also
|
|
72
|
+
* have a descendant Component B that also emits ionChange. We only want to update
|
|
73
|
+
* the v-model for Component A when ionChange originates from that element and not
|
|
74
|
+
* when ionChange bubbles up from Component B.
|
|
75
|
+
*/
|
|
76
|
+
if (e.target.tagName === el.tagName) {
|
|
77
|
+
modelPropValue = (e === null || e === void 0 ? void 0 : e.target)[modelProp];
|
|
78
|
+
emit(UPDATE_VALUE_EVENT, modelPropValue);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const currentInstance = getCurrentInstance();
|
|
85
|
+
const hasRouter = (_a = currentInstance === null || currentInstance === void 0 ? void 0 : currentInstance.appContext) === null || _a === void 0 ? void 0 : _a.provides[NAV_MANAGER];
|
|
86
|
+
const navManager = hasRouter ? inject(NAV_MANAGER) : undefined;
|
|
87
|
+
const handleRouterLink = (ev) => {
|
|
88
|
+
const { routerLink } = props;
|
|
89
|
+
if (routerLink === EMPTY_PROP)
|
|
90
|
+
return;
|
|
91
|
+
if (navManager !== undefined) {
|
|
92
|
+
/**
|
|
93
|
+
* This prevents the browser from
|
|
94
|
+
* performing a page reload when pressing
|
|
95
|
+
* an Ionic component with routerLink.
|
|
96
|
+
* The page reload interferes with routing
|
|
97
|
+
* and causes ion-back-button to disappear
|
|
98
|
+
* since the local history is wiped on reload.
|
|
99
|
+
*/
|
|
100
|
+
ev.preventDefault();
|
|
101
|
+
let navigationPayload = { event: ev };
|
|
102
|
+
for (const key in props) {
|
|
103
|
+
const value = props[key];
|
|
104
|
+
if (props.hasOwnProperty(key) && key.startsWith(ROUTER_PROP_PREFIX) && value !== EMPTY_PROP) {
|
|
105
|
+
navigationPayload[key] = value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
navManager.navigate(navigationPayload);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return () => {
|
|
115
|
+
modelPropValue = props[modelProp];
|
|
116
|
+
getComponentClasses(attrs.class).forEach((value) => {
|
|
117
|
+
classes.add(value);
|
|
118
|
+
});
|
|
119
|
+
const oldClick = props.onClick;
|
|
120
|
+
const handleClick = (ev) => {
|
|
121
|
+
if (oldClick !== undefined) {
|
|
122
|
+
oldClick(ev);
|
|
123
|
+
}
|
|
124
|
+
if (!ev.defaultPrevented) {
|
|
125
|
+
handleRouterLink(ev);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
let propsToAdd = {
|
|
129
|
+
ref: containerRef,
|
|
130
|
+
class: getElementClasses(containerRef, classes),
|
|
131
|
+
onClick: handleClick,
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* We can use Object.entries here
|
|
135
|
+
* to avoid the hasOwnProperty check,
|
|
136
|
+
* but that would require 2 iterations
|
|
137
|
+
* where as this only requires 1.
|
|
138
|
+
*/
|
|
139
|
+
for (const key in props) {
|
|
140
|
+
const value = props[key];
|
|
141
|
+
if ((props.hasOwnProperty(key) && value !== EMPTY_PROP) || key.startsWith(ARIA_PROP_PREFIX)) {
|
|
142
|
+
propsToAdd[key] = value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (modelProp) {
|
|
146
|
+
/**
|
|
147
|
+
* If form value property was set using v-model
|
|
148
|
+
* then we should use that value.
|
|
149
|
+
* Otherwise, check to see if form value property
|
|
150
|
+
* was set as a static value (i.e. no v-model).
|
|
151
|
+
*/
|
|
152
|
+
if (props[MODEL_VALUE] !== EMPTY_PROP) {
|
|
153
|
+
propsToAdd = Object.assign(Object.assign({}, propsToAdd), { [modelProp]: props[MODEL_VALUE] });
|
|
154
|
+
}
|
|
155
|
+
else if (modelPropValue !== EMPTY_PROP) {
|
|
156
|
+
propsToAdd = Object.assign(Object.assign({}, propsToAdd), { [modelProp]: modelPropValue });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// If router link is defined, add href to props
|
|
160
|
+
// in order to properly render an anchor tag inside
|
|
161
|
+
// of components that should become activatable and
|
|
162
|
+
// focusable with router link.
|
|
163
|
+
if (props[ROUTER_LINK_VALUE] !== EMPTY_PROP) {
|
|
164
|
+
propsToAdd = Object.assign(Object.assign({}, propsToAdd), { href: props[ROUTER_LINK_VALUE] });
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* vModelDirective is only needed on components that support v-model.
|
|
168
|
+
* As a result, we conditionally call withDirectives with v-model components.
|
|
169
|
+
*/
|
|
170
|
+
const node = h(name, propsToAdd, slots.default && slots.default());
|
|
171
|
+
return modelProp === undefined ? node : withDirectives(node, [[vModelDirective]]);
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
if (typeof Container !== 'function') {
|
|
175
|
+
Container.name = name;
|
|
176
|
+
Container.props = {
|
|
177
|
+
[ROUTER_LINK_VALUE]: DEFAULT_EMPTY_PROP,
|
|
178
|
+
};
|
|
179
|
+
componentProps.forEach((componentProp) => {
|
|
180
|
+
Container.props[componentProp] = DEFAULT_EMPTY_PROP;
|
|
181
|
+
});
|
|
182
|
+
if (modelProp) {
|
|
183
|
+
Container.props[MODEL_VALUE] = DEFAULT_EMPTY_PROP;
|
|
184
|
+
Container.emits = [UPDATE_VALUE_EVENT];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return Container;
|
|
188
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type DefineComponent } from 'vue';
|
|
2
|
+
import { type KritzelDynamicObjectRenderContext, type KritzelDynamicObjectRenderer } from '@kritzel/engine';
|
|
3
|
+
export interface VueDynamicObjectRendererOptions<TState = unknown> {
|
|
4
|
+
tagName: string;
|
|
5
|
+
component: DefineComponent<any, any, any, any, any, any, any, any>;
|
|
6
|
+
createInitialState?: () => TState;
|
|
7
|
+
getInitialState?: (data: unknown, context: KritzelDynamicObjectRenderContext) => TState;
|
|
8
|
+
mapProps?: (state: TState | undefined, context: KritzelDynamicObjectRenderContext) => Record<string, unknown>;
|
|
9
|
+
normalizeState?: (state: unknown, context: KritzelDynamicObjectRenderContext) => TState;
|
|
10
|
+
onStateChangeProp?: string | null;
|
|
11
|
+
styles?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function createVueDynamicObjectRenderer<TState = unknown>(options: VueDynamicObjectRendererOptions<TState>): KritzelDynamicObjectRenderer;
|
|
14
|
+
export declare function registerVueDynamicObjectRenderer<TState = unknown>(rendererKey: string, options: VueDynamicObjectRendererOptions<TState>): () => void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineCustomElement } from 'vue';
|
|
2
|
+
import { KritzelDynamicObjectRendererRegistry, } from '@kritzel/engine';
|
|
3
|
+
function setHostProps(host, props) {
|
|
4
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
5
|
+
host[key] = value;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export function createVueDynamicObjectRenderer(options) {
|
|
9
|
+
const mountedElements = new Map();
|
|
10
|
+
const ensureCustomElementDefined = () => {
|
|
11
|
+
if (!customElements.get(options.tagName)) {
|
|
12
|
+
customElements.define(options.tagName, defineCustomElement(options.component));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
onMount: (context) => {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
if (!context.container) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
ensureCustomElementDefined();
|
|
22
|
+
const initialState = options.getInitialState
|
|
23
|
+
? options.getInitialState(context.data, context)
|
|
24
|
+
: (_a = context.data) !== null && _a !== void 0 ? _a : (_b = options.createInitialState) === null || _b === void 0 ? void 0 : _b.call(options);
|
|
25
|
+
const host = document.createElement(options.tagName);
|
|
26
|
+
const mounted = {
|
|
27
|
+
host,
|
|
28
|
+
latestState: initialState,
|
|
29
|
+
};
|
|
30
|
+
const baseProps = options.mapProps
|
|
31
|
+
? options.mapProps(initialState, context)
|
|
32
|
+
: (initialState !== undefined ? { initialState } : {});
|
|
33
|
+
const onStateChangeProp = options.onStateChangeProp === undefined ? 'onStateChange' : options.onStateChangeProp;
|
|
34
|
+
if (onStateChangeProp !== null) {
|
|
35
|
+
const existing = baseProps[onStateChangeProp];
|
|
36
|
+
baseProps[onStateChangeProp] = (nextState) => {
|
|
37
|
+
mounted.latestState = options.normalizeState
|
|
38
|
+
? options.normalizeState(nextState, context)
|
|
39
|
+
: nextState;
|
|
40
|
+
if (typeof existing === 'function') {
|
|
41
|
+
existing(nextState);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
context.container.innerHTML = '';
|
|
46
|
+
if (options.styles) {
|
|
47
|
+
const styleElement = document.createElement('style');
|
|
48
|
+
styleElement.textContent = options.styles;
|
|
49
|
+
context.container.appendChild(styleElement);
|
|
50
|
+
}
|
|
51
|
+
setHostProps(host, baseProps);
|
|
52
|
+
context.container.appendChild(host);
|
|
53
|
+
mountedElements.set(context.object.id, mounted);
|
|
54
|
+
},
|
|
55
|
+
onUnmount: (context) => {
|
|
56
|
+
const mounted = mountedElements.get(context.object.id);
|
|
57
|
+
if (!mounted) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
mounted.host.remove();
|
|
61
|
+
mountedElements.delete(context.object.id);
|
|
62
|
+
if (context.container) {
|
|
63
|
+
context.container.innerHTML = '';
|
|
64
|
+
}
|
|
65
|
+
return mounted.latestState;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function registerVueDynamicObjectRenderer(rendererKey, options) {
|
|
70
|
+
const renderer = createVueDynamicObjectRenderer(options);
|
|
71
|
+
KritzelDynamicObjectRendererRegistry.register(rendererKey, renderer);
|
|
72
|
+
return () => {
|
|
73
|
+
KritzelDynamicObjectRendererRegistry.unregister(rendererKey);
|
|
74
|
+
};
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kritzel/vue-engine",
|
|
3
|
+
"version": "0.4.31",
|
|
4
|
+
"homepage": "https://kritzel.io/",
|
|
5
|
+
"description": "Kritzel engine wrapper for Vue.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"infinite canvas",
|
|
8
|
+
"collaboration",
|
|
9
|
+
"whiteboard",
|
|
10
|
+
"drawing",
|
|
11
|
+
"vue"
|
|
12
|
+
],
|
|
13
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"directories": {
|
|
17
|
+
"lib": "lib",
|
|
18
|
+
"test": "__tests__"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "echo \"Error: run tests from root\" && exit 1",
|
|
25
|
+
"build": "npm run tsc",
|
|
26
|
+
"tsc": "tsc -p . --outDir ./dist",
|
|
27
|
+
"clean": "rimraf dist node_modules",
|
|
28
|
+
"bump": "npm version patch",
|
|
29
|
+
"release": "npm publish"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/kasual1/kritzel/issues"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@stencil/vue-output-target": "0.11.8"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@kritzel/engine": "^0.4.31"
|
|
42
|
+
}
|
|
43
|
+
}
|