@ulu/frontend-vue 0.1.3-beta.9 → 0.2.0-beta.2

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 (49) hide show
  1. package/dist/frontend-vue.css +1 -1
  2. package/dist/frontend-vue.js +10057 -3612
  3. package/dist/types/components/collapsible/UluAccordionGroup.vue.d.ts +20 -0
  4. package/dist/types/components/collapsible/UluAccordionGroup.vue.d.ts.map +1 -1
  5. package/dist/types/components/elements/UluCard.vue.d.ts.map +1 -1
  6. package/dist/types/components/layout/UluDataGrid.vue.d.ts +16 -1
  7. package/dist/types/components/layout/UluDataGrid.vue.d.ts.map +1 -1
  8. package/dist/types/components/layout/UluWhenBreakpoint.vue.d.ts.map +1 -1
  9. package/dist/types/components/systems/index.d.ts +4 -0
  10. package/dist/types/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts +20 -58
  11. package/dist/types/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts.map +1 -1
  12. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsHeadlessSection.vue.d.ts +27 -0
  13. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsHeadlessSection.vue.d.ts.map +1 -0
  14. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNav.vue.d.ts +17 -13
  15. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNav.vue.d.ts.map +1 -1
  16. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts +27 -30
  17. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts.map +1 -1
  18. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsSection.vue.d.ts +27 -45
  19. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsSection.vue.d.ts.map +1 -1
  20. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSection.d.ts +9 -0
  21. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSection.d.ts.map +1 -0
  22. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSections.d.ts +8 -0
  23. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSections.d.ts.map +1 -0
  24. package/dist/types/components/systems/scroll-anchors/useScrollAnchors.d.ts +14 -0
  25. package/dist/types/components/systems/scroll-anchors/useScrollAnchors.d.ts.map +1 -0
  26. package/dist/types/composables/useBreakpointManager.d.ts +2 -2
  27. package/dist/types/composables/useBreakpointManager.d.ts.map +1 -1
  28. package/lib/components/_index.scss +1 -0
  29. package/lib/components/collapsible/UluAccordionGroup.vue +39 -5
  30. package/lib/components/collapsible/UluModal.vue +2 -2
  31. package/lib/components/elements/UluCard.vue +7 -1
  32. package/lib/components/layout/UluDataGrid.vue +55 -15
  33. package/lib/components/layout/UluWhenBreakpoint.vue +11 -4
  34. package/lib/components/navigation/UluSkipLink.vue +1 -1
  35. package/lib/components/systems/index.js +4 -0
  36. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +46 -145
  37. package/lib/components/systems/scroll-anchors/UluScrollAnchorsHeadlessSection.vue +50 -0
  38. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNav.vue +18 -16
  39. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +100 -89
  40. package/lib/components/systems/scroll-anchors/UluScrollAnchorsSection.vue +65 -51
  41. package/lib/components/systems/scroll-anchors/_scroll-anchors-nav-animated.scss +67 -0
  42. package/lib/components/systems/scroll-anchors/useScrollAnchorSection.js +60 -0
  43. package/lib/components/systems/scroll-anchors/useScrollAnchorSections.js +15 -0
  44. package/lib/components/systems/scroll-anchors/useScrollAnchors.js +158 -0
  45. package/lib/composables/useBreakpointManager.js +2 -2
  46. package/package.json +5 -5
  47. package/dist/types/components/systems/scroll-anchors/symbols.d.ts +0 -7
  48. package/dist/types/components/systems/scroll-anchors/symbols.d.ts.map +0 -1
  49. package/lib/components/systems/scroll-anchors/symbols.js +0 -6
@@ -16,26 +16,66 @@
16
16
  -->
17
17
 
18
18
  <template>
19
- <div>
19
+ <component
20
+ :is="element"
21
+ :data-grid-init="initialized"
22
+ ref="rootElement"
23
+ >
20
24
  <slot />
21
- </div>
25
+ </component>
22
26
  </template>
23
27
 
24
- <script>
25
- import { setPositionClasses } from "@ulu/frontend/js/utils/dom.js";
28
+ <script setup>
29
+ import { ref, onMounted, onBeforeUnmount, watch } from "vue";
26
30
  import { debounce } from "@ulu/utils/performance.js";
