@ulu/frontend-vue 0.1.1-beta.17 → 0.1.1-beta.19

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.
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <component
3
- class="card"
4
- :is="resolvedElement"
3
+ :is="resolvedElement"
4
+ ref="cardRoot"
5
+ class="card"
5
6
  @mousedown="onMousedown"
6
7
  @mouseup="onMouseup"
7
8
  :class="[
@@ -76,166 +77,179 @@
76
77
  </component>
77
78
  </template>
78
79
 
79
- <script>
80
+ <script setup>
81
+ import { ref, computed, useSlots } from 'vue';
80
82
  import { RouterLink } from "vue-router";
81
83
  import { useModifiers } from "../../composables/useModifiers.js";
82
- const titleLinkValidator = (_, props) => {
83
- const valid = !(props.to || props.href);
84
- if (!valid) {
85
- console.warn("'titleHref' and 'titleTo' can't be used with to or href (nesting links)");
86
- }
87
- return valid;
88
- };
89
- export default {
90
- name: "UluCard",
91
- props: {
92
- /**
93
- * Specify card element, unless to or href are used which will use 'a' or 'router-link'
94
- * - Other than changing to more appropriate element (ie 'li' if in list for example), this can
95
- * be used to set the card to a button to attach your own click handlers to
96
- */
97
- cardElement: {
98
- type: String,
99
- default: "article"
100
- },
101
- /**
102
- * Text for title if not using slot
103
- */
104
- title: String,
105
- /**
106
- * Element to use for title
107
- */
108
- titleElement: {
109
- type: String,
110
- default: "h3"
111
- },
112
- /**
113
- * Title will be router link
114
- */
115
- titleTo: {
116
- type: [String, Object],
117
- validator: titleLinkValidator
118
- },
119
- /**
120
- * Will make title a link to href
121
- */
122
- titleHref: {
123
- type: String,
124
- validator: titleLinkValidator
125
- },
126
- /**
127
- * When using href this will set title link's target attribute
128
- */
129
- titleTarget: String,
130
- /**
131
- * If set the entire card will be router link
132
- * - Do not us in combination with titleTo or titleHref
133
- */
134
- to: [String, Object],
135
- /**
136
- * If set the entire card will be a link to href
137
- * - Do not us in combination with titleTo or titleHref
138
- */
139
- href: {
140
- type: String,
141
-
142
- },
143
- /**
144
- * When using href this will set link's target attribute
145
- */
146
- target: String,
147
- /**
148
- * Classes with class bindings for different elements including ({ title, image })
149
- */
150
- classes: {
151
- type: Object,
152
- default: () => ({})
153
- },
154
- /**
155
- * Whether to proxy clicks of non-interactive elements (making whole card clickable)
156
- */
157
- proxyClick: Boolean,
158
- /**
159
- * Options to be merged for proxy click settings ({ preventSelector, preventSelectionDuration })
160
- */
161
- proxyClickOptions: {
162
- type: Object,
163
- default: () => ({})
164
- },
165
- /**
166
- * Source of image
167
- */
168
- imageSrc: String,
169
- /**
170
- * Alt text for image
171
- */
172
- imageAlt: String,
173
- /**
174
- * If true image will use icon modifier (displays for image adjusts for icon vs full image)
175
- */
176
- imageIcon: Boolean,
177
- /**
178
- * Horizontal card layout
179
- */
180
- horizontal: Boolean,
181
- /**
182
- * Horizontal centered card layout
183
- */
184
- horizontalCenter: Boolean,
185
- /**
186
- * Overlay card layout
187
- */
188
- overlay: Boolean,
189
- /**
190
- * Class modifiers (ie. 'transparent', 'secondary', etc)
191
- * - Can be String or Array of Strings
192
- */
193
- modifiers: [Array, String]
84
+
85
+ const props = defineProps({
86
+ /**
87
+ * Specify card element, unless to or href are used which will use 'a' or 'router-link'
88
+ * - Other than changing to more appropriate element (ie 'li' if in list for example), this can
89
+ * be used to set the card to a button to attach your own click handlers to
90
+ */
91
+ cardElement: {
92
+ type: String,
93
+ default: "article"
194
94
  },
195
- data() {
196
- const { proxyClickOptions, proxyClick, titleHref, titleTo } = this;
197
- return {
198
- proxyClickEnabled: (proxyClick && (titleHref || titleTo)) || null,
199
- resolvedProxyOptions: {
200
- selectorPrevent: "input, select, textarea, button, a, [tabindex='-1']",
201
- mousedownDurationPrevent: 250,
202
- ...proxyClickOptions
203
- },
204
- cursorStyle: null,
205
- proxyStart: null,
206
- shouldProxy: false
207
- }
95
+ /**
96
+ * Text for title if not using slot
97
+ */
98
+ title: String,
99
+ /**
100
+ * Element to use for title
101
+ */
102
+ titleElement: {
103
+ type: String,
104
+ default: "h3"
208
105
  },
209
- setup(props) {
210
- const { resolvedModifiers } = useModifiers({ props, baseClass: "card" });
211
- return { resolvedModifiers };
106
+ /**
107
+ * Title will be router link
108
+ */
109
+ titleTo: [String, Object],
110
+ /**
111
+ * Will make title a link to href
112
+ */
113
+ titleHref: String,
114
+ /**
115
+ * When using href this will set title link's target attribute
116
+ */
117
+ titleTarget: String,
118
+ /**
119
+ * If set the entire card will be router link
120
+ * - Do not us in combination with titleTo or titleHref
121
+ */
122
+ to: [String, Object],
123
+ /**
124
+ * If set the entire card will be a link to href
125
+ * - Do not us in combination with titleTo or titleHref
126
+ */
127
+ href: String,
128
+ /**
129
+ * When using href this will set link's target attribute
130
+ */
131
+ target: String,
132
+ /**
133
+ * Classes with class bindings for different elements including ({ title, image })
134
+ */
135
+ classes: {
136
+ type: Object,
137
+ default: () => ({})
212
138
  },
213
- computed: {
214
- resolvedElement() {
215
- const { cardElement, to, href } = this;
216
- return to ? RouterLink : href ? 'a' : cardElement;
217
- }
139
+ /**
140
+ * Whether to proxy clicks of non-interactive elements (making whole card clickable).
141
+ * This is for accessibility, allowing a non-link card to have a primary action.
142
+ * The proxy action is determined in the following order:
143
+ * 1. If the title has a link (`titleTo` or `titleHref`), it will proxy the click to the title's link.
144
+ * 2. If not, it will look for an element with the `data-ulu-card-proxy-target` attribute within the card's slots and click it.
145
+ * 3. If no proxy target is found, it will emit a `proxy-click` event.
146
+ * Note: This should not be used with the `to` or `href` props.
147
+ */
148
+ proxyClick: Boolean,
149
+ /**
150
+ * Options to be merged for proxy click settings ({ preventSelector, preventSelectionDuration })
151
+ */
152
+ proxyClickOptions: {
153
+ type: Object,
154
+ default: () => ({})
218
155
  },
219
- methods: {
220
- onMousedown({ target, timeStamp }) {
221
- if (!this.proxyClickEnabled) return;
222
- const { resolvedProxyOptions } = this;
223
- const { selectorPrevent } = resolvedProxyOptions;
224
- this.shouldProxy = false;
225
- if (!target.matches(selectorPrevent)) {
226
- this.shouldProxy = true;
227
- this.proxyStart = timeStamp;
228
- }
229
- },
230
- onMouseup({ timeStamp }) {
231
- if (!this.proxyClickEnabled) return;
232
- const { link } = this.$refs;
233
- const { resolvedProxyOptions } = this;
234
- const { mousedownDurationPrevent } = resolvedProxyOptions;
235
- if (this.shouldProxy && timeStamp - this.proxyStart < mousedownDurationPrevent) {
236
- link.click();
156
+ /**
157
+ * Source of image
158
+ */
159
+ imageSrc: String,
160
+ /**
161
+ * Alt text for image
162
+ */
163
+ imageAlt: String,
164
+ /**
165
+ * If true image will use icon modifier (displays for image adjusts for icon vs full image)
166
+ */
167
+ imageIcon: Boolean,
168
+ /**
169
+ * Horizontal card layout
170
+ */
171
+ horizontal: Boolean,
172
+ /**
173
+ * Horizontal centered card layout
174
+ */
175
+ horizontalCenter: Boolean,
176
+ /**
177
+ * Overlay card layout
178
+ */
179
+ overlay: Boolean,
180
+ /**
181
+ * Class modifiers (ie. 'transparent', 'secondary', etc)
182
+ * - Can be String or Array of Strings
183
+ */
184
+ modifiers: [Array, String]
185
+ });
186
+
187
+ const emit = defineEmits(['proxy-click']);
188
+ const $slots = useSlots();
189
+
190
+ // --- Validation warnings
191
+ if (props.proxyClick && (props.to || props.href)) {
192
+ console.warn("UluCard: 'proxyClick' is ignored when 'to' or 'href' are present.");
193
+ }
194
+ if ((props.titleTo || props.titleHref) && (props.to || props.href)) {
195
+ console.warn("UluCard: 'titleTo'/'titleHref' should not be used with 'to'/'href'.");
196
+ }
197
+
198
+ // --- Template refs
199
+ const cardRoot = ref(null);
200
+ const link = ref(null);
201
+
202
+ // --- Composables
203
+ const { resolvedModifiers } = useModifiers({ props, baseClass: "card" });
204
+
205
+ // --- State
206
+ const proxyStart = ref(null);
207
+ const shouldProxy = ref(false);
208
+
209
+ // --- Computed properties
210
+ const isClickable = computed(() => props.proxyClick && !props.to && !props.href);
211
+ const isTitleProxy = computed(() => isClickable.value && (props.titleTo || props.titleHref));
212
+ const isEventProxy = computed(() => isClickable.value && !isTitleProxy.value);
213
+ const proxyClickEnabled = computed(() => isClickable.value || null);
214
+
215
+ const resolvedProxyOptions = computed(() => ({
216
+ selectorPrevent: "input, select, textarea, button, a, [tabindex='-1']",
217
+ mousedownDurationPrevent: 250,
218
+ ...props.proxyClickOptions
219
+ }));
220
+
221
+ const cursorStyle = computed(() => isClickable.value ? 'pointer' : null);
222
+
223
+ const resolvedElement = computed(() => {
224
+ return props.to ? RouterLink : props.href ? 'a' : props.cardElement;
225
+ });
226
+
227
+ // --- Methods
228
+ function onMousedown({ target, timeStamp }) {
229
+ if (!proxyClickEnabled.value) return;
230
+ const { selectorPrevent } = resolvedProxyOptions.value;
231
+ shouldProxy.value = false;
232
+ if (!target.closest(selectorPrevent)) {
233
+ shouldProxy.value = true;
234
+ proxyStart.value = timeStamp;
235
+ }
236
+ }
237
+
238
+ function onMouseup({ timeStamp }) {
239
+ if (!proxyClickEnabled.value || !shouldProxy.value) return;
240
+ const { mousedownDurationPrevent } = resolvedProxyOptions.value;
241
+ if (timeStamp - proxyStart.value < mousedownDurationPrevent) {
242
+ if (isTitleProxy.value) {
243
+ link.value?.click();
244
+ } else if (isEventProxy.value) {
245
+ const proxyTarget = cardRoot.value?.querySelector('[data-ulu-card-proxy-target]');
246
+ if (proxyTarget) {
247
+ proxyTarget.click();
248
+ } else {
249
+ emit('proxy-click');
237
250
  }
238
- },
251
+ }
239
252
  }
240
- };
241
- </script>
253
+ shouldProxy.value = false;
254
+ }
255
+ </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <dl :class="classes.list">
2
+ <dl class="definition-list" :class="[resolvedModifiers, classes.list]">
3
3
  <div
4
4
  v-for="(item, index) in items"
5
5
  :key="index"
@@ -10,6 +10,7 @@
10
10
  {{ item.term }}
11
11
  </slot>
12
12
  </dt>
13
+
13
14
  <dd :class="classes.description">
14
15
  <slot name="description" :item="item" :index="index">
15
16
  {{ item.description }}
@@ -20,7 +21,10 @@
20
21
  </template>
21
22
 
22
23
  <script setup>
23
- defineProps({
24
+ import { computed } from 'vue';
25
+ import { useModifiers } from "../../composables/useModifiers.js";
26
+
27
+ const props = defineProps({
24
28
  /**
25
29
  * Array of term, and description (props in object)
26
30
  * - Can use slots also
@@ -32,6 +36,54 @@
32
36
  classes: {
33
37
  type: Object,
34
38
  default: () => ({})
35
- }
39
+ },
40
+ /**
41
+ * Class modifiers (ie. 'transparent', 'secondary', etc)
42
+ */
43
+ modifiers: [String, Array],
44
+ /**
45
+ * Displays only the definition descriptions on the same line.
46
+ */
47
+ inline: Boolean,
48
+ /**
49
+ * Displays both the definition term and its descriptions on the same line.
50
+ */
51
+ inlineAll: Boolean,
52
+ /**
53
+ * Displays the list in a two-column grid on larger screens.
54
+ */
55
+ table: Boolean,
56
+ /**
57
+ * Adds a rule between each item.
58
+ */
59
+ separated: Boolean,
60
+ /**
61
+ * Adds a rule to the top of the first item.
62
+ */
63
+ separatedFirst: Boolean,
64
+ /**
65
+ * Adds a rule to the bottom of the last item.
66
+ */
67
+ separatedLast: Boolean,
68
+ /**
69
+ * Reduces the margin between items.
70
+ */
71
+ compact: Boolean,
72
+ });
73
+
74
+ const internalModifiers = computed(() => ({
75
+ "inline" : props.inline,
76
+ "inline-all" : props.inlineAll,
77
+ "table" : props.table,
78
+ "separated" : props.separated,
79
+ "separated-first" : props.separatedFirst,
80
+ "separated-last" : props.separatedLast,
81
+ "compact" : props.compact,
82
+ }));
83
+
84
+ const { resolvedModifiers } = useModifiers({
85
+ props,
86
+ internal: internalModifiers,
87
+ baseClass: "definition-list"
36
88
  });
37
89
  </script>
@@ -56,8 +56,8 @@
56
56
  });
57
57
 
58
58
  const internalModifiers = computed(() => ({
59
- 'hanging' : props.hanging,
60
- 'compact' : props.compact,
59
+ "hanging" : props.hanging,
60
+ "compact" : props.compact,
61
61
  }));
62
62
 
63
63
  const { resolvedModifiers } = useModifiers({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.1.1-beta.17",
3
+ "version": "0.1.1-beta.19",
4
4
  "description": "A modular and tree-shakeable Vue 3 component library for the Ulu frontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -63,7 +63,7 @@
63
63
  "peerDependencies": {
64
64
  "@formkit/auto-animate": "^0.9.0",
65
65
  "@headlessui/vue": "^1.7.23",
66
- "@ulu/frontend": "^0.1.0-beta.114",
66
+ "@ulu/frontend": "^0.1.0-beta.118",
67
67
  "@unhead/vue": "^2.0.11",
68
68
  "vue": "^3.5.17",
69
69
  "vue-router": "^4.5.1"
@@ -87,7 +87,7 @@
87
87
  "@storybook/addon-essentials": "^9.0.0-alpha.12",
88
88
  "@storybook/addon-links": "^9.1.1",
89
89
  "@storybook/vue3-vite": "^9.1.1",
90
- "@ulu/frontend": "^0.1.0-beta.114",
90
+ "@ulu/frontend": "^0.1.0-beta.118",
91
91
  "@unhead/vue": "^2.0.11",
92
92
  "@vitejs/plugin-vue": "^6.0.0",
93
93
  "ollama": "^0.5.16",