@ulu/frontend-vue 0.5.15 → 0.6.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.
- package/dist/components/elements/UluButton.vue.d.ts +4 -0
- package/dist/components/elements/UluButton.vue.d.ts.map +1 -1
- package/dist/components/elements/UluButton.vue.js +31 -16
- package/dist/components/elements/UluIcon.vue.js +21 -36
- package/dist/components/forms/UluFormCheckbox.vue.d.ts +3 -19
- package/dist/components/forms/UluFormCheckbox.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormCheckbox.vue.js +10 -31
- package/dist/components/forms/UluFormFile.vue.d.ts +3 -25
- package/dist/components/forms/UluFormFile.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormFile.vue.js +11 -49
- package/dist/components/forms/UluFormItem.vue.d.ts +23 -8
- package/dist/components/forms/UluFormItem.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormItem.vue.js +126 -29
- package/dist/components/forms/UluFormLabel.vue.d.ts +24 -0
- package/dist/components/forms/UluFormLabel.vue.d.ts.map +1 -0
- package/dist/components/forms/UluFormLabel.vue.js +34 -0
- package/dist/components/forms/UluFormRadio.vue.d.ts +7 -25
- package/dist/components/forms/UluFormRadio.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormRadio.vue.js +11 -37
- package/dist/components/forms/UluFormSelect.vue.d.ts +7 -23
- package/dist/components/forms/UluFormSelect.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormSelect.vue.js +24 -43
- package/dist/components/forms/UluFormText.vue.d.ts +5 -23
- package/dist/components/forms/UluFormText.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormText.vue.js +10 -38
- package/dist/components/forms/UluFormTextarea.vue.d.ts +5 -23
- package/dist/components/forms/UluFormTextarea.vue.d.ts.map +1 -1
- package/dist/components/forms/UluFormTextarea.vue.js +10 -37
- package/dist/components/forms/UluSearchForm.vue.d.ts +24 -3
- package/dist/components/forms/UluSearchForm.vue.d.ts.map +1 -1
- package/dist/components/forms/UluSearchForm.vue.js +67 -22
- package/dist/components/index.d.ts +1 -0
- package/dist/components/systems/facets/UluFacetsFilterSelects.vue.d.ts.map +1 -1
- package/dist/components/systems/facets/UluFacetsFilterSelects.vue.js +21 -22
- package/dist/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts.map +1 -1
- package/dist/components/systems/scroll-anchors/UluScrollAnchors.vue.js +18 -10
- package/dist/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts +8 -2
- package/dist/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts.map +1 -1
- package/dist/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.js +92 -47
- package/dist/components/systems/scroll-anchors/useScrollAnchors.d.ts.map +1 -1
- package/dist/components/systems/scroll-anchors/useScrollAnchors.js +113 -70
- package/dist/components/utils/UluAction.vue.d.ts +2 -0
- package/dist/components/utils/UluAction.vue.d.ts.map +1 -1
- package/dist/components/utils/UluAction.vue.js +9 -5
- package/dist/components/visualizations/UluProgressBar.vue.d.ts +2 -2
- package/dist/index.js +130 -128
- package/dist/plugins/core/index.d.ts.map +1 -1
- package/dist/plugins/core/index.js +17 -16
- package/dist/utils/props.d.ts +7 -0
- package/dist/utils/props.d.ts.map +1 -1
- package/dist/utils/props.js +8 -2
- package/lib/components/elements/UluButton.vue +18 -3
- package/lib/components/elements/UluIcon.vue +8 -26
- package/lib/components/forms/UluForm.vue +25 -25
- package/lib/components/forms/UluFormCheckbox.vue +11 -25
- package/lib/components/forms/UluFormFieldset.vue +6 -6
- package/lib/components/forms/UluFormFile.vue +10 -40
- package/lib/components/forms/UluFormItem.vue +150 -39
- package/lib/components/forms/UluFormLabel.vue +30 -0
- package/lib/components/forms/UluFormRadio.vue +15 -34
- package/lib/components/forms/UluFormSelect.vue +19 -24
- package/lib/components/forms/UluFormText.vue +7 -25
- package/lib/components/forms/UluFormTextarea.vue +7 -25
- package/lib/components/forms/UluSearchForm.vue +67 -19
- package/lib/components/forms/UluSelectableMenu.vue +62 -62
- package/lib/components/index.js +1 -0
- package/lib/components/systems/facets/UluFacetsFilterSelects.vue +11 -14
- package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +9 -1
- package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +95 -16
- package/lib/components/systems/scroll-anchors/_scroll-anchors-nav-animated.scss +12 -1
- package/lib/components/systems/scroll-anchors/useScrollAnchors.js +195 -54
- package/lib/components/utils/UluAction.vue +6 -2
- package/lib/plugins/core/index.js +2 -1
- package/lib/utils/props.js +14 -0
- package/package.json +3 -3
|
@@ -1,32 +1,80 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<div class="
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<form class="form-theme" @submit.prevent="onSubmit">
|
|
3
|
+
<div class="input-group input-group--joined">
|
|
4
|
+
<div class="input-group__item input-group__item--field">
|
|
5
|
+
<label :for="inputId" class="hidden-visually">{{ label }}</label>
|
|
6
|
+
<input
|
|
7
|
+
type="search"
|
|
8
|
+
:id="inputId"
|
|
9
|
+
class="input-group__input"
|
|
10
|
+
:placeholder="placeholder"
|
|
11
|
+
:value="modelValue"
|
|
12
|
+
@input="$emit('update:modelValue', $event.target.value)"
|
|
13
|
+
>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="input-group__item">
|
|
16
|
+
<slot name="submit">
|
|
17
|
+
<UluButton
|
|
18
|
+
class="input-group__button"
|
|
19
|
+
v-bind="submitButtonProps"
|
|
20
|
+
/>
|
|
21
|
+
</slot>
|
|
22
|
+
</div>
|
|
11
23
|
</div>
|
|
12
|
-
|
|
13
|
-
class="search-form__submit button button--primary"
|
|
14
|
-
aria-label="Submit Search"
|
|
15
|
-
>
|
|
16
|
-
<UluIcon icon="type:search" />
|
|
17
|
-
</button>
|
|
18
|
-
</div>
|
|
24
|
+
</form>
|
|
19
25
|
</template>
|
|
20
26
|
|
|
21
27
|
<script setup>
|
|
22
|
-
import
|
|
23
|
-
|
|
28
|
+
import { computed } from "vue";
|
|
29
|
+
import { newId } from "../../utils/dom.js";
|
|
30
|
+
import UluButton from "../elements/UluButton.vue";
|
|
31
|
+
|
|
32
|
+
const props = defineProps({
|
|
33
|
+
/**
|
|
34
|
+
* The search input value (for v-model).
|
|
35
|
+
*/
|
|
36
|
+
modelValue: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: ""
|
|
39
|
+
},
|
|
24
40
|
/**
|
|
25
41
|
* The placeholder text for the search input.
|
|
26
42
|
*/
|
|
27
43
|
placeholder: {
|
|
28
44
|
type: String,
|
|
29
45
|
default: "Titles, keyword…"
|
|
30
|
-
}
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* The visually hidden label for the search input.
|
|
49
|
+
*/
|
|
50
|
+
label: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: "Search"
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Props to pass to the default UluButton component (used for submit button)
|
|
56
|
+
* - Alternately use 'submit' slot
|
|
57
|
+
*/
|
|
58
|
+
submitButtonProps: {
|
|
59
|
+
type: Object,
|
|
60
|
+
default: () => ({
|
|
61
|
+
type: "submit",
|
|
62
|
+
primary: true,
|
|
63
|
+
icon: "type:search",
|
|
64
|
+
ariaLabel: "Submit Search"
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* Optional ID for the input element. If not provided, a unique ID is generated.
|
|
69
|
+
*/
|
|
70
|
+
id: String
|
|
31
71
|
});
|
|
72
|
+
|
|
73
|
+
const emit = defineEmits(["update:modelValue", "submit"]);
|
|
74
|
+
|
|
75
|
+
const inputId = computed(() => props.id || newId());
|
|
76
|
+
|
|
77
|
+
const onSubmit = () => {
|
|
78
|
+
emit("submit", props.modelValue);
|
|
79
|
+
};
|
|
32
80
|
</script>
|
|
@@ -32,75 +32,75 @@
|
|
|
32
32
|
</template>
|
|
33
33
|
|
|
34
34
|
<script setup>
|
|
35
|
-
import { computed } from 'vue';
|
|
35
|
+
import { computed } from 'vue';
|
|
36
36
|
|
|
37
|
-
const props = defineProps({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
/**
|
|
39
|
+
* The legend for the menu.
|
|
40
|
+
*/
|
|
41
|
+
legend: String,
|
|
42
|
+
/**
|
|
43
|
+
* An array of options for the menu.
|
|
44
|
+
*/
|
|
45
|
+
options: Array,
|
|
46
|
+
/**
|
|
47
|
+
* Use compact modifier on menu stack
|
|
48
|
+
*/
|
|
49
|
+
compact: Boolean,
|
|
50
|
+
/**
|
|
51
|
+
* The type of input to use ('checkbox' or 'radio').
|
|
52
|
+
*/
|
|
53
|
+
type: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: 'checkbox',
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* The value of the menu (for v-model).
|
|
59
|
+
*/
|
|
60
|
+
modelValue: [String, Array],
|
|
61
|
+
/**
|
|
62
|
+
* If true, the input elements will be visually hidden.
|
|
63
|
+
*/
|
|
64
|
+
hideInputs: Boolean
|
|
65
|
+
});
|
|
66
66
|
|
|
67
|
-
const emit = defineEmits(['update:modelValue']);
|
|
67
|
+
const emit = defineEmits(['update:modelValue']);
|
|
68
68
|
|
|
69
|
-
const name = computed(() => props.legend ? props.legend.toLowerCase().replace(/\s+/g, '-') : `menu-${ Math.random().toString(36).substring(7) }`);
|
|
70
|
-
const legendId = computed(() => name.value ? `${name.value}-legend` : null);
|
|
71
|
-
const groupRole = computed(() => props.type === 'radio' ? 'radiogroup' : 'group');
|
|
69
|
+
const name = computed(() => props.legend ? props.legend.toLowerCase().replace(/\s+/g, '-') : `menu-${ Math.random().toString(36).substring(7) }`);
|
|
70
|
+
const legendId = computed(() => name.value ? `${name.value}-legend` : null);
|
|
71
|
+
const groupRole = computed(() => props.type === 'radio' ? 'radiogroup' : 'group');
|
|
72
72
|
|
|
73
|
-
const getId = (option) => `${name.value}-${option.uid}`;
|
|
73
|
+
const getId = (option) => `${name.value}-${option.uid}`;
|
|
74
74
|
|
|
75
|
-
const isChecked = (option) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (Array.isArray(props.modelValue)) {
|
|
80
|
-
return props.modelValue.includes(option.uid);
|
|
81
|
-
}
|
|
82
|
-
if (props.type === 'checkbox') {
|
|
83
|
-
return option.checked || false;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const handleChange = (option, event) => {
|
|
89
|
-
if (props.type === 'radio') {
|
|
90
|
-
emit('update:modelValue', option.uid);
|
|
91
|
-
} else {
|
|
75
|
+
const isChecked = (option) => {
|
|
76
|
+
if (props.type === 'radio') {
|
|
77
|
+
return props.modelValue === option.uid;
|
|
78
|
+
}
|
|
92
79
|
if (Array.isArray(props.modelValue)) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
return props.modelValue.includes(option.uid);
|
|
81
|
+
}
|
|
82
|
+
if (props.type === 'checkbox') {
|
|
83
|
+
return option.checked || false;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleChange = (option, event) => {
|
|
89
|
+
if (props.type === 'radio') {
|
|
90
|
+
emit('update:modelValue', option.uid);
|
|
91
|
+
} else {
|
|
92
|
+
if (Array.isArray(props.modelValue)) {
|
|
93
|
+
const newValue = [...props.modelValue];
|
|
94
|
+
const index = newValue.indexOf(option.uid);
|
|
95
|
+
if (index > -1) {
|
|
96
|
+
newValue.splice(index, 1);
|
|
97
|
+
} else {
|
|
98
|
+
newValue.push(option.uid);
|
|
99
|
+
}
|
|
100
|
+
emit('update:modelValue', newValue);
|
|
97
101
|
} else {
|
|
98
|
-
|
|
102
|
+
option.checked = event.target.checked;
|
|
99
103
|
}
|
|
100
|
-
emit('update:modelValue', newValue);
|
|
101
|
-
} else {
|
|
102
|
-
option.checked = event.target.checked;
|
|
103
104
|
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
105
|
+
};
|
|
106
106
|
</script>
|
package/lib/components/index.js
CHANGED
|
@@ -56,6 +56,7 @@ export { default as UluFormActions } from './forms/UluFormActions.vue';
|
|
|
56
56
|
export { default as UluFormCheckbox } from './forms/UluFormCheckbox.vue';
|
|
57
57
|
export { default as UluFormFieldset } from './forms/UluFormFieldset.vue';
|
|
58
58
|
export { default as UluFormItem } from './forms/UluFormItem.vue';
|
|
59
|
+
export { default as UluFormLabel } from './forms/UluFormLabel.vue';
|
|
59
60
|
export { default as UluFormItemsInline } from './forms/UluFormItemsInline.vue';
|
|
60
61
|
export { default as UluFormRadio } from './forms/UluFormRadio.vue';
|
|
61
62
|
export { default as UluFormRequiredChar } from './forms/UluFormRequiredChar.vue';
|
|
@@ -59,9 +59,6 @@ const props = defineProps({
|
|
|
59
59
|
},
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
console.log(props);
|
|
63
|
-
|
|
64
|
-
|
|
65
62
|
const emit = defineEmits(['facet-change']);
|
|
66
63
|
|
|
67
64
|
function onFilterChange(group, event) {
|
|
@@ -84,15 +81,15 @@ function onFilterChange(group, event) {
|
|
|
84
81
|
</script>
|
|
85
82
|
|
|
86
83
|
<style lang="scss">
|
|
87
|
-
.facets-dropdown-filters {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
.facets-dropdown-filters__group {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
84
|
+
.facets-dropdown-filters {
|
|
85
|
+
display: flex;
|
|
86
|
+
gap: 1rem;
|
|
87
|
+
align-items: center;
|
|
88
|
+
flex-wrap: wrap;
|
|
89
|
+
}
|
|
90
|
+
.facets-dropdown-filters__group {
|
|
91
|
+
display: flex;
|
|
92
|
+
gap: 0.5rem;
|
|
93
|
+
align-items: center;
|
|
94
|
+
}
|
|
98
95
|
</style>
|
|
@@ -39,7 +39,15 @@
|
|
|
39
39
|
/**
|
|
40
40
|
* Enable debug logging for the IntersectionObserver
|
|
41
41
|
*/
|
|
42
|
-
debug: Boolean
|
|
42
|
+
debug: Boolean,
|
|
43
|
+
/**
|
|
44
|
+
* If true, the last section will deactivate when scrolling past its bounding box.
|
|
45
|
+
* By default, the last section remains active until the user scrolls back up.
|
|
46
|
+
*/
|
|
47
|
+
deactivateLastItem: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: false
|
|
50
|
+
}
|
|
43
51
|
});
|
|
44
52
|
|
|
45
53
|
const emit = defineEmits(["section-change"]);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
class="scroll-anchors__nav scroll-anchors__nav--animated scroll-anchors-nav-animated"
|
|
6
6
|
:style="{ '--ulu-sa-nav-rail-width': `${ railWidth }px` }"
|
|
7
7
|
>
|
|
8
|
-
<ul class="scroll-anchors-nav-animated__rail">
|
|
8
|
+
<ul class="scroll-anchors-nav-animated__rail" ref="listRef" :style="railStyles">
|
|
9
9
|
<li
|
|
10
10
|
v-for="(item, index) in sections" :key="index"
|
|
11
11
|
:class="{ 'is-active' : item.active }"
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
:class="{
|
|
27
27
|
'scroll-anchors-nav-animated__indicator--can-transition' : indicatorAnimReady
|
|
28
28
|
}"
|
|
29
|
-
ref="indicator"
|
|
30
29
|
:style="{
|
|
31
30
|
opacity: indicatorStyles ? '1' : '0',
|
|
32
31
|
transform: `translateY(${ indicatorStyles ? indicatorStyles.y : 0 }px)`,
|
|
@@ -38,9 +37,9 @@
|
|
|
38
37
|
</template>
|
|
39
38
|
|
|
40
39
|
<script setup>
|
|
41
|
-
import { ref, computed, watch } from
|
|
40
|
+
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
|
42
41
|
import { runAfterFramePaint } from "@ulu/utils/browser/performance.js";
|
|
43
|
-
import { useScrollAnchorSections } from
|
|
42
|
+
import { useScrollAnchorSections } from "./useScrollAnchorSections.js";
|
|
44
43
|
|
|
45
44
|
const props = defineProps({
|
|
46
45
|
/**
|
|
@@ -57,6 +56,26 @@
|
|
|
57
56
|
type: Number,
|
|
58
57
|
default: 3
|
|
59
58
|
},
|
|
59
|
+
/**
|
|
60
|
+
* Dynamically trims the rail to span exactly from the center of the first indicator to the center of the last indicator. Disabled by default
|
|
61
|
+
*/
|
|
62
|
+
trimRailToCenters: {
|
|
63
|
+
type: Boolean
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* Pixel offset for the start (top) of the dynamic rail.
|
|
67
|
+
*/
|
|
68
|
+
railStartOffset: {
|
|
69
|
+
type: Number,
|
|
70
|
+
default: 0
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Pixel offset for the end (bottom) of the dynamic rail.
|
|
74
|
+
*/
|
|
75
|
+
railEndOffset: {
|
|
76
|
+
type: Number,
|
|
77
|
+
default: 0
|
|
78
|
+
},
|
|
60
79
|
/**
|
|
61
80
|
* The width of the indicator, defaults to railWidth
|
|
62
81
|
*/
|
|
@@ -76,7 +95,7 @@
|
|
|
76
95
|
*/
|
|
77
96
|
indicatorAlignment: {
|
|
78
97
|
type: String,
|
|
79
|
-
default:
|
|
98
|
+
default: "center" // options: center, top
|
|
80
99
|
},
|
|
81
100
|
/**
|
|
82
101
|
* Pixel offset for the indicator's vertical alignment
|
|
@@ -87,22 +106,42 @@
|
|
|
87
106
|
}
|
|
88
107
|
});
|
|
89
108
|
|
|
109
|
+
// State from the scroll anchor system
|
|
90
110
|
const sections = useScrollAnchorSections();
|
|
91
111
|
|
|
112
|
+
// Template refs for the list and individual links
|
|
113
|
+
const listRef = ref(null);
|
|
92
114
|
const linkRefs = ref({});
|
|
115
|
+
|
|
116
|
+
// Flag to enable CSS transitions only after initial placement
|
|
93
117
|
const indicatorAnimReady = ref(false);
|
|
94
|
-
const indicator = ref(null);
|
|
95
118
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
// Resize observer to recalculate metrics on layout shifts
|
|
120
|
+
const resizeTrigger = ref(0);
|
|
121
|
+
let resizeObserver = null;
|
|
122
|
+
|
|
123
|
+
onMounted(() => {
|
|
124
|
+
if (listRef.value) {
|
|
125
|
+
resizeObserver = new ResizeObserver(() => {
|
|
126
|
+
resizeTrigger.value++;
|
|
127
|
+
});
|
|
128
|
+
resizeObserver.observe(listRef.value);
|
|
99
129
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
onBeforeUnmount(() => {
|
|
133
|
+
if (resizeObserver) {
|
|
134
|
+
resizeObserver.disconnect();
|
|
135
|
+
resizeObserver = null;
|
|
103
136
|
}
|
|
104
|
-
|
|
105
|
-
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Helper to calculate the target position/size of the indicator for a specific item
|
|
141
|
+
*/
|
|
142
|
+
function getIndicatorMetrics(index) {
|
|
143
|
+
const link = linkRefs.value[index];
|
|
144
|
+
if (!link) return null;
|
|
106
145
|
|
|
107
146
|
const { offsetTop, offsetHeight } = link;
|
|
108
147
|
const isStatic = props.indicatorHeight != null;
|
|
@@ -110,15 +149,54 @@
|
|
|
110
149
|
const height = isStatic ? props.indicatorHeight : offsetHeight;
|
|
111
150
|
|
|
112
151
|
let y = offsetTop; // Default to 'top' alignment
|
|
113
|
-
if (props.indicatorAlignment ===
|
|
152
|
+
if (props.indicatorAlignment === "center") {
|
|
114
153
|
y = offsetTop + (offsetHeight / 2) - (height / 2);
|
|
115
154
|
}
|
|
116
155
|
|
|
117
156
|
y += props.indicatorAlignmentOffset;
|
|
118
157
|
|
|
119
158
|
return { y, height, width };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Active indicator styles
|
|
162
|
+
const indicatorStyles = computed(() => {
|
|
163
|
+
resizeTrigger.value; // Re-evaluate on resize
|
|
164
|
+
if (!sections || !sections.value || !sections.value.length) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
const activeIndex = sections.value.findIndex(s => s.active);
|
|
168
|
+
if (activeIndex === -1) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return getIndicatorMetrics(activeIndex) || false;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Background rail styles (trimmed to start/end of indicators)
|
|
175
|
+
const railStyles = computed(() => {
|
|
176
|
+
resizeTrigger.value; // Re-evaluate on resize
|
|
177
|
+
if (!props.trimRailToCenters) return {};
|
|
178
|
+
if (!sections || !sections.value || sections.value.length < 1) return {};
|
|
179
|
+
|
|
180
|
+
const firstMetrics = getIndicatorMetrics(0);
|
|
181
|
+
const lastMetrics = getIndicatorMetrics(sections.value.length - 1);
|
|
182
|
+
|
|
183
|
+
if (!firstMetrics || !lastMetrics) return {};
|
|
184
|
+
|
|
185
|
+
let top = firstMetrics.y + (firstMetrics.height / 2);
|
|
186
|
+
let bottom = lastMetrics.y + (lastMetrics.height / 2);
|
|
187
|
+
|
|
188
|
+
top += props.railStartOffset;
|
|
189
|
+
bottom += props.railEndOffset;
|
|
190
|
+
|
|
191
|
+
const height = Math.max(0, bottom - top);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
"--ulu-sa-nav-rail-top": `${top}px`,
|
|
195
|
+
"--ulu-sa-nav-rail-height": `${height}px`
|
|
196
|
+
};
|
|
120
197
|
});
|
|
121
198
|
|
|
199
|
+
// Allow transition after initial styles are applied
|
|
122
200
|
watch(indicatorStyles, (val) => {
|
|
123
201
|
if (val && !indicatorAnimReady.value) {
|
|
124
202
|
runAfterFramePaint(() => {
|
|
@@ -127,9 +205,10 @@
|
|
|
127
205
|
}
|
|
128
206
|
});
|
|
129
207
|
|
|
208
|
+
// Helper to store link template refs
|
|
130
209
|
function addLinkRef(index, el) {
|
|
131
210
|
if (el) {
|
|
132
211
|
linkRefs.value[index] = el;
|
|
133
212
|
}
|
|
134
213
|
}
|
|
135
|
-
</script>
|
|
214
|
+
</script>
|
|
@@ -49,8 +49,18 @@ $config: (
|
|
|
49
49
|
position: relative;
|
|
50
50
|
}
|
|
51
51
|
#{ $prefix }__rail {
|
|
52
|
-
border-left: var(--ulu-sa-nav-rail-width, 3px) solid color.get(get("rail-border-color"));
|
|
53
52
|
padding-left: get("rail-padding");
|
|
53
|
+
|
|
54
|
+
&::before {
|
|
55
|
+
content: "";
|
|
56
|
+
position: absolute;
|
|
57
|
+
left: 0;
|
|
58
|
+
top: var(--ulu-sa-nav-rail-top, 0);
|
|
59
|
+
height: var(--ulu-sa-nav-rail-height, 100%);
|
|
60
|
+
width: var(--ulu-sa-nav-rail-width, 3px);
|
|
61
|
+
background-color: color.get(get("rail-border-color"));
|
|
62
|
+
z-index: 0;
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
#{ $prefix }__indicator {
|
|
56
66
|
position: absolute;
|
|
@@ -58,6 +68,7 @@ $config: (
|
|
|
58
68
|
left: 0;
|
|
59
69
|
background-color: color.get(get("indicator-color"));
|
|
60
70
|
clip-path: get("indicator-clip-path");
|
|
71
|
+
z-index: 1;
|
|
61
72
|
}
|
|
62
73
|
#{ $prefix }__indicator--can-transition {
|
|
63
74
|
transition-property: height, transform, width;
|