27
- export default {
28
- name: "UluDataGrid",
29
- async mounted() {
30
- const setClasses = () => setPositionClasses(this.$el);
31
- setClasses();
32
- this.resizeHandler = debounce(setClasses, 200, false, this);
33
- window.addEventListener("resize", this.resizeHandler);
31
+ import { setPositionClasses } from "@ulu/frontend";
32
+
33
+ const props = defineProps({
34
+ /**
35
+ * The element to use on data-grid container
36
+ */
37
+ element: {
38
+ type: String,
39
+ default: "div"
34
40
  },
35
- beforeUnmount() {
36
- if (this.resizeHandler) {
37
- window.removeEventListener("resize", this.resizeHandler);
41
+ /**
42
+ * Tell the component when this grid is actually in a hidden container
43
+ * - When value changes the component will properly update position classes
44
+ */
45
+ hidden: Boolean // New prop from SSR version
46
+ });
47
+
48
+ const rootElement = ref(null); // Ref for the template root element
49
+ const initialized = ref(null);
50
+ let setThisPositionClasses = null; // To store the setPositionClasses function
51
+ let resizeHandler = null; // To store the debounced resize handler
52
+
53
+ onMounted(async () => {
54
+ setThisPositionClasses = () => {
55
+ if (rootElement.value) {
56
+ setPositionClasses(rootElement.value);
38
57
  }
58
+ };
59
+ setThisPositionClasses(); // Initial call
60
+ initialized.value = true;
61
+ resizeHandler = debounce(setThisPositionClasses, 200, false); // `this` context is not needed in setup
62
+ window.addEventListener("resize", resizeHandler);
63
+ });
64
+
65
+ onBeforeUnmount(() => {
66
+ if (resizeHandler) {
67
+ resizeHandler.cancel();
68
+ window.removeEventListener("resize", resizeHandler);
69
+ resizeHandler = null;
70
+ setThisPositionClasses = null; // Clear the function reference
71
+ }
72
+ });
73
+
74
+ // Watcher for the hidden prop
75
+ watch(() => props.hidden, (newVal, oldVal) => {
76
+ // Only run setClasses if it was hidden and now it's not, and the function exists
77
+ if (oldVal && !newVal && setThisPositionClasses) {
78
+ setThisPositionClasses();
39
79
  }
40
- };
80
+ });
41
81
  </script>
@@ -58,9 +58,16 @@
58
58
  };
59
59
 
60
60
  const tearDownHandlers = () => {
61
- if (uluBreakpointManager) {
61
+ if (uluBreakpointManager.value) {
62
62
  handlers.value.forEach(({ name, direction, handler }) => {
63
- uluBreakpointManager.at(name).remove(handler, direction);
63
+ const breakpoint = uluBreakpointManager.value.at(name);
64
+ if (breakpoint) {
65
+ try {
66
+ breakpoint.remove(handler, direction);
67
+ } catch (error) {
68
+ console.error(error);
69
+ }
70
+ }
64
71
  });
65
72
  }
66
73
  handlers.value = [];
@@ -77,9 +84,9 @@
77
84
  // Watch all the props and update if they change
78
85
  // - Using array syntax to avoid "deep" flag
79
86
  watch([() => props.max, () => props.min, () => props.only], () => {
80
- if (uluBreakpointManager && handlersSetup.value) {
87
+ if (uluBreakpointManager.value && handlersSetup.value) {
81
88
  tearDownHandlers();
82
- setupHandlers(uluBreakpointManager);
89
+ setupHandlers(uluBreakpointManager.value);
83
90
  }
84
91
  });
85
92
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <a class="site-skip-link hidden-visually-focusable" href="#main-content">
2
+ <a class="skip-link hidden-visually-focusable" href="#main-content">
3
3
  Skip to main content
4
4
  </a>
5
5
  </template>
@@ -11,10 +11,14 @@ export { default as UluFacetsSidebarLayout } from './facets/UluFacetsSidebarLayo
11
11
  export { default as UluFacetsSort } from './facets/UluFacetsSort.vue';
12
12
  export { default as UluFacetsList } from './facets/UluFacetsList.vue';
13
13
 
14
+ export { useScrollAnchors } from './scroll-anchors/useScrollAnchors.js';
15
+ export { useScrollAnchorSection } from './scroll-anchors/useScrollAnchorSection.js';
16
+ export { useScrollAnchorSections } from './scroll-anchors/useScrollAnchorSections.js';
14
17
  export { default as UluScrollAnchors } from './scroll-anchors/UluScrollAnchors.vue';
15
18
  export { default as UluScrollAnchorsNav } from './scroll-anchors/UluScrollAnchorsNav.vue';
16
19
  export { default as UluScrollAnchorsNavAnimated } from './scroll-anchors/UluScrollAnchorsNavAnimated.vue';
17
20
  export { default as UluScrollAnchorsSection } from './scroll-anchors/UluScrollAnchorsSection.vue';
21
+ export { default as UluScrollAnchorsHeadlessSection } from './scroll-anchors/UluScrollAnchorsHeadlessSection.vue';
18
22
 
19
23
  export { default as UluShowSkeleton } from './skeleton/UluShowSkeleton.vue';
20
24
  export { default as UluSkeletonContent } from './skeleton/UluSkeletonContent.vue';
@@ -1,153 +1,54 @@
1
- <!-- Version: 0.0.2? (NEED to diff, unsure of changes) -->
2
1
  <template>
3
- <div class="scroll-anchors">
2
+ <div class="scroll-anchors" ref="componentEl">
4
3
  <slot/>
5
4
  </div>
6
5
  </template>
7
6
 
8
- <script>
9
- import { computed } from "vue";
10
- import { SECTIONS, REGISTER, UNREGISTER } from "./symbols.js";
11
- export default {
12
- name: "ScrollAnchors",
13
- emits: ["section-change"],
14
- props: {
15
- firstItemActive: Boolean,
16
- /**
17
- * Observe
18
- */
19
- observerOptions: {
20
- type: Object,
21
- default: () => ({
22
- root: null,
23
- threshhold: [0,1],
24
- rootMargin: "-25% 0px -55% 0px"
25
- // root: null,
26
- // threshhold: [0,1],
27
- // rootMargin: "25% 0px 75% 0px"
28
- })
29
- }
30
- },
31
- data() {
32
- return {
33
- isMounted: false,
34
- sections: [], // Child components will section themselves
35
- };
36
- },
7
+ <script setup>
8
+ import { ref, computed, provide } from "vue";
9
+ import { useScrollAnchors } from "./useScrollAnchors.js";
10
+
11
+ const props = defineProps({
37
12
  /**
38
- * Interface for chil components to register themselves
39
- * - Uses symbols
13
+ * Make the first item active by default on load
40
14
  */
41
- provide() {
42
- return {
43
- [SECTIONS]: computed(() => this.sections),
44
- [REGISTER]: (instance) => {
45
- const { titleId, title } = instance;
46
- const { element } = instance.$refs;
47
- this.sections.push({
48
- instance,
49
- titleId,
50
- title,
51
- element,
52
- active: false
53
- });
54
- this.update();
55
- },
56
- [UNREGISTER]: (instance) => {
57
- const sections = this.sections;
58
- const index = sections.findIndex(r => r.instance === instance);
59
- if (index > -1) {
60
- sections.splice(index, 1);
61
- }
62
- this.update();
63
- },
64
- };
65
- },
66
- methods: {
67
- update() {
68
- if (this.isMounted) {
69
- this.observeItems();
70
- }
71
- },
72
- getSectionIndex(el) {
73
- return this.sections.findIndex(({ element }) => el === element);
74
- },
75
- /**
76
- * Sets up a new observer to watch the section visibility
77
- */
78
- createObserver() {
79
- const { observerOptions, sections, removeActive, firstItemActive } = this;
80
- let lastY = 0;
81
- // Observer callback, basically just sets active state for a given slide
82
- // - isIntersecting will change when the element enters and leaves
83
- const onObserve = (entries) => {
84
- entries.forEach(({ target, isIntersecting }) => {
85
- const index = this.getSectionIndex(target);
86
- const y = target.offsetTop;
87
- const section = sections[index];
88
- const firstExiting = index === 0 && lastY > y;
89
- const lastExiting = index === sections.length - 1 && lastY < y;
90
- if (section) {
91
- this.$nextTick(() => {
92
- if (isIntersecting) {
93
- removeActive(section);
94
- section.active = true;
95
- // Only allow first and last to
96
- } else if (firstExiting && !firstItemActive) {
97
- removeActive();
98
- } else if (lastExiting && section.active) {
99
- removeActive();
100
- }
101
- this.$emit("section-change", {
102
- section,
103
- sections,
104
- active: isIntersecting
105
- });
106
- });
107
- }
108
- });
109
- };
110
- // Add non-reactive prop for removal and changes to targets
111
- this.observer = new IntersectionObserver(onObserve, observerOptions);
112
- },
113
- /**
114
- * Add all slide elements as targets in observer
115
- */
116
- observeItems() {
117
- const { observer, sections } = this;
118
- observer.disconnect();
119
- sections.forEach(({ element }) => {
120
- if (element) {
121
- observer.observe(element);
122
- }
123
- });
124
- },
125
- removeActive(except = null) {
126
- this.sections.forEach(s => {
127
- if (s !== except) {
128
- s.active = false;
129
- }
130
- });
131
- },
132
- /**
133
- * Remove observer and it's internal DOM references (GC)
134
- */
135
- destroyObserver() {
136
- this.observer.disconnect();
137
- this.observer = null;
138
- },
139
- },
140
- mounted() {
141
- const first = this.sections[0];
142
- if (this.firstItemActive && first) {
143
- first.active = true;
144
- }
145
- this.createObserver();
146
- this.observeItems();
147
- this.isMounted = true;
148
- },
149
- unmounted() {
150
- this.destroyObserver();
15
+ firstItemActive: Boolean,
16
+ /**
17
+ * IntersectionObserver options
18
+ * - Defaults: { root: null, threshold: 0, rootMargin: "-25% 0px -55% 0px" }
19
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver
20
+ */
21
+ observerOptions: {
22
+ type: Object,
23
+ default: () => ({
24
+ root: null,
25
+ threshold: 0,
26
+ rootMargin: "-25% 0px -55% 0px"
27
+ })
151
28
  },
152
- };
153
- </script>
29
+ /**
30
+ * Enable debug logging for the IntersectionObserver
31
+ */
32
+ debug: Boolean
33
+ });
34
+
35
+ const emit = defineEmits(["section-change"]);
36
+
37
+ const sections = ref([]);
38
+ const componentEl = ref(null);
39
+
40
+ useScrollAnchors({ sections, props, emit, componentElRef: componentEl });
41
+
42
+ provide('uluScrollAnchorsSections', computed(() => sections.value));
43
+
44
+ provide('uluScrollAnchorsRegister', (section) => {
45
+ sections.value.push(section);
46
+ });
47
+
48
+ provide('uluScrollAnchorsUnregister', (sectionId) => {
49
+ const index = sections.value.findIndex(r => r.id === sectionId);
50
+ if (index > -1) {
51
+ sections.value.splice(index, 1);
52
+ }
53
+ });
54
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <component
3
+ :is="element"
4
+ :class="[
5
+ 'scroll-anchors__section',
6
+ { 'is-active': isActive }
7
+ ]"
8
+ :data-scrollpoint-state="sectionState"
9
+ ref="sectionRef"
10
+ >
11
+ <slot :isActive="isActive" :titleId="titleId" :section="section" :inactiveFrom="inactiveFrom" :activeFrom="activeFrom" :sectionState="sectionState" />
12
+ </component>
13
+ </template>
14
+
15
+ <script setup>
16
+ import { computed } from "vue";
17
+ import { useScrollAnchorSection } from "./useScrollAnchorSection";
18
+
19
+ const props = defineProps({
20
+ /**
21
+ * The title of the section, used for navigation and generating a default ID
22
+ */
23
+ title: {
24
+ type: String,
25
+ required: true
26
+ },
27
+ /**
28
+ * A custom ID to use for the section anchor, overriding the auto-generated one
29
+ */
30
+ customTitleId: String,
31
+ /**
32
+ * Element to use
33
+ */
34
+ element: {
35
+ type: String,
36
+ default: "div"
37
+ }
38
+ });
39
+
40
+ const { sectionRef, titleId, isActive, inactiveFrom, activeFrom, section } = useScrollAnchorSection(props);
41
+
42
+ const sectionState = computed(() => {
43
+ if (isActive.value) {
44
+ if (activeFrom.value) return `enter-${activeFrom.value}`;
45
+ } else {
46
+ if (inactiveFrom.value) return `exit-${inactiveFrom.value}`;
47
+ }
48
+ return null;
49
+ });
50
+ </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <component
3
- v-if="sections.length"
3
+ v-if="sections && sections.length"
4
4
  :is="element"
5
5
  class="scroll-anchors__nav"
6
6
  >
@@ -13,25 +13,27 @@
13
13
  :class="{ 'is-active' : item.active }"
14
14
  :href="`#${ item.titleId }`"
15
15
  >
16
- {{ item.title }}
16
+ <slot :item="item" :index="index">
17
+ {{ item.title }}
18
+ </slot>
17
19
  </a>
18
20
  </li>
19
21
  </ul>
20
22
  </component>
21
23
  </template>
22
24
 
23
- <script>
24
- import { SECTIONS } from "./symbols.js";
25
- export default {
26
- name: "ScrollAnchorsNav",
27
- inject: {
28
- sections: { from: SECTIONS }
29
- },
30
- props: {
31
- element: {
32
- type: String,
33
- default: "nav"
34
- }
25
+ <script setup>
26
+ import { useScrollAnchorSections } from "./useScrollAnchorSections.js";
27
+
28
+ defineProps({
29
+ /**
30
+ * The HTML element to use for the navigation root
31
+ */
32
+ element: {
33
+ type: String,
34
+ default: "nav"
35
35
  }
36
- };
37
- </script>
36
+ });
37
+
38
+ const sections = useScrollAnchorSections();
39
+ </script>
@@ -1,16 +1,11 @@
1
- <!--
2
- Version: 0.0.2
3
- Changes:
4
- - 0.0.2 | Added transition initial state/class so the indicator
5
- doesn't transition at first
6
- -->
7
1
  <template>
8
2
  <component
9
- v-if="sections.length"
3
+ v-if="sections && sections.length"
10
4
  :is="element"
11
- class="scroll-anchors__nav scroll-anchors__nav--animated"
5
+ class="scroll-anchors__nav scroll-anchors__nav--animated scroll-anchors-nav-animated"
6
+ :style="{ '--ulu-sa-nav-rail-width': `${ railWidth }px` }"
12
7
  >
13
- <ul class="scroll-anchors__rail">
8
+ <ul class="scroll-anchors-nav-animated__rail">
14
9
  <li
15
10
  v-for="(item, index) in sections" :key="index"
16
11
  :class="{ 'is-active' : item.active }"
@@ -20,105 +15,121 @@ Changes:
20
15
  :ref="(el) => addLinkRef(index, el)"
21
16
  :href="`#${ item.titleId }`"
22
17
  >
23
- {{ item.title }}
18
+ <slot :item="item" :index="index">
19
+ {{ item.title }}
20
+ </slot>
24
21
  </a>
25
22
  </li>
26
23
  </ul>
27
24
  <div
28
- class="scroll-anchors__indicator"
25
+ class="scroll-anchors-nav-animated__indicator"
29
26
  :class="{
30
- 'scroll-anchors__indicator--can-transition' : indicatorAnimReady
27
+ 'scroll-anchors-nav-animated__indicator--can-transition' : indicatorAnimReady
31
28
  }"
32
29
  ref="indicator"
33
- :style="{
30
+ :style="{
34
31
  opacity: indicatorStyles ? '1' : '0',
35
- transform: `translateY(${ indicatorStyles.y }px)`,
36
- height: `${ indicatorStyles.height }px`,
32
+ transform: `translateY(${ indicatorStyles ? indicatorStyles.y : 0 }px)`,
33
+ height: `${ indicatorStyles ? indicatorStyles.height : 0 }px`,
34
+ width: `${ indicatorStyles ? indicatorStyles.width : 0 }px`
37
35
  }"
38
36
  ></div>
39
37
  </component>
40
38
  </template>
41
39
 
42
- <script>
40
+ <script setup>
41
+ import { ref, computed, watch } from 'vue';
43
42
  import { runAfterFramePaint } from "@ulu/utils/browser/performance.js";
44
- import { SECTIONS } from "./symbols.js";
45
- export default {
46
- name: "ScrollAnchorsNavAnimated",
47
- inject: {
48
- sections: { from: SECTIONS }
43
+ import { useScrollAnchorSections } from './useScrollAnchorSections.js';
44
+
45
+ const props = defineProps({
46
+ /**
47
+ * The HTML element to use for the navigation root
48
+ */
49
+ element: {
50
+ type: String,
51
+ default: "nav"
49
52
  },
50
- props: {
51
- /**
52
- * Element to use for container
53
- */
54
- element: {
55
- type: String,
56
- default: "nav"
57
- },
53
+ /**
54
+ * The width of the navigation rail
55
+ */
56
+ railWidth: {
57
+ type: Number,
58
+ default: 3
58
59
  },
59
- data() {
60
- return {
61
- linkRefs: {},
62
- indicatorAnimReady: false
63
- };
60
+ /**
61
+ * The width of the indicator, defaults to railWidth
62
+ */
63
+ indicatorWidth: {
64
+ type: Number,
65
+ default: null
64
66
  },
65
- computed: {
66
- indicatorStyles() {
67
- const { sections, linkRefs } = this;
68
- const linkCount = Object.keys(linkRefs).length;
69
- // Checking for sections and link refs incase
70
- // we were waiting for the components to mount, etc
71
- if (!sections || !linkCount) {
72
- return false;
73
- }
74
- const activeIndex = sections.findIndex(s => s.active);
75
- if (activeIndex === -1) {
76
- return false;
77
- }
78
- const link = this.linkRefs[activeIndex];
79
- const { offsetTop, offsetHeight } = link;
80
- return {
81
- y: offsetTop,
82
- height: offsetHeight,
83
- initial: this.inidica
84
- };
85
- }
67
+ /**
68
+ * If set, creates a static height, centered indicator
69
+ */
70
+ indicatorHeight: {
71
+ type: Number,
72
+ default: null
86
73
  },
87
- watch: {
88
- indicatorStyles(val) {
89
- if (val && !this.indicatorAnimReady) {
90
- runAfterFramePaint(() => {
91
- this.indicatorAnimReady = true;
92
- });
93
- }
94
- }
74
+ /**
75
+ * Vertical alignment of the indicator relative to the link
76
+ */
77
+ indicatorAlignment: {
78
+ type: String,
79
+ default: 'center' // options: center, top
95
80
  },
96
- methods: {
97
- addLinkRef(index, el) {
98
- this.linkRefs[index] = el;
99
- }
81
+ /**
82
+ * Pixel offset for the indicator's vertical alignment
83
+ */
84
+ indicatorAlignmentOffset: {
85
+ type: Number,
86
+ default: 0
100
87
  }
101
- };
102
- </script>
88
+ });
103
89
 
104
- <style lang="scss">
105
- // User can override these styles
106
- // - Think this is better than props/etc
107
- // - Refactored from props to just plain css to be overridden
108
- .scroll-anchors__rail {
109
- border-left: 3px solid rgb(220, 220, 220);
110
- padding-left: 1rem;
111
- }
112
- .scroll-anchors__indicator {
113
- position: absolute;
114
- top: 0;
115
- left: 0;
116
- width: 3px;
117
- background-color: black;
118
- }
119
- .scroll-anchors__indicator--can-transition {
120
- transition-property: height, transform;
121
- transition-timing-function: ease-in-out;
122
- transition-duration: 250ms;
90
+ const sections = useScrollAnchorSections();
91
+
92
+ const linkRefs = ref({});
93
+ const indicatorAnimReady = ref(false);
94
+ const indicator = ref(null);
95
+
96
+ const indicatorStyles = computed(() => {
97
+ if (!sections || !sections.value || !sections.value.length) {
98
+ return false;
99
+ }
100
+ const activeIndex = sections.value.findIndex(s => s.active);
101
+ if (activeIndex === -1) {
102
+ return false;
103
+ }
104
+ const link = linkRefs.value[activeIndex];
105
+ if (!link) return false; // Link might not be rendered yet
106
+
107
+ const { offsetTop, offsetHeight } = link;
108
+ const isStatic = props.indicatorHeight != null;
109
+ const width = props.indicatorWidth ?? props.railWidth;
110
+ const height = isStatic ? props.indicatorHeight : offsetHeight;
111
+
112
+ let y = offsetTop; // Default to 'top' alignment
113
+ if (props.indicatorAlignment === 'center') {
114
+ y = offsetTop + (offsetHeight / 2) - (height / 2);
115
+ }
116
+
117
+ y += props.indicatorAlignmentOffset;
118
+
119
+ return { y, height, width };
120
+ });
121
+
122
+ watch(indicatorStyles, (val) => {
123
+ if (val && !indicatorAnimReady.value) {
124
+ runAfterFramePaint(() => {
125
+ indicatorAnimReady.value = true;
126
+ });
127
+ }
128
+ });
129
+
130
+ function addLinkRef(index, el) {
131
+ if (el) {
132
+ linkRefs.value[index] = el;
133
+ }
123
134
  }
124
- </style>
135
+ </script>