@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.
@@ -0,0 +1,2 @@
1
+ export * from './components.js';
2
+ export type { InputProps, StencilVueComponent } from './types.js';
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>;
@@ -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
+ };
@@ -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": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
  }