@resee-movies/nuxt-ux 0.15.0 → 0.17.0

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.
Files changed (26) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/CardScroller.vue +59 -15
  3. package/dist/runtime/components/CardScroller.vue.d.ts +2 -0
  4. package/dist/runtime/components/GlobalHeaderAnnouncement.vue +1 -1
  5. package/dist/runtime/components/ImageTiler.vue +193 -0
  6. package/dist/runtime/components/ImageTiler.vue.d.ts +31 -0
  7. package/dist/runtime/components/InlineStatsList.vue +33 -0
  8. package/dist/runtime/components/InlineStatsList.vue.d.ts +15 -0
  9. package/dist/runtime/components/InlineStatsListItem.vue +39 -0
  10. package/dist/runtime/components/InlineStatsListItem.vue.d.ts +19 -0
  11. package/dist/runtime/components/LayoutPageColumn.vue +1 -1
  12. package/dist/runtime/components/TableOfContents.vue +19 -0
  13. package/dist/runtime/components/TableOfContents.vue.d.ts +7 -0
  14. package/dist/runtime/components/form/Form.vue +8 -2
  15. package/dist/runtime/components/form/FormField.vue +7 -7
  16. package/dist/runtime/components/form/FormFieldTurnstile.vue +2 -1
  17. package/dist/runtime/components/form/element/FormElementTurnstile.vue +2 -1
  18. package/dist/runtime/components/form/element/FormElementTurnstile.vue.d.ts +1 -0
  19. package/dist/runtime/composables/use-settings-for-breakpoint.d.ts +11 -0
  20. package/dist/runtime/composables/use-settings-for-breakpoint.js +25 -0
  21. package/dist/runtime/theme/css/brand/image.css +1 -0
  22. package/dist/runtime/theme/css/styles.css +1 -1
  23. package/dist/runtime/types/form.d.ts +1 -0
  24. package/dist/runtime/utils/form.d.ts +3 -3
  25. package/dist/runtime/utils/form.js +6 -5
  26. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@resee-movies/nuxt-ux",
3
3
  "configKey": "ux",
4
- "version": "0.15.0",
4
+ "version": "0.17.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.0"
@@ -66,7 +66,9 @@ const props = defineProps({
66
66
  loading: { type: Boolean, required: false, default: false },
67
67
  centerAlign: { type: Boolean, required: false, default: false },
68
68
  toPageContainerEdge: { type: Boolean, required: false, default: true },
69
- styleWhenTransiting: { type: String, required: false, default: "right" }
69
+ styleWhenTransiting: { type: String, required: false, default: "right" },
70
+ transitOpacity: { type: Boolean, required: false, default: true },
71
+ transitScale: { type: Boolean, required: false, default: true }
70
72
  });
71
73
  const slots = defineSlots();
72
74
  const ruler = ref();
@@ -111,43 +113,85 @@ const displayBounds = computed(() => {
111
113
  bounds.x3_4 = `${bounds.x4 - bounds.x3}px`;
112
114
  return bounds;
113
115
  });
