@awc-ui/vue 1.0.0-beta.9 → 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/README.md +60 -17
- package/dist/components.d.ts +81 -81
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +19 -14
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +546 -467
- package/dist/index.mjs.map +4 -4
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +178 -0
- package/dist/runtime.js.map +1 -0
- package/lib/components.ts +45 -27
- package/lib/index.ts +4 -1
- package/lib/runtime.ts +220 -0
- package/package.json +5 -4
package/lib/runtime.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Hand-owned Vue adapter. Generated proxies import this through withVueRuntime.
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
// It's easier and safer for Volar to disable typechecking and let the return type inference do its job.
|
|
4
|
+
import { defineComponent, getCurrentInstance, h, inject, ref, Ref } from 'vue';
|
|
5
|
+
|
|
6
|
+
export interface InputProps<T> {
|
|
7
|
+
modelValue?: T;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const UPDATE_VALUE_EVENT = 'update:modelValue';
|
|
11
|
+
const MODEL_VALUE = 'modelValue';
|
|
12
|
+
const ROUTER_LINK_VALUE = 'routerLink';
|
|
13
|
+
const NAV_MANAGER = 'navManager';
|
|
14
|
+
const ROUTER_PROP_PREFIX = 'router';
|
|
15
|
+
const ARIA_PROP_PREFIX = 'aria';
|
|
16
|
+
/**
|
|
17
|
+
* Starting in Vue 3.1.0, all properties are
|
|
18
|
+
* added as keys to the props object, even if
|
|
19
|
+
* they are not being used. In order to correctly
|
|
20
|
+
* account for both value props and v-model props,
|
|
21
|
+
* we need to check if the key exists for Vue <3.1.0
|
|
22
|
+
* and then check if it is not undefined for Vue >= 3.1.0.
|
|
23
|
+
* See https://github.com/vuejs/vue-next/issues/3889
|
|
24
|
+
*/
|
|
25
|
+
const EMPTY_PROP = Symbol();
|
|
26
|
+
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };
|
|
27
|
+
|
|
28
|
+
interface NavManager<T = any> {
|
|
29
|
+
navigate: (options: T) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const getComponentClasses = (classes: unknown) => {
|
|
33
|
+
return (classes as string)?.split(' ') || [];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getElementClasses = (
|
|
37
|
+
ref: Ref<HTMLElement | undefined>,
|
|
38
|
+
componentClasses: Set<string>,
|
|
39
|
+
defaultClasses: string[] = []
|
|
40
|
+
) => {
|
|
41
|
+
return [...Array.from(ref.value?.classList || []), ...defaultClasses].filter(
|
|
42
|
+
(c: string, i, self) => !componentClasses.has(c) && self.indexOf(c) === i
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a callback to define a Vue component wrapper around a Web Component.
|
|
48
|
+
*
|
|
49
|
+
* @prop name - The component tag name (i.e. `ion-button`)
|
|
50
|
+
* @prop componentProps - An array of properties on the
|
|
51
|
+
* component. These usually match up with the @Prop definitions
|
|
52
|
+
* in each component's TSX file.
|
|
53
|
+
* @prop customElement - An option custom element instance to pass
|
|
54
|
+
* to customElements.define. Only set if `includeImportCustomElements: true` in your config.
|
|
55
|
+
* @prop modelProp - The prop that v-model binds to (i.e. value)
|
|
56
|
+
* @prop modelUpdateEvent - The event that is fired from your Web Component when the value changes (i.e. ionChange)
|
|
57
|
+
*/
|
|
58
|
+
export const defineContainer = <Props, VModelType = string | number | boolean>(
|
|
59
|
+
name: string,
|
|
60
|
+
defineCustomElement: any,
|
|
61
|
+
componentProps: string[] = [],
|
|
62
|
+
modelProp?: string,
|
|
63
|
+
modelUpdateEvent?: string
|
|
64
|
+
) => {
|
|
65
|
+
/**
|
|
66
|
+
* Create a Vue component wrapper around a Web Component.
|
|
67
|
+
* Note: The `props` here are not all properties on a component.
|
|
68
|
+
* They refer to whatever properties are set on an instance of a component.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
if (defineCustomElement !== undefined) {
|
|
72
|
+
defineCustomElement();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Stencil includes both props and events in this array. AWC custom events
|
|
76
|
+
// consistently use the mdXxx prefix; ordinary component props do not.
|
|
77
|
+
const eventNames = componentProps.filter((name) => /^md[A-Z]/.test(name));
|
|
78
|
+
const propertyNames = componentProps.filter((name) => !eventNames.includes(name));
|
|
79
|
+
|
|
80
|
+
const Container = defineComponent<Props & InputProps<VModelType>>((props, { attrs, slots, emit }) => {
|
|
81
|
+
let modelPropValue = props[modelProp];
|
|
82
|
+
const containerRef = ref<HTMLElement>();
|
|
83
|
+
const classes = new Set(getComponentClasses(attrs.class));
|
|
84
|
+
|
|
85
|
+
const currentInstance = getCurrentInstance();
|
|
86
|
+
const hasRouter = currentInstance?.appContext?.provides[NAV_MANAGER];
|
|
87
|
+
const navManager: NavManager | undefined = hasRouter ? inject(NAV_MANAGER) : undefined;
|
|
88
|
+
const handleRouterLink = (ev: Event) => {
|
|
89
|
+
const { routerLink } = props;
|
|
90
|
+
if (routerLink === EMPTY_PROP) return;
|
|
91
|
+
|
|
92
|
+
if (navManager !== undefined) {
|
|
93
|
+
/**
|
|
94
|
+
* This prevents the browser from
|
|
95
|
+
* performing a page reload when pressing
|
|
96
|
+
* an Ionic component with routerLink.
|
|
97
|
+
* The page reload interferes with routing
|
|
98
|
+
* and causes ion-back-button to disappear
|
|
99
|
+
* since the local history is wiped on reload.
|
|
100
|
+
*/
|
|
101
|
+
ev.preventDefault();
|
|
102
|
+
|
|
103
|
+
let navigationPayload: any = { event: ev };
|
|
104
|
+
for (const key in props) {
|
|
105
|
+
const value = props[key];
|
|
106
|
+
if (props.hasOwnProperty(key) && key.startsWith(ROUTER_PROP_PREFIX) && value !== EMPTY_PROP) {
|
|
107
|
+
navigationPayload[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
navManager.navigate(navigationPayload);
|
|
112
|
+
} else {
|
|
113
|
+
console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
modelPropValue = props[modelProp];
|
|
119
|
+
|
|
120
|
+
getComponentClasses(attrs.class).forEach((value) => {
|
|
121
|
+
classes.add(value);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const oldClick = props.onClick;
|
|
125
|
+
const handleClick = (ev: Event) => {
|
|
126
|
+
if (oldClick !== undefined) {
|
|
127
|
+
oldClick(ev);
|
|
128
|
+
}
|
|
129
|
+
if (!ev.defaultPrevented) {
|
|
130
|
+
handleRouterLink(ev);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let propsToAdd: any = {
|
|
135
|
+
ref: containerRef,
|
|
136
|
+
class: getElementClasses(containerRef, classes),
|
|
137
|
+
onClick: handleClick,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* We can use Object.entries here
|
|
142
|
+
* to avoid the hasOwnProperty check,
|
|
143
|
+
* but that would require 2 iterations
|
|
144
|
+
* where as this only requires 1.
|
|
145
|
+
*/
|
|
146
|
+
for (const key in props) {
|
|
147
|
+
const value = props[key];
|
|
148
|
+
if ((props.hasOwnProperty(key) && value !== EMPTY_PROP) || key.startsWith(ARIA_PROP_PREFIX)) {
|
|
149
|
+
propsToAdd[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (modelProp) {
|
|
154
|
+
/**
|
|
155
|
+
* If form value property was set using v-model
|
|
156
|
+
* then we should use that value.
|
|
157
|
+
* Otherwise, check to see if form value property
|
|
158
|
+
* was set as a static value (i.e. no v-model).
|
|
159
|
+
*/
|
|
160
|
+
if (props[MODEL_VALUE] !== EMPTY_PROP) {
|
|
161
|
+
propsToAdd = {
|
|
162
|
+
...propsToAdd,
|
|
163
|
+
...(name === 'md-radio'
|
|
164
|
+
? { checked: props[MODEL_VALUE] != null && Object.is(props[MODEL_VALUE], props.value) }
|
|
165
|
+
: { [modelProp]: props[MODEL_VALUE] }),
|
|
166
|
+
};
|
|
167
|
+
} else if (modelPropValue !== EMPTY_PROP) {
|
|
168
|
+
propsToAdd = {
|
|
169
|
+
...propsToAdd,
|
|
170
|
+
[modelProp]: modelPropValue,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// If router link is defined, add href to props
|
|
176
|
+
// in order to properly render an anchor tag inside
|
|
177
|
+
// of components that should become activatable and
|
|
178
|
+
// focusable with router link.
|
|
179
|
+
if (props[ROUTER_LINK_VALUE] !== EMPTY_PROP) {
|
|
180
|
+
propsToAdd = {
|
|
181
|
+
...propsToAdd,
|
|
182
|
+
href: props[ROUTER_LINK_VALUE],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const eventName of eventNames) {
|
|
187
|
+
// Vue's onMdInput DOM fallback listens for md-input. The on: prefix
|
|
188
|
+
// preserves the actual case-sensitive CustomEvent name instead.
|
|
189
|
+
propsToAdd[`on:${eventName}`] = (event: Event) => {
|
|
190
|
+
if (modelProp && eventName === modelUpdateEvent && event.target === event.currentTarget && (name !== 'md-radio' || (event.target as any).checked)) {
|
|
191
|
+
emit(UPDATE_VALUE_EVENT, (event.target as any)[modelProp]);
|
|
192
|
+
}
|
|
193
|
+
emit(eventName, event);
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return h(name, propsToAdd, slots.default && slots.default());
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (typeof Container !== 'function') {
|
|
201
|
+
Container.name = name;
|
|
202
|
+
|
|
203
|
+
Container.props = {
|
|
204
|
+
[ROUTER_LINK_VALUE]: DEFAULT_EMPTY_PROP,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
propertyNames.forEach((componentProp) => {
|
|
208
|
+
Container.props[componentProp] = DEFAULT_EMPTY_PROP;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
Container.emits = [...eventNames, ...(modelProp ? [UPDATE_VALUE_EVENT] : [])];
|
|
212
|
+
|
|
213
|
+
if (modelProp) {
|
|
214
|
+
Container.props[MODEL_VALUE] = DEFAULT_EMPTY_PROP;
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return Container;
|
|
220
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awc-ui/vue",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Typed Vue 3 components for accessible Material Design 3 Web Components, with automatic registration and Nuxt SSR support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vue",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
],
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"vue": ">=3",
|
|
58
|
-
"@awc-ui/core": "1.0.
|
|
58
|
+
"@awc-ui/core": "1.0.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"esbuild": "^0.25.12",
|
|
62
62
|
"typescript": "^5.4.0",
|
|
63
|
-
"@awc-ui/core": "1.0.
|
|
63
|
+
"@awc-ui/core": "1.0.1"
|
|
64
64
|
},
|
|
65
65
|
"license": "MIT",
|
|
66
66
|
"author": "AWC UI",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"url": "https://github.com/awc-ui/core/issues"
|
|
75
75
|
},
|
|
76
76
|
"scripts": {
|
|
77
|
-
"build": "rm -rf dist && tsc -p tsconfig.json && esbuild lib/index.ts --bundle --format=esm --target=es2020 --sourcemap --outfile=dist/index.mjs --external:vue --external:@awc-ui/core --external:@awc-ui/core/*"
|
|
77
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && esbuild lib/index.ts --bundle --format=esm --target=es2020 --sourcemap --outfile=dist/index.mjs --external:vue --external:@awc-ui/core --external:@awc-ui/core/*",
|
|
78
|
+
"test:integration": "node tests/generation.mjs && node tests/forms.mjs"
|
|
78
79
|
}
|
|
79
80
|
}
|