@shineuix/vue 0.0.0-stage → 1.0.1
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 +21 -0
- package/README.md +48 -2
- package/dist/components.d.ts +148 -0
- package/dist/components.js +2569 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/runtime.d.ts +30 -0
- package/dist/runtime.js +204 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +1 -0
- package/package.json +59 -3
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Vendored from @stencil/vue-output-target 0.14.2, src/runtime.ts (defineContainer and its helpers).
|
|
3
|
+
* The MIT License (MIT). Copyright (c) 2019-present Drifty Co.
|
|
4
|
+
* Changed here: the wrapper renders nothing on the server and while the page hydrates
|
|
5
|
+
* (useClientRender), and it listens for the element's events once the element exists.
|
|
6
|
+
*/
|
|
7
|
+
import type { DefineSetupFnComponent, PublicProps } from 'vue';
|
|
8
|
+
import type { InputProps } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a Vue component wrapper around a Web Component.
|
|
11
|
+
*
|
|
12
|
+
* @prop name - The component tag name (i.e. `ion-button`)
|
|
13
|
+
* @prop componentProps - An array of properties on the component. These usually match up with the
|
|
14
|
+
* @Prop definitions in each component's TSX file.
|
|
15
|
+
* @prop emitProps - An array of for event listener on the Component. These usually match up with
|
|
16
|
+
* the @Event definitions in each component's TSX file.
|
|
17
|
+
* @prop customElement - An option custom element instance to pass to customElements.define. Only
|
|
18
|
+
* set if `includeImportCustomElements: true` in your config.
|
|
19
|
+
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
|
20
|
+
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes
|
|
21
|
+
* (i.e. ionChange), or a list of them.
|
|
22
|
+
* @prop modelUpdateEventAttribute - Property to read value from when the value changes.
|
|
23
|
+
*
|
|
24
|
+
* The element's @Event listeners, which `@sui-change` and the rest rely on, are attached once the
|
|
25
|
+
* element exists: on mount in a client render, on the update that first draws it after hydration.
|
|
26
|
+
* The v-model listeners are added in the element's `created` hook, so they fire before any
|
|
27
|
+
* listener the app sets, and a model event counts only when it comes from this element, not from a
|
|
28
|
+
* descendant of the same tag that bubbled up to it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const defineContainer: <Props, VModelType = string | number | boolean>(name: string, defineCustomElement?: () => void, componentProps?: string[], emitProps?: string[], modelProp?: string, modelUpdateEvent?: string, modelUpdateEventAttribute?: string, transformTagFn?: (tagName: string) => string) => DefineSetupFnComponent<Props & InputProps<VModelType>, {}, {}, Props & InputProps<VModelType> & {}, PublicProps>;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Vendored from @stencil/vue-output-target 0.14.2, src/runtime.ts (defineContainer and its helpers).
|
|
3
|
+
* The MIT License (MIT). Copyright (c) 2019-present Drifty Co.
|
|
4
|
+
* Changed here: the wrapper renders nothing on the server and while the page hydrates
|
|
5
|
+
* (useClientRender), and it listens for the element's events once the element exists.
|
|
6
|
+
*/
|
|
7
|
+
import { defineComponent, getCurrentInstance, h, inject, onMounted, onUpdated, ref, withDirectives } from 'vue';
|
|
8
|
+
const UPDATE_VALUE_EVENT = 'update:modelValue';
|
|
9
|
+
const MODEL_VALUE = 'modelValue';
|
|
10
|
+
const ROUTER_LINK_VALUE = 'routerLink';
|
|
11
|
+
const NAV_MANAGER = 'navManager';
|
|
12
|
+
const ROUTER_PROP_PREFIX = 'router';
|
|
13
|
+
const ARIA_PROP_PREFIX = 'aria';
|
|
14
|
+
/**
|
|
15
|
+
* Starting in Vue 3.1.0, all properties are added as keys to the props object, even if they are not
|
|
16
|
+
* being used. To tell a v-model prop from an unset one, an unset prop holds this marker.
|
|
17
|
+
*/
|
|
18
|
+
const EMPTY_PROP = Symbol();
|
|
19
|
+
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };
|
|
20
|
+
/** Split a class attribute the way `DOMTokenList` does, on runs of ASCII whitespace. */
|
|
21
|
+
const getComponentClasses = (classes) => ((typeof classes === 'string' && classes.split(/[ \t\n\f\r]+/)) || []).filter((className) => className.length > 0);
|
|
22
|
+
/**
|
|
23
|
+
* Sync the classes Vue supplied onto the host element, and return the ones it did not supply
|
|
24
|
+
* (Stencil's `hydrated` and `sc-*` scope classes, plus anything added imperatively) so Vue's class
|
|
25
|
+
* patch does not wipe them. Classes Vue stopped supplying come off the element first, or a stale
|
|
26
|
+
* one would be reported back as a class Vue never supplied.
|
|
27
|
+
*/
|
|
28
|
+
const syncElementClasses = (elementRef, previousVueClasses, vueClasses, defaultClasses = []) => {
|
|
29
|
+
const nextClasses = new Set(getComponentClasses(vueClasses));
|
|
30
|
+
if (elementRef.value) {
|
|
31
|
+
const element = elementRef.value;
|
|
32
|
+
previousVueClasses.forEach((c) => {
|
|
33
|
+
if (!nextClasses.has(c))
|
|
34
|
+
element.classList.remove(c);
|
|
35
|
+
});
|
|
36
|
+
nextClasses.forEach((c) => {
|
|
37
|
+
if (!element.classList.contains(c))
|
|
38
|
+
element.classList.add(c);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
previousVueClasses.clear();
|
|
42
|
+
nextClasses.forEach((c) => previousVueClasses.add(c));
|
|
43
|
+
return [...Array.from(elementRef.value?.classList || []), ...defaultClasses].filter((c, i, self) => !nextClasses.has(c) && self.indexOf(c) === i);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* False on the server and while the page hydrates, true in every other render.
|
|
47
|
+
*
|
|
48
|
+
* ShineUI's elements render into their own light DOM. In a server-rendered Vue app (Nuxt) that
|
|
49
|
+
* collides with hydration: the element modules define the tags on import, the host tags the server
|
|
50
|
+
* sent upgrade and draw their inner markup, and Vue then finds nodes it did not render, reports
|
|
51
|
+
* "Hydration completed but contains mismatches", warns of a class mismatch on every element and
|
|
52
|
+
* replaces the content of each slot. So the wrapper renders nothing until this is true, and the
|
|
53
|
+
* element from the render after it mounts. Hydration is told apart from a plain client mount the
|
|
54
|
+
* way Vue's renderer marks it: it sets the component's `vnode.el` to the server node before the
|
|
55
|
+
* component's setup runs, and a client mount sets it only afterward. An app that only renders in
|
|
56
|
+
* the browser therefore sees the element in its first render.
|
|
57
|
+
*/
|
|
58
|
+
const useClientRender = () => {
|
|
59
|
+
const server = typeof window === 'undefined';
|
|
60
|
+
const hydrating = !!getCurrentInstance()?.vnode.el;
|
|
61
|
+
const client = ref(!server && !hydrating);
|
|
62
|
+
if (!client.value && !server)
|
|
63
|
+
onMounted(() => { client.value = true; });
|
|
64
|
+
return client;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Create a Vue component wrapper around a Web Component.
|
|
68
|
+
*
|
|
69
|
+
* @prop name - The component tag name (i.e. `ion-button`)
|
|
70
|
+
* @prop componentProps - An array of properties on the component. These usually match up with the
|
|
71
|
+
* @Prop definitions in each component's TSX file.
|
|
72
|
+
* @prop emitProps - An array of for event listener on the Component. These usually match up with
|
|
73
|
+
* the @Event definitions in each component's TSX file.
|
|
74
|
+
* @prop customElement - An option custom element instance to pass to customElements.define. Only
|
|
75
|
+
* set if `includeImportCustomElements: true` in your config.
|
|
76
|
+
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
|
77
|
+
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes
|
|
78
|
+
* (i.e. ionChange), or a list of them.
|
|
79
|
+
* @prop modelUpdateEventAttribute - Property to read value from when the value changes.
|
|
80
|
+
*
|
|
81
|
+
* The element's @Event listeners, which `@sui-change` and the rest rely on, are attached once the
|
|
82
|
+
* element exists: on mount in a client render, on the update that first draws it after hydration.
|
|
83
|
+
* The v-model listeners are added in the element's `created` hook, so they fire before any
|
|
84
|
+
* listener the app sets, and a model event counts only when it comes from this element, not from a
|
|
85
|
+
* descendant of the same tag that bubbled up to it.
|
|
86
|
+
*/
|
|
87
|
+
export const defineContainer = (name, defineCustomElement, componentProps = [], emitProps = [], modelProp, modelUpdateEvent, modelUpdateEventAttribute, transformTagFn) => {
|
|
88
|
+
if (defineCustomElement !== undefined) {
|
|
89
|
+
defineCustomElement();
|
|
90
|
+
}
|
|
91
|
+
const emits = emitProps;
|
|
92
|
+
const props = [ROUTER_LINK_VALUE, ...componentProps].reduce((acc, prop) => {
|
|
93
|
+
acc[prop] = DEFAULT_EMPTY_PROP;
|
|
94
|
+
return acc;
|
|
95
|
+
}, {});
|
|
96
|
+
if (modelProp) {
|
|
97
|
+
emits.push(UPDATE_VALUE_EVENT);
|
|
98
|
+
props[MODEL_VALUE] = DEFAULT_EMPTY_PROP;
|
|
99
|
+
}
|
|
100
|
+
const Container = defineComponent((props, { attrs, slots, emit }) => {
|
|
101
|
+
let modelPropValue = modelProp ? props[modelProp] : undefined;
|
|
102
|
+
const containerRef = ref();
|
|
103
|
+
const previousVueClasses = new Set();
|
|
104
|
+
const client = useClientRender();
|
|
105
|
+
let listening;
|
|
106
|
+
const listen = () => {
|
|
107
|
+
const element = containerRef.value;
|
|
108
|
+
if (!element || listening === element)
|
|
109
|
+
return;
|
|
110
|
+
listening = element;
|
|
111
|
+
emitProps.forEach((eventName) => {
|
|
112
|
+
element.addEventListener(eventName, (e) => emit(eventName, e));
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
onMounted(listen);
|
|
116
|
+
onUpdated(listen);
|
|
117
|
+
const vModelDirective = {
|
|
118
|
+
created: (el) => {
|
|
119
|
+
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
|
|
120
|
+
eventsNames.forEach((eventName) => {
|
|
121
|
+
el.addEventListener(eventName, (e) => {
|
|
122
|
+
if (e.target.tagName === el.tagName && modelProp) {
|
|
123
|
+
const resolvePath = (object, path) => path.split('.').reduce((value, key) => (value !== undefined ? value[key] : undefined), object);
|
|
124
|
+
const path = modelUpdateEventAttribute ?? `target.${modelProp}`;
|
|
125
|
+
emit(UPDATE_VALUE_EVENT, resolvePath(e, path));
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
const currentInstance = getCurrentInstance();
|
|
132
|
+
const hasRouter = currentInstance?.appContext?.provides[NAV_MANAGER];
|
|
133
|
+
const navManager = hasRouter ? inject(NAV_MANAGER) : undefined;
|
|
134
|
+
const handleRouterLink = (ev) => {
|
|
135
|
+
const { routerLink } = props;
|
|
136
|
+
if (routerLink === EMPTY_PROP)
|
|
137
|
+
return;
|
|
138
|
+
const mouseEv = ev;
|
|
139
|
+
if (mouseEv.metaKey || mouseEv.ctrlKey || mouseEv.shiftKey)
|
|
140
|
+
return;
|
|
141
|
+
if (navManager !== undefined) {
|
|
142
|
+
ev.preventDefault();
|
|
143
|
+
const navigationPayload = { event: ev };
|
|
144
|
+
for (const key in props) {
|
|
145
|
+
const value = props[key];
|
|
146
|
+
if (Object.prototype.hasOwnProperty.call(props, key) && key.startsWith(ROUTER_PROP_PREFIX) && value !== EMPTY_PROP) {
|
|
147
|
+
navigationPayload[key] = value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
navManager.navigate(navigationPayload);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return () => {
|
|
157
|
+
if (!client.value)
|
|
158
|
+
return null;
|
|
159
|
+
modelPropValue = modelProp ? props[modelProp] : undefined;
|
|
160
|
+
const oldClick = props.onClick;
|
|
161
|
+
const handleClick = (ev) => {
|
|
162
|
+
if (oldClick !== undefined)
|
|
163
|
+
oldClick(ev);
|
|
164
|
+
if (!ev.defaultPrevented)
|
|
165
|
+
handleRouterLink(ev);
|
|
166
|
+
};
|
|
167
|
+
const propsToAdd = {
|
|
168
|
+
ref: containerRef,
|
|
169
|
+
class: syncElementClasses(containerRef, previousVueClasses, attrs.class),
|
|
170
|
+
onClick: handleClick,
|
|
171
|
+
};
|
|
172
|
+
for (const key in props) {
|
|
173
|
+
const value = props[key];
|
|
174
|
+
if ((Object.prototype.hasOwnProperty.call(props, key) && value !== EMPTY_PROP) || key.startsWith(ARIA_PROP_PREFIX)) {
|
|
175
|
+
propsToAdd[key] = value;
|
|
176
|
+
}
|
|
177
|
+
const eventHandlerKey = 'on' + key.slice(0, 1).toUpperCase() + key.slice(1);
|
|
178
|
+
const eventHandler = attrs[eventHandlerKey];
|
|
179
|
+
if (containerRef.value && Object.prototype.hasOwnProperty.call(attrs, eventHandlerKey) && 'addEventListener' in containerRef.value) {
|
|
180
|
+
containerRef.value.addEventListener(key, eventHandler);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (modelProp) {
|
|
184
|
+
if (props[MODEL_VALUE] !== EMPTY_PROP) {
|
|
185
|
+
propsToAdd[modelProp] = props[MODEL_VALUE];
|
|
186
|
+
}
|
|
187
|
+
else if (modelPropValue !== EMPTY_PROP) {
|
|
188
|
+
propsToAdd[modelProp] = modelPropValue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (ROUTER_LINK_VALUE in props && props[ROUTER_LINK_VALUE] !== EMPTY_PROP) {
|
|
192
|
+
propsToAdd.href = props[ROUTER_LINK_VALUE];
|
|
193
|
+
}
|
|
194
|
+
const tagName = transformTagFn ? transformTagFn(name) : name;
|
|
195
|
+
const node = h(tagName, propsToAdd, slots.default && slots.default());
|
|
196
|
+
return modelProp === undefined ? node : withDirectives(node, [[vModelDirective]]);
|
|
197
|
+
};
|
|
198
|
+
}, {
|
|
199
|
+
name,
|
|
200
|
+
props: props,
|
|
201
|
+
emits,
|
|
202
|
+
});
|
|
203
|
+
return Container;
|
|
204
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AllowedComponentProps, ComponentCustomProps, DefineSetupFnComponent, VNodeProps } from 'vue';
|
|
2
|
+
export interface InputProps<T> {
|
|
3
|
+
modelValue?: T;
|
|
4
|
+
}
|
|
5
|
+
type WrapperProps<Props, VModelType> = Props & InputProps<VModelType> & VNodeProps & AllowedComponentProps & ComponentCustomProps;
|
|
6
|
+
type Wrapper<Props, VModelType> = DefineSetupFnComponent<WrapperProps<Props, VModelType>, {}, {}, WrapperProps<Props, VModelType>>;
|
|
7
|
+
/**
|
|
8
|
+
* A ShineUI component for Vue. A template ref to one holds the component instance, and the
|
|
9
|
+
* instance's `$el` is the element itself, typed as that element, so its methods and state are
|
|
10
|
+
* checked: `ref.value?.$el.setFocus()`. `El` is the element type (`HTMLSuiDialogElement` for
|
|
11
|
+
* `SuiDialog`); scripts/own-types.mjs writes it into every declaration after the build.
|
|
12
|
+
*/
|
|
13
|
+
export type StencilVueComponent<Props, El extends HTMLElement = HTMLElement, VModelType = string | number | boolean> = new (...args: ConstructorParameters<Wrapper<Props, VModelType>>) => Omit<InstanceType<Wrapper<Props, VModelType>>, '$el'> & {
|
|
14
|
+
readonly $el: El;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shineuix/vue",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Vue 3 bindings for ShineUI web components.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"vue",
|
|
8
|
+
"vue3",
|
|
9
|
+
"web-components",
|
|
10
|
+
"design-system",
|
|
11
|
+
"ui",
|
|
12
|
+
"components",
|
|
13
|
+
"shineui"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "dist/index.js",
|
|
20
|
+
"module": "dist/index.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"dist/"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@shineuix/core": "1.0.1"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"vue": "^3.4.38"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@stencil/vue-output-target": "^0.14.2",
|
|
43
|
+
"typescript": "~5.9.0",
|
|
44
|
+
"vue": "^3.5.0"
|
|
45
|
+
},
|
|
46
|
+
"author": "ShineUI contributors",
|
|
47
|
+
"homepage": "https://shineui.dev",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"email": "info@shineui.dev"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/smallandsimple/shineui.git",
|
|
57
|
+
"directory": "packages/vue"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.json && node scripts/vendor-runtime.mjs && node scripts/own-types.mjs && node scripts/check-types.mjs"
|
|
61
|
+
}
|
|
6
62
|
}
|