114
- watchDebounced([displayBounds], () => {
116
+ const {
117
+ pause: pauseScrollReset,
118
+ resume: resumeScrollReset
119
+ } = watchDebounced([displayBounds], () => {
115
120
  scrollBox.value?.scrollTo({ left: 0, behavior: "instant" });
116
121
  }, { debounce: 500 });
117
122
  const scrollState = useScroll(scrollBox);
118
- watch([scrollState.x, displayBounds], () => {
119
- queueStyleChanges();
120
- debouncedQueueStyleChanges();
123
+ const {
124
+ pause: pauseStyleChanges,
125
+ resume: resumeStyleChanges
126
+ } = watch([scrollState.x, displayBounds], () => {
127
+ queueStyleChanges(props.styleWhenTransiting, props.transitOpacity, props.transitScale);
128
+ debouncedQueueStyleChanges(props.styleWhenTransiting, props.transitOpacity, props.transitScale);
121
129
  }, { flush: "sync" });
122
- const debouncedQueueStyleChanges = useDebounceFn(() => queueStyleChanges(), 100);
130
+ watch(
131
+ [() => props.transitOpacity, () => props.transitScale],
132
+ ([styleOpacity, styleScale]) => {
133
+ const areAnyDisabled = !(styleOpacity && styleScale);
134
+ const areAllDisabled = !(styleOpacity || styleScale);
135
+ if (areAnyDisabled) {
136
+ removeStyleChanges(styleOpacity, styleScale);
137
+ } else {
138
+ debouncedQueueStyleChanges(props.styleWhenTransiting, styleOpacity, styleScale);
139
+ }
140
+ if (areAllDisabled) {
141
+ pauseScrollReset();
142
+ pauseStyleChanges();
143
+ } else {
144
+ resumeScrollReset();
145
+ resumeStyleChanges();
146
+ }
147
+ }
148
+ );
149
+ const debouncedQueueStyleChanges = useDebounceFn(queueStyleChanges, 100);
123
150
  let queuedFrame = void 0;
124
- function queueStyleChanges() {
151
+ function queueStyleChanges(transiting, styleOpacity, styleScale) {
125
152
  if (queuedFrame) {
126
153
  cancelAnimationFrame(queuedFrame);
127
154
  queuedFrame = void 0;
128
155
  }
129
156
  queuedFrame = requestAnimationFrame(() => {
130
- applyStyleChanges();
157
+ applyStyleChanges(transiting, styleOpacity, styleScale);
131
158
  queuedFrame = void 0;
132
159
  });
133
160
  }
134
- function applyStyleChanges() {
135
- const styleLeft = props.styleWhenTransiting === "left" || props.styleWhenTransiting === "both";
136
- const styleRight = props.styleWhenTransiting === "right" || props.styleWhenTransiting === "both";
161
+ function applyStyleChanges(transiting, styleOpacity, styleScale) {
162
+ const styleLeft = transiting === "both" || transiting === "left";
163
+ const styleRight = transiting === "both" || transiting === "right";
137
164
  for (const element of scrollItemElements.value) {
138
165
  const [percentage, direction] = percentInBounds(element, displayBounds.value);
139
166
  if (direction === "none") {
140
- element.style.opacity = "1";
141
- element.style.scale = "1";
167
+ if (styleOpacity) {
168
+ element.style.opacity = "1";
169
+ }
170
+ if (styleScale) {
171
+ element.style.scale = "1";
172
+ }
142
173
  } else if (direction === "left" && styleLeft || direction === "right" && styleRight) {
143
- element.style.opacity = percentage.toString();
144
- if (element.firstElementChild instanceof HTMLElement) {
174
+ if (styleOpacity) {
175
+ element.style.opacity = percentage.toString();
176
+ }
177
+ if (styleScale && element.firstElementChild instanceof HTMLElement) {
145
178
  element.firstElementChild.style.transformOrigin = `center ${direction === "left" ? "right" : "left"}`;
146
179
  element.firstElementChild.style.scale = (1 - 0.2 * (1 - percentage)).toString();
147
180
  }
148
181
  }
149
182
  }
150
183
  }
184
+ function removeStyleChanges(styleOpacity, styleScale) {
185
+ for (const element of scrollItemElements.value) {
186
+ if (!styleOpacity) {
187
+ element.style.opacity = "";
188
+ }
189
+ if (!styleScale && element.firstElementChild instanceof HTMLElement) {
190
+ element.firstElementChild.style.transformOrigin = "";
191
+ element.firstElementChild.style.scale = "";
192
+ }
193
+ }
194
+ }
151
195
  function percentInBounds(element, { x1, x2, x3, x4 }) {
152
196
  const {
153
197
  left: elementStart,
@@ -9,6 +9,8 @@ export type CardScrollerProps<T> = {
9
9
  centerAlign?: boolean;
10
10
  toPageContainerEdge?: boolean;
11
11
  styleWhenTransiting?: 'left' | 'right' | 'both';
12
+ transitOpacity?: boolean;
13
+ transitScale?: boolean;
12
14
  };
13
15
  declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
14
16
  props: __VLS_PrettifyLocal<CardScrollerProps<T>> & import("vue").PublicProps & (typeof globalThis extends {
@@ -45,5 +45,5 @@ function dismissAnnouncement() {
45
45
  </script>
46
46
 
47
47
  <style scoped>
48
- @reference "tailwindcss";.announcement{--announcement-bg-color:#fff;@variant dark{--announcement-bg-color:#000}background-image:linear-gradient(to bottom,var(--announcement-bg-color) 0 calc(100% - 10px),transparent),linear-gradient(to right,var(--colorscale-resee-linear));padding-bottom:6px}.announcement.dismissable>div{padding-right:12rem}
48
+ @reference "tailwindcss";.announcement{--announcement-bg-color:#fff;@variant dark{--announcement-bg-color:#000}background-image:linear-gradient(to bottom,var(--announcement-bg-color) 0 calc(100% - 10px),transparent),linear-gradient(to right,var(--colorscale-resee-linear));padding-bottom:6px}.announcement.dismissable>div{padding-right:--spacing(12)}
49
49
  </style>
@@ -0,0 +1,193 @@
1
+ <template>
2
+ <div :class="['tiler', props.maskPreset]">
3
+ <div v-for="(entry, idx) of displayArray" :key="idx">
4
+ <ImageBase
5
+ :src = "entry.source"
6
+ aspect = "poster"
7
+ :class = "[props.imageClass, { 'opacity-0': entry.switching || entry.loading }]"
8
+ :style = "{ '--duration': `${entry.switchTime}ms`, '--delay': `${entry.switchDelay}ms` }"
9
+ @loading = "entry.loading = true"
10
+ @load = "entry.loading = false"
11
+ />
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <style scoped>
17
+ @reference "tailwindcss";@layer components{.tiler{-moz-column-gap:--spacing(v-bind(grid.gapY));column-gap:--spacing(v-bind(grid.gapY));display:grid;grid-template-columns:repeat(v-bind(grid.cols),1fr);row-gap:--spacing(v-bind(grid.gapX))}.tiler>div{justify-self:center}img{--duration:200ms;--delay:0ms;transition:opacity;transition-delay:var(--delay);transition-duration:var(--duration);transition-timing-function:ease-out}}
18
+ </style>
19
+
20
+ <script>
21
+ export const DefaultImageTilerGridSizeFallback = {
22
+ cols: 5,
23
+ rows: 8,
24
+ gap: 3
25
+ };
26
+ export const DefaultImageTilerGridSizeDefinition = {
27
+ md: {
28
+ cols: 7,
29
+ rows: 5
30
+ },
31
+ lg: {
32
+ cols: 10,
33
+ rows: 5
34
+ }
35
+ };
36
+ </script>
37
+
38
+ <script setup>
39
+ import { useState } from "#imports";
40
+ import { getRandomEntry } from "@resee-movies/utilities/arrays/get-random-entry";
41
+ import { normalizeImageFileDescriptor } from "@resee-movies/utilities/images/normalize-image-file-descriptor";
42
+ import { getRandomInteger } from "@resee-movies/utilities/numbers/get-random-integer";
43
+ import { toInteger } from "@resee-movies/utilities/numbers/to-integer";
44
+ import { sleep } from "@resee-movies/utilities/timers/sleep";
45
+ import { computed, onMounted, shallowReactive, useId, watch } from "vue";
46
+ import { useSettingsForBreakpoint } from "../composables/use-settings-for-breakpoint";
47
+ import ImageBase from "./ImageBase.vue";
48
+ const props = defineProps({
49
+ images: { type: Array, required: true },
50
+ play: { type: Boolean, required: false, default: true },
51
+ gridSettings: { type: Object, required: false, default: () => DefaultImageTilerGridSizeDefinition },
52
+ turnoverRate: { type: [String, Number, Array], required: false, default: () => [2e3, 6e3] },
53
+ switchTime: { type: [String, Number, Array], required: false, default: () => [1200, 1800] },
54
+ switchDelay: { type: [String, Number, Array], required: false, default: () => [0, 500] },
55
+ imageClass: { type: null, required: false, default: void 0 },
56
+ maskPreset: { type: [String, Array], required: false }
57
+ });
58
+ const gridSettingsForBreakpoint = useSettingsForBreakpoint(() => props.gridSettings);
59
+ const grid = computed(() => {
60
+ const settings = gridSettingsForBreakpoint.value;
61
+ const rows = settings?.rows ?? DefaultImageTilerGridSizeFallback.rows;
62
+ const cols = settings?.cols ?? DefaultImageTilerGridSizeFallback.cols;
63
+ const cells = rows * cols;
64
+ const gapX = settings?.gapX ?? settings?.gap ?? DefaultImageTilerGridSizeFallback.gap;
65
+ const gapY = settings?.gapY ?? settings?.gap ?? DefaultImageTilerGridSizeFallback.gap;
66
+ return { rows, cols, cells, gapX, gapY };
67
+ });
68
+ const componentId = useId();
69
+ const displayArray = useState(componentId, () => {
70
+ const arr = [];
71
+ for (let i = 0; i < grid.value.cells; i += 1) {
72
+ arr.push(generateImageDisplayInfo(i, arr));
73
+ }
74
+ return arr;
75
+ });
76
+ onMounted(() => {
77
+ showAll();
78
+ queueNextChange();
79
+ });
80
+ watch(() => grid.value.cells, (newCount, oldCount) => {
81
+ if (newCount < oldCount) {
82
+ displayArray.value.splice(newCount, Number.POSITIVE_INFINITY);
83
+ return;
84
+ }
85
+ if (newCount > oldCount) {
86
+ while (displayArray.value.length < newCount) {
87
+ const info = generateImageDisplayInfo(displayArray.value.length, displayArray.value);
88
+ displayArray.value.push(info);
89
+ showImageCellContent(info);
90
+ }
91
+ }
92
+ });
93
+ function queueNextChange() {
94
+ setTimeout(async () => {
95
+ if (props.play) {
96
+ const idx = getValueFromRange([0, displayArray.value.length - 1]);
97
+ const info = displayArray.value[idx];
98
+ if (info) {
99
+ await switchImageCellContent(info, idx);
100
+ }
101
+ }
102
+ queueNextChange();
103
+ }, getValueFromRange(props.turnoverRate));
104
+ }
105
+ function getValueFromRange(value) {
106
+ return Array.isArray(value) ? getRandomInteger(value[0], value[1]) : toInteger(value);
107
+ }
108
+ function pickImage(forIdx, forArray) {
109
+ let candidate = normalizeImageFileDescriptor(getRandomEntry(props.images));
110
+ if (props.images.length > 5) {
111
+ const neighbors = getNeighborIndexes(
112
+ forIdx,
113
+ grid.value.cols,
114
+ grid.value.rows,
115
+ props.images.length > 9 ? "8-edge" : "4-edge"
116
+ );
117
+ neighbors.push(forIdx);
118
+ for (let maxAttempts = 0; maxAttempts < 3; maxAttempts += 1) {
119
+ if (isImageInUse(candidate, forArray, neighbors)) {
120
+ candidate = normalizeImageFileDescriptor(getRandomEntry(props.images));
121
+ } else {
122
+ break;
123
+ }
124
+ }
125
+ }
126
+ return candidate;
127
+ }
128
+ function getNeighborIndexes(centerIdx, columnCount, rowCount, distance = "8-edge") {
129
+ const rowOffset = Math.floor(centerIdx / columnCount);
130
+ const colOffset = centerIdx % columnCount;
131
+ const top = rowOffset > 0 ? centerIdx - columnCount : -1;
132
+ const right = colOffset < columnCount ? centerIdx + 1 : -1;
133
+ const bottom = rowOffset < rowCount ? centerIdx + columnCount : -1;
134
+ const left = colOffset > 0 ? centerIdx - 1 : -1;
135
+ if (distance === "4-edge") {
136
+ return [top, right, bottom, left];
137
+ }
138
+ const topRight = top > -1 && right > -1 ? top + 1 : -1;
139
+ const bottomRight = bottom > -1 && right > -1 ? bottom + 1 : -1;
140
+ const bottomLeft = bottom > -1 && left > -1 ? bottom - 1 : -1;
141
+ const topLeft = top > -1 && left > -1 ? top - 1 : -1;
142
+ return [top, topRight, right, bottomRight, bottom, bottomLeft, left, topLeft];
143
+ }
144
+ function isImageInUse(candidate, sourceArray, indicesToCheck) {
145
+ for (const idx of indicesToCheck) {
146
+ if (idx < 0 || idx > sourceArray.length) {
147
+ continue;
148
+ }
149
+ if (sourceArray.at(idx)?.source.identifier === candidate.identifier) {
150
+ return true;
151
+ }
152
+ }
153
+ return false;
154
+ }
155
+ function generateImageDisplayInfo(forIdx, forArray) {
156
+ return shallowReactive({
157
+ source: pickImage(forIdx, forArray),
158
+ loading: true,
159
+ switching: true,
160
+ switchTime: getValueFromRange(props.switchTime),
161
+ switchDelay: getValueFromRange(props.switchDelay)
162
+ });
163
+ }
164
+ async function hideImageCellContent(info) {
165
+ if (info.switching) {
166
+ return;
167
+ }
168
+ info.switching = true;
169
+ await sleep(info.switchTime + info.switchDelay);
170
+ }
171
+ async function showImageCellContent(info) {
172
+ if (!info.switching) {
173
+ return;
174
+ }
175
+ info.switching = false;
176
+ await sleep(info.switchTime + info.switchDelay);
177
+ }
178
+ async function switchImageCellContent(info, idx) {
179
+ await hideImageCellContent(info);
180
+ info.source = pickImage(idx, displayArray.value);
181
+ await showImageCellContent(info);
182
+ }
183
+ async function showAll() {
184
+ return Promise.allSettled(
185
+ displayArray.value.map((entry) => showImageCellContent(entry))
186
+ );
187
+ }
188
+ async function hideAll() {
189
+ return Promise.allSettled(
190
+ displayArray.value.map((entry) => hideImageCellContent(entry))
191
+ );
192
+ }
193
+ </script>
@@ -0,0 +1,31 @@
1
+ import type { ImageFileDescriptor } from '@resee-movies/utilities/images/normalize-image-file-descriptor';
2
+ import type { BreakpointSettings } from '../composables/use-settings-for-breakpoint.js';
3
+ import type { HTMLElementClassNames } from '../types/index.js';
4
+ export type ValueOrRange = string | number | [min: number, max: number];
5
+ export type ImageTilerGridSizeDefinition = {
6
+ cols: number;
7
+ rows: number;
8
+ gap?: number;
9
+ gapX?: number;
10
+ gapY?: number;
11
+ };
12
+ export type ImageMaskPreset = 'image-mask-washout' | 'image-mask-gradient-washout' | 'image-mask-gradient-washout-lite' | 'image-mask-gradient-opacity' | 'image-mask-hero';
13
+ export declare const DefaultImageTilerGridSizeFallback: {
14
+ cols: number;
15
+ rows: number;
16
+ gap: number;
17
+ };
18
+ export declare const DefaultImageTilerGridSizeDefinition: BreakpointSettings<ImageTilerGridSizeDefinition>;
19
+ export interface ImageTilerProps {
20
+ images: ImageFileDescriptor[];
21
+ play?: boolean;
22
+ gridSettings?: BreakpointSettings<ImageTilerGridSizeDefinition>;
23
+ turnoverRate?: ValueOrRange;
24
+ switchTime?: ValueOrRange;
25
+ switchDelay?: ValueOrRange;
26
+ imageClass?: HTMLElementClassNames;
27
+ maskPreset?: ImageMaskPreset | ImageMaskPreset[];
28
+ }
29
+ declare const __VLS_export: import("vue").DefineComponent<ImageTilerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageTilerProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div :class="['stats', { responsive: props.responsive }]">
3
+ <dl>
4
+ <slot name="default">
5
+ <template v-if="props.items?.length">
6
+ <InlineStatsListItem
7
+ v-for = "item of props.items"
8
+ :key = "item.label"
9
+ :label = "item.label"
10
+ :description = "item.description"
11
+ :icon = "item.icon"
12
+ />
13
+ </template>
14
+ </slot>
15
+ </dl>
16
+ </div>
17
+ </template>
18
+
19
+ <script>
20
+
21
+ </script>
22
+
23
+ <script setup>
24
+ import InlineStatsListItem from "./InlineStatsListItem.vue";
25
+ const props = defineProps({
26
+ responsive: { type: Boolean, required: false, default: false },
27
+ items: { type: Array, required: false, default: void 0 }
28
+ });
29
+ </script>
30
+
31
+ <style scoped>
32
+ @reference "tailwindcss";.stats{--stats-list-spacer-width:1.2rem;overflow:clip}.stats dl{display:flex;flex-wrap:wrap;margin-inline-start:calc(var(--stats-list-spacer-width)*-1)}.stats.responsive dl{@variant max-sm{flex-direction:column}}
33
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { InlineStatsListItemProps } from './InlineStatsListItem.vue.js';
2
+ export interface InlineStatsListProps {
3
+ responsive?: boolean;
4
+ items?: InlineStatsListItemProps[];
5
+ }
6
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<InlineStatsListProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<InlineStatsListProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
7
+ default?: (props: {}) => any;
8
+ }>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
11
+ type __VLS_WithSlots<T, S> = T & {
12
+ new (): {
13
+ $slots: S;
14
+ };
15
+ };
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <dt class="sr-only">
3
+ <slot name="label">
4
+ {{ props.label }}
5
+ </slot>
6
+ </dt>
7
+
8
+ <dd>
9
+ <slot name="default">
10
+ <IconTextPair :icon="props.icon" :text="props.description">
11
+ <template #default v-if="slots.description">
12
+ <slot name="description" />
13
+ </template>
14
+ </IconTextPair>
15
+ </slot>
16
+ </dd>
17
+ </template>
18
+
19
+ <script>
20
+
21
+ </script>
22
+
23
+ <script setup>
24
+ import IconTextPair from "./IconTextPair.vue";
25
+ import { useSlots } from "vue";
26
+ defineOptions({
27
+ inheritAttrs: false
28
+ });
29
+ const props = defineProps({
30
+ label: { type: String, required: true },
31
+ description: { type: String, required: true },
32
+ icon: { type: String, required: false, default: void 0 }
33
+ });
34
+ const slots = useSlots();
35
+ </script>
36
+
37
+ <style scoped>
38
+ @reference "tailwindcss";dd{padding-inline-start:var(--stats-list-spacer-width);position:relative}dd:before{color:var(--color-global-foreground-accent);content:"|";inset-inline-start:0;margin-inline:--spacing(2);position:absolute}
39
+ </style>
@@ -0,0 +1,19 @@
1
+ export interface InlineStatsListItemProps {
2
+ label: string;
3
+ description: string;
4
+ icon?: string;
5
+ }
6
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<InlineStatsListItemProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<InlineStatsListItemProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
7
+ label?: (props: {}) => any;
8
+ } & {
9
+ default?: (props: {}) => any;
10
+ } & {
11
+ description?: (props: {}) => any;
12
+ }>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
15
+ type __VLS_WithSlots<T, S> = T & {
16
+ new (): {
17
+ $slots: S;
18
+ };
19
+ };
@@ -19,5 +19,5 @@ const props = defineProps({
19
19
  </script>
20
20
 
21
21
  <style scoped>
22
- @reference "tailwindcss";@layer components{:global(:root){--page-column-gutter:calc(var(--spacing)*3);--page-column-pad-y:calc(var(--spacing)*12);--page-column-pad-vista:calc(var(--spacing)*52)}.page-column{margin-left:auto;margin-right:auto;padding:var(--page-column-gutter)}.page-column.layout-main:where(:has(:first-child.page-container)){padding-bottom:0;padding-top:0}.page-column.layout-vista:where(:has(:last-child.page-container)){padding-bottom:0}@variant sm{.page-column{max-width:var(--container-3xl)}.page-column.layout-main{padding-top:var(--page-column-pad-y)}.page-column.layout-main,.page-column.layout-vista{padding-bottom:var(--page-column-pad-y)}}@variant lg{.page-column{max-width:var(--container-5xl)}.page-column.layout-vista{padding-top:var(--page-column-pad-vista)}}@variant portrait{.page-column.layout-vista{padding-top:min(calc(var(--spacing)*52),calc(56.25vw - 110px))}}}
22
+ @reference "tailwindcss";@layer components{:global(:root){--page-column-gutter:calc(var(--spacing)*3);--page-column-pad-y:calc(var(--spacing)*12);--page-column-pad-vista:calc(var(--spacing)*52)}.page-column{margin-left:auto;margin-right:auto;padding:var(--page-column-gutter)}.page-column.layout-main:where(:has(:first-child.page-container)){padding-bottom:0;padding-top:0}.page-column.layout-vista:where(:has(:last-child.page-container)){padding-bottom:0}@variant sm{.page-column{max-width:var(--container-3xl)}.page-column.layout-main{padding-top:var(--page-column-pad-y)}.page-column.layout-main,.page-column.layout-vista{padding-bottom:var(--page-column-pad-y)}}@variant lg{.page-column{max-width:var(--container-5xl)}.page-column.layout-vista{padding-top:var(--page-column-pad-vista)}}:global(.page-column-xl){@variant xl{.page-column{max-width:var(--container-7xl)}}}@variant portrait{.page-column.layout-vista{padding-top:min(calc(var(--spacing)*52),calc(56.25vw - 110px))}}}
23
23
  </style>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <ol v-if="props.toc?.length">
3
+ <li v-for="entry of props.toc" :key="entry.slug">
4
+ <a :href="`#${entry.slug}`" v-html="entry.text" />
5
+ <TableOfContents v-if="entry.children.length" :toc="entry.children" />
6
+ </li>
7
+ </ol>
8
+ </template>
9
+
10
+ <script>
11
+
12
+ </script>
13
+
14
+ <script setup>
15
+ import TableOfContents from "./TableOfContents.vue";
16
+ const props = defineProps({
17
+ toc: { type: null, required: true }
18
+ });
19
+ </script>
@@ -0,0 +1,7 @@
1
+ import type { TableOfContents as TableOfContentsOptions } from '@resee-movies/utilities/dom/generate-table-of-contents';
2
+ export type UiTableOfContentsProps = {
3
+ toc: TableOfContentsOptions[] | null | undefined;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<UiTableOfContentsProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<UiTableOfContentsProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -22,6 +22,8 @@
22
22
 
23
23
  <PrimeForm
24
24
  v-slot = "$form"
25
+ v-bind = "$attrs"
26
+ :id = "formUid"
25
27
  ref = "form"
26
28
  novalidate = "true"
27
29
  :validate-on-mount = "true"
@@ -53,12 +55,15 @@ import PrimeForm, {} from "@primevue/forms/form";
53
55
  import { toNonNullableArray } from "@resee-movies/utilities/arrays/to-non-nullable-array";
54
56
  import { isPromiseLike } from "@resee-movies/utilities/objects/is-promise-like";
55
57
  import { syncRefs } from "@vueuse/core";
56
- import { ref, useTemplateRef } from "vue";
58
+ import { ref, useId, useTemplateRef } from "vue";
57
59
  import FormFieldBuilder from "./FormFieldBuilder.vue";
58
60
  import LazySuccessSplash from "../SuccessSplash.vue";
59
61
  import LazyMessage from "../Message.vue";
60
62
  import { useReactiveObjectsSync } from "../../composables/use-reactive-objects-sync";
61
63
  import { provideFormInstance, getValuesFromFormState } from "../../utils/form";
64
+ defineOptions({
65
+ inheritAttrs: false
66
+ });
62
67
  const props = defineProps({
63
68
  disabled: { type: Boolean, required: false, default: false },
64
69
  onSubmit: { type: [Function, Array], required: false, default: void 0 },
@@ -72,8 +77,9 @@ const form = useTemplateRef("form");
72
77
  const values = defineModel("values", { type: null, ...{ default: void 0 } });
73
78
  const success = ref(false);
74
79
  const errors = ref();
80
+ const formUid = useId();
75
81
  defineEmits(["submit", "change"]);
76
- const formInstance = provideFormInstance();
82
+ const formInstance = provideFormInstance(formUid);
77
83
  syncRefs(() => props.disabled, formInstance.isDisabled);
78
84
  useReactiveObjectsSync({
79
85
  left: () => form.value?.states,
@@ -42,10 +42,10 @@
42
42
  :readonly = "isReadonly"
43
43
  :value = "$field.value"
44
44
  :invalid = "$field.invalid"
45
- :on-blur = "$field.props.onBlur"
46
- :on-change = "$field.props.onChange"
47
- :on-input = "$field.props.onInput"
48
- :on-invalid = "$field.props.onInvalid"
45
+ :on-blur = "$field.props?.onBlur"
46
+ :on-change = "$field.props?.onChange"
47
+ :on-input = "$field.props?.onInput"
48
+ :on-invalid = "$field.props?.onInvalid"
49
49
  />
50
50
  </template>
51
51
  </FormLabelInputPair>
@@ -87,9 +87,9 @@ const props = defineProps({
87
87
  class: { type: null, required: false }
88
88
  });
89
89
  const formState = injectFormInstance();
90
- const inputId = useId();
91
- const labelId = `${inputId}_label`;
92
- const messageId = `${inputId}_message`;
90
+ const inputId = computed(() => `${formState.formUid}_${props.name}_field`);
91
+ const labelId = computed(() => `${formState.formUid}_${props.name}_label`);
92
+ const messageId = computed(() => `${formState.formUid}_${props.name}_message`);
93
93
  const labelText = computed(() => props.label ?? humanize(props.name));
94
94
  const isDisabled = computed(() => props.disabled || formState.isDisabled.value);
95
95
  const isReadonly = computed(() => props.readonly || formState.isSubmitting.value);
@@ -4,8 +4,9 @@
4
4
  <slot name="label" />
5
5
  </template>
6
6
 
7
- <template #default="{ inputName, onChange }">
7
+ <template #default="{ inputId, inputName, onChange }">
8
8
  <FormElementTurnstile
9
+ :id = "inputId"
9
10
  :sitekey = "props.sitekey"
10
11
  :response-field-name = "inputName"
11
12
  :callback = "(token) => onChange({ value: token })"
@@ -16,6 +16,7 @@
16
16
  import { onUnmounted, useId } from "#imports";
17
17
  import { useCloudflareTurnstile } from "../../../composables/use-cloudflare-turnstile";
18
18
  const props = defineProps({
19
+ id: { type: String, required: false },
19
20
  sitekey: { type: String, required: true },
20
21
  action: { type: String, required: false },
21
22
  cData: { type: String, required: false },
@@ -40,7 +41,7 @@ const props = defineProps({
40
41
  appearance: { type: String, required: false, default: "always" },
41
42
  "feedback-enabled": { type: Boolean, required: false }
42
43
  });
43
- const widgetId = useId();
44
+ const widgetId = props.id ?? useId();
44
45
  const { onReady } = useCloudflareTurnstile();
45
46
  let turnstile = void 0;
46
47
  onReady((ts) => {
@@ -1,5 +1,6 @@
1
1
  import type { TurnstileRenderOptions } from '../../../composables/use-cloudflare-turnstile.js';
2
2
  export interface FormElementTurnstileProps extends TurnstileRenderOptions {
3
+ id?: string;
3
4
  }
4
5
  declare const __VLS_export: import("vue").DefineComponent<FormElementTurnstileProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormElementTurnstileProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
6
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,11 @@
1
+ import type { breakpointsTailwind } from '@vueuse/core';
2
+ import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
3
+ export type BreakpointSettingName = (keyof typeof breakpointsTailwind) | 'default';
4
+ export type BreakpointSettings<T> = Partial<Record<BreakpointSettingName, T>>;
5
+ /**
6
+ * Given an object whose properties are Tailwind breakpoint names, this will return
7
+ * the value of the property that matches the active breakpoint. If an exact match
8
+ * is not available, a mobile-first approach is taken - the first value of a smaller
9
+ * breakpoint, or finally the `default`, will be returned.
10
+ */
11
+ export declare function useSettingsForBreakpoint<T>(settings: MaybeRefOrGetter<BreakpointSettings<T> | null | undefined>): ComputedRef<T | undefined>;
@@ -0,0 +1,25 @@
1
+ import { computed, toValue } from "vue";
2
+ import { useReseeBreakpoints } from "./use-resee-breakpoints.js";
3
+ export function useSettingsForBreakpoint(settings) {
4
+ const ordering = ["default", "sm", "md", "lg", "xl", "2xl"];
5
+ const activeBreak = useReseeBreakpoints().active();
6
+ return computed(() => {
7
+ const breakpoint = activeBreak.value || "default";
8
+ const rawSettings = toValue(settings);
9
+ if (!rawSettings) {
10
+ return void 0;
11
+ }
12
+ if (breakpoint in rawSettings) {
13
+ return rawSettings[breakpoint];
14
+ }
15
+ let idx = ordering.indexOf(breakpoint);
16
+ while (idx > 0) {
17
+ idx -= 1;
18
+ const name = ordering[idx];
19
+ if (name && name in rawSettings) {
20
+ return rawSettings[name];
21
+ }
22
+ }
23
+ return rawSettings["default"];
24
+ });
25
+ }
@@ -0,0 +1 @@
1
+ @layer base{:root{--image-mask-gradient-opacity-dir:to bottom;--image-mask-gradient-opacity-stop:85%;--image-mask-gradient-dir:to bottom;--image-mask-gradient-stop:80%;--image-mask-color:var(--color-global-background);--image-mask-alpha-percent:85%}}@utility image-mask-washout{@apply relative before:z-1 before:absolute before:inset-0;&:before{background:--alpha(var(--image-mask-color)/var(--image-mask-alpha-percent))}}@utility image-mask-gradient-washout{@apply relative before:z-1 before:absolute before:inset-0;&:before{background:linear-gradient(var(--image-mask-gradient-dir),--alpha(var(--image-mask-color)/var(--image-mask-alpha-percent)) var(--image-mask-gradient-stop),var(--image-mask-color))}}@utility image-mask-gradient-washout-lite{@apply relative before:z-1 before:absolute before:inset-0;--image-mask-alpha-percent:10%;--image-mask-gradient-stop:30%;&:before{background:linear-gradient(var(--image-mask-gradient-dir),--alpha(var(--image-mask-color)/var(--image-mask-alpha-percent)) var(--image-mask-gradient-stop),var(--image-mask-color))}}@utility image-mask-gradient-opacity{-webkit-mask-image:linear-gradient(var(--image-mask-gradient-opacity-dir),#000 var(--image-mask-gradient-opacity-stop),transparent);mask-image:linear-gradient(var(--image-mask-gradient-opacity-dir),#000 var(--image-mask-gradient-opacity-stop),transparent)}@utility image-mask-hero{@apply relative before:z-1 before:absolute before:inset-0;--image-mask-color:#fff;@variant dark{--image-mask-color:#000}&:before{background:--alpha(var(--image-mask-color)/75%);@variant sm{background:linear-gradient(to right,--alpha(var(--image-mask-color)/90%) 35%,--alpha(var(--image-mask-color)/15%))}}}
@@ -1 +1 @@
1
- @import "./brand/anchor.css";@import "./brand/application.css";@import "./brand/button.css";@import "./brand/form.css";@import "./brand/gradient.css";@import "./brand/menu.css";@import "./brand/mobile.css";@import "./brand/status.css";@import "./brand/theme.css";@import "./brand/tooltip.css";@import "./brand/transition.css";@import "./brand/typography.css";@import "./brand/utilities.css";
1
+ @import "./brand/anchor.css";@import "./brand/application.css";@import "./brand/button.css";@import "./brand/form.css";@import "./brand/gradient.css";@import "./brand/image.css";@import "./brand/menu.css";@import "./brand/mobile.css";@import "./brand/status.css";@import "./brand/theme.css";@import "./brand/tooltip.css";@import "./brand/transition.css";@import "./brand/typography.css";@import "./brand/utilities.css";
@@ -9,6 +9,7 @@ export type FormValues = Record<string, any>;
9
9
  * injected into descendent components.
10
10
  */
11
11
  export interface FormInstance {
12
+ formUid: string | undefined;
12
13
  hasSubmitted: Ref<boolean>;
13
14
  isSubmitting: Ref<boolean>;
14
15
  isDisabled: Ref<boolean>;
@@ -13,21 +13,21 @@ export declare const FormSymbol: InjectionKey<FormInstance>;
13
13
  *
14
14
  * @private
15
15
  */
16
- export declare function createFormInstance(): FormInstance;
16
+ export declare function createFormInstance(uid?: string): FormInstance;
17
17
  /**
18
18
  * Provides the object that a Form uses to convey stateful information to the
19
19
  * DI registry. You shouldn't ever need to use this directly.
20
20
  *
21
21
  * @private
22
22
  */
23
- export declare function provideFormInstance(): FormInstance;
23
+ export declare function provideFormInstance(uid?: string): FormInstance;
24
24
  /**
25
25
  * Injects the stateful object provided by an ancestor Form instance. If not
26
26
  * available, a dummy state object is generated.
27
27
  *
28
28
  * @private
29
29
  */
30
- export declare function injectFormInstance(): FormInstance;
30
+ export declare function injectFormInstance(uid?: string): FormInstance;
31
31
  /**
32
32
  * Takes a Primevue Form's `states` object, and extracts the current values
33
33
  * of each named property. In doing so, it will deref proxies, and create
@@ -4,20 +4,21 @@ import { isObjectLike } from "@resee-movies/utilities/objects/is-object-like";
4
4
  import { isString } from "@resee-movies/utilities/strings/is-string";
5
5
  import { computed, inject, provide, ref, toRaw, toValue } from "vue";
6
6
  export const FormSymbol = Symbol("form");
7
- export function createFormInstance() {
7
+ export function createFormInstance(uid) {
8
8
  return {
9
+ formUid: uid,
9
10
  hasSubmitted: ref(false),
10
11
  isSubmitting: ref(false),
11
12
  isDisabled: ref(false)
12
13
  };
13
14
  }
14
- export function provideFormInstance() {
15
- const instance = createFormInstance();
15
+ export function provideFormInstance(uid) {
16
+ const instance = createFormInstance(uid);
16
17
  provide(FormSymbol, instance);
17
18
  return instance;
18
19
  }
19
- export function injectFormInstance() {
20
- return inject(FormSymbol, () => createFormInstance(), true);
20
+ export function injectFormInstance(uid) {
21
+ return inject(FormSymbol, () => createFormInstance(uid), true);
21
22
  }
22
23
  export function getValuesFromFormState(state) {
23
24
  const result = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resee-movies/nuxt-ux",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "The next-gen user experience library for ReSee Movies - currently in development. ",
5
5
  "repository": {
6
6
  "url": "https://github.com/ReSee-Movies/nuxt-ux.git"