@ulu/frontend-vue 0.1.3-beta.10 → 0.1.3-beta.12

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 (37) hide show
  1. package/dist/frontend-vue.css +1 -1
  2. package/dist/frontend-vue.js +2918 -2774
  3. package/dist/types/components/layout/UluDataGrid.vue.d.ts +16 -1
  4. package/dist/types/components/layout/UluDataGrid.vue.d.ts.map +1 -1
  5. package/dist/types/components/systems/index.d.ts +4 -0
  6. package/dist/types/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts +20 -58
  7. package/dist/types/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts.map +1 -1
  8. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsHeadlessSection.vue.d.ts +24 -0
  9. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsHeadlessSection.vue.d.ts.map +1 -0
  10. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNav.vue.d.ts +17 -13
  11. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNav.vue.d.ts.map +1 -1
  12. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts +27 -30
  13. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts.map +1 -1
  14. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsSection.vue.d.ts +27 -45
  15. package/dist/types/components/systems/scroll-anchors/UluScrollAnchorsSection.vue.d.ts.map +1 -1
  16. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSection.d.ts +9 -0
  17. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSection.d.ts.map +1 -0
  18. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSections.d.ts +8 -0
  19. package/dist/types/components/systems/scroll-anchors/useScrollAnchorSections.d.ts.map +1 -0
  20. package/dist/types/components/systems/scroll-anchors/useScrollAnchors.d.ts +14 -0
  21. package/dist/types/components/systems/scroll-anchors/useScrollAnchors.d.ts.map +1 -0
  22. package/lib/components/_index.scss +1 -0
  23. package/lib/components/layout/UluDataGrid.vue +51 -16
  24. package/lib/components/systems/index.js +4 -0
  25. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +46 -145
  26. package/lib/components/systems/scroll-anchors/UluScrollAnchorsHeadlessSection.vue +36 -0
  27. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNav.vue +18 -16
  28. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +100 -92
  29. package/lib/components/systems/scroll-anchors/UluScrollAnchorsSection.vue +55 -52
  30. package/lib/components/systems/scroll-anchors/_scroll-anchors-nav-animated.scss +66 -0
  31. package/lib/components/systems/scroll-anchors/useScrollAnchorSection.js +56 -0
  32. package/lib/components/systems/scroll-anchors/useScrollAnchorSections.js +15 -0
  33. package/lib/components/systems/scroll-anchors/useScrollAnchors.js +152 -0
  34. package/package.json +2 -2
  35. package/dist/types/components/systems/scroll-anchors/symbols.d.ts +0 -7
  36. package/dist/types/components/systems/scroll-anchors/symbols.d.ts.map +0 -1
  37. package/lib/components/systems/scroll-anchors/symbols.js +0 -6
@@ -0,0 +1,66 @@
1
+ ////
2
+ /// @group scroll-anchors-nav-animated
3
+ /// For use with UluScrollAnchorsNavAnimated vue component.
4
+ ////
5
+
6
+ @use "sass:map";
7
+ @use "sass:meta";
8
+
9
+ @use "@ulu/frontend/scss/selector";
10
+ @use "@ulu/frontend/scss/utils";
11
+
12
+ // Used for function fallback
13
+ $-fallbacks: () !default;
14
+
15
+ /// Module Settings
16
+ /// @type Map
17
+ $config: (
18
+ "rail-border-color": #dcdcdc,
19
+ "rail-padding": 1rem,
20
+ "indicator-color": #000,
21
+ "indicator-clip-path": null,
22
+ "transition-duration": 250ms,
23
+ "transition-timing-function": ease-in-out
24
+ ) !default;
25
+
26
+ /// Change modules $config
27
+ /// @param {Map} $changes Map of changes
28
+ @mixin set($changes) {
29
+ $config: map.merge($config, $changes) !global;
30
+ }
31
+
32
+ /// Get a config option
33
+ /// @param {Map} $name Name of property
34
+ @function get($name) {
35
+ $value: utils.require-map-get($config, $name, "scroll-anchors-nav-animated [config]");
36
+ @return utils.function-fallback($name, $value, $-fallbacks);
37
+ }
38
+
39
+ /// Prints component styles
40
+ /// @demo scroll-anchors-nav-animated
41
+ /// @example scss
42
+ /// @include ulu.component-scroll-anchors-nav-animated-styles();
43
+
44
+ @mixin styles {
45
+ $prefix: selector.class("scroll-anchors-nav-animated");
46
+
47
+ #{ $prefix } {
48
+ position: relative;
49
+ }
50
+ #{ $prefix }__rail {
51
+ border-left: var(--rail-width, 3px) solid get("rail-border-color");
52
+ padding-left: get("rail-padding");
53
+ }
54
+ #{ $prefix }__indicator {
55
+ position: absolute;
56
+ top: 0;
57
+ left: 0;
58
+ background-color: get("indicator-color");
59
+ clip-path: get("indicator-clip-path");
60
+ }
61
+ #{ $prefix }__indicator--can-transition {
62
+ transition-property: height, transform, width;
63
+ transition-timing-function: get("transition-timing-function");
64
+ transition-duration: get("transition-duration");
65
+ }
66
+ }
@@ -0,0 +1,56 @@
1
+ import { ref, onMounted, onUnmounted, inject, computed, reactive, watch } from 'vue';
2
+ import { urlize } from '@ulu/utils/string.js';
3
+
4
+ /**
5
+ * Composable for a component that acts as a section within the Scroll Anchors system.
6
+ * It handles registering the section with the parent `UluScrollAnchors` component,
7
+ * manages its active state, and provides reactive properties for use in the template.
8
+ * @param {object} props The component props, requires `title` and optional `customTitleId`.
9
+ * @returns {object} An object with reactive properties for the section: `sectionRef` (the ref to bind), `titleId`, `isActive`, and the raw `section` object.
10
+ */
11
+ export function useScrollAnchorSection(props) {
12
+ const sectionRef = ref(null); // for the user to bind to their root element
13
+
14
+ const register = inject('uluScrollAnchorsRegister');
15
+ const unregister = inject('uluScrollAnchorsUnregister');
16
+ const newId = title => `ulu-sa-${ urlize(title) }`;
17
+
18
+ const titleId = computed(() => props.customTitleId || newId(props.title));
19
+
20
+ const section = reactive({
21
+ id: Symbol('section-id'),
22
+ title: props.title,
23
+ titleId: titleId.value,
24
+ element: null, // will be set on mount
25
+ active: false
26
+ });
27
+
28
+ // Keep title and titleId in sync with props
29
+ watch(() => props.title, (newTitle) => {
30
+ section.title = newTitle;
31
+ section.titleId = props.customTitleId || newId(newTitle);
32
+ });
33
+ watch(() => props.customTitleId, (newTitleId) => {
34
+ section.titleId = newTitleId || newId(props.title);
35
+ });
36
+
37
+ onMounted(() => {
38
+ if (register && sectionRef.value) {
39
+ section.element = sectionRef.value;
40
+ register(section);
41
+ }
42
+ });
43
+
44
+ onUnmounted(() => {
45
+ if (unregister) {
46
+ unregister(section.id);
47
+ }
48
+ });
49
+
50
+ return {
51
+ sectionRef, // the ref for the user to attach
52
+ titleId,
53
+ isActive: computed(() => section.active),
54
+ section
55
+ };
56
+ }
@@ -0,0 +1,15 @@
1
+ import { inject } from 'vue';
2
+
3
+ /**
4
+ * Composable that provides a reactive list of all registered scroll anchor sections.
5
+ * This is the recommended way to access section data for building custom navigation.
6
+ * Must be used within a component that is a descendant of `UluScrollAnchors`.
7
+ * @returns {object} A reactive ref containing an array of section objects.
8
+ */
9
+ export function useScrollAnchorSections() {
10
+ const sections = inject('uluScrollAnchorsSections');
11
+ if (!sections) {
12
+ console.warn('useScrollAnchorSections() must be used within an UluScrollAnchors component provider.');
13
+ }
14
+ return sections;
15
+ }
@@ -0,0 +1,152 @@
1
+ import { onMounted, onUnmounted, nextTick, watch } from "vue";
2
+ import { getScrollParent } from "@ulu/utils/browser/dom.js";
3
+
4
+ /**
5
+ * The main composable that contains the core "engine" for the Scroll Anchors system.
6
+ * It encapsulates the IntersectionObserver logic to track sections and manage their active state.
7
+ * This is intended for advanced use cases where a developer needs to build a custom provider
8
+ * component instead of using the default `UluScrollAnchors`.
9
+ * @param {{sections: object, props: object, emit: Function, componentElRef: object}} options
10
+ */
11
+ export function useScrollAnchors({ sections, props, emit, componentElRef }) {
12
+ let observer = null;
13
+
14
+ function getSectionIndex(el) {
15
+ return sections.value.findIndex(({ element }) => el === element);
16
+ }
17
+
18
+ function removeActive(except = null) {
19
+ sections.value.forEach(s => {
20
+ if (s !== except) {
21
+ s.active = false;
22
+ }
23
+ });
24
+ }
25
+
26
+ function createObserver() {
27
+ let lastScrollY = 0;
28
+ let isInitialObservation = true;
29
+
30
+ const onObserve = (entries) => {
31
+ const { root } = observer;
32
+ const currentScrollY = root ? root.scrollTop : document.documentElement.scrollTop || window.scrollY;
33
+
34
+ if (props.debug) {
35
+ console.group("useScrollAnchors: onObserve");
36
+ console.log("Observer:", observer);
37
+ console.log("Last/Current Y:", `${ lastScrollY }/${ currentScrollY }`);
38
+ console.log("Entries:", entries.map(e => ({ el: e.target, is: e.isIntersecting })));
39
+ }
40
+
41
+ if (isInitialObservation && props.firstItemActive) {
42
+ if (props.debug) console.log("Initial observation, respecting `firstItemActive`.");
43
+ isInitialObservation = false;
44
+ lastScrollY = currentScrollY;
45
+ if (props.debug) console.groupEnd();
46
+ return;
47
+ }
48
+ isInitialObservation = false;
49
+
50
+ const scrollDirection = currentScrollY > lastScrollY ? 'down' : 'up';
51
+ lastScrollY = currentScrollY;
52
+ if (props.debug) console.log(`Scroll direction: ${scrollDirection}`);
53
+
54
+ const intersectingEntries = entries.filter(entry => entry.isIntersecting);
55
+ if (props.debug) console.log("Intersecting entries:", intersectingEntries.map(e => e.target));
56
+
57
+ if (intersectingEntries.length > 0) {
58
+ intersectingEntries.sort((a, b) => getSectionIndex(a.target) - getSectionIndex(b.target));
59
+
60
+ const targetEntry = scrollDirection === 'down'
61
+ ? intersectingEntries[intersectingEntries.length - 1]
62
+ : intersectingEntries[0];
63
+ if (props.debug) console.log("Chosen target entry:", targetEntry.target);
64
+
65
+ const sectionToActivate = sections.value[getSectionIndex(targetEntry.target)];
66
+
67
+ if (sectionToActivate && !sectionToActivate.active) {
68
+ if (props.debug) console.log("Activating section:", sectionToActivate.title);
69
+ nextTick(() => {
70
+ removeActive(sectionToActivate);
71
+ sectionToActivate.active = true;
72
+ emit("section-change", { section: sectionToActivate, sections: sections.value, active: true });
73
+ });
74
+ }
75
+ } else {
76
+ if (props.debug) console.log("No intersecting entries. Checking edge cases.");
77
+ const activeSection = sections.value.find(s => s.active);
78
+ if (activeSection) {
79
+ const entryForActive = entries.find(e => e.target === activeSection.element);
80
+ if (entryForActive && !entryForActive.isIntersecting) {
81
+ const index = getSectionIndex(entryForActive.target);
82
+ const isFirst = index === 0;
83
+ const isLast = index === sections.value.length - 1;
84
+ if ((isFirst && scrollDirection === 'up' && !props.firstItemActive) || (isLast && scrollDirection === 'down')) {
85
+ if (props.debug) console.log("Deactivating section at edge:", activeSection.title);
86
+ nextTick(() => {
87
+ removeActive();
88
+ emit("section-change", { section: activeSection, sections: sections.value, active: false });
89
+ });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ if (props.debug) console.groupEnd();
95
+ };
96
+
97
+ let root = null;
98
+ if (props.observerOptions && props.observerOptions.root) {
99
+ root = props.observerOptions.root;
100
+ } else if (componentElRef.value) {
101
+ root = getScrollParent(componentElRef.value);
102
+ if (root === document.scrollingElement) {
103
+ root = null;
104
+ }
105
+ }
106
+
107
+ const finalObserverOptions = {
108
+ ...props.observerOptions,
109
+ root
110
+ };
111
+
112
+ observer = new IntersectionObserver(onObserve, finalObserverOptions);
113
+ }
114
+
115
+ function observeItems() {
116
+ if (!observer) return;
117
+ observer.disconnect();
118
+ sections.value.forEach(({ element }) => {
119
+ if (element) {
120
+ observer.observe(element);
121
+ }
122
+ });
123
+ }
124
+
125
+ function destroyObserver() {
126
+ if (observer) {
127
+ observer.disconnect();
128
+ observer = null;
129
+ }
130
+ }
131
+
132
+ onMounted(() => {
133
+ if (props.firstItemActive && sections.value.length > 0) {
134
+ const first = sections.value[0];
135
+ if (first) {
136
+ first.active = true;
137
+ }
138
+ }
139
+ createObserver();
140
+ observeItems();
141
+ });
142
+
143
+ onUnmounted(() => {
144
+ destroyObserver();
145
+ });
146
+
147
+ watch(() => sections.value.length, () => {
148
+ nextTick(() => {
149
+ observeItems();
150
+ });
151
+ });
152
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.1.3-beta.10",
3
+ "version": "0.1.3-beta.12",
4
4
  "description": "A modular and tree-shakeable Vue 3 component library for the Ulu frontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@floating-ui/vue": "^1.1.8",
68
- "@ulu/utils": "^0.0.30",
68
+ "@ulu/utils": "^0.0.32",
69
69
  "lodash-es": "^4.17.21"
70
70
  },
71
71
  "devDependencies": {
@@ -1,7 +0,0 @@
1
- /**
2
- * Symbol for register provide/inject
3
- */
4
- export const REGISTER: unique symbol;
5
- export const UNREGISTER: unique symbol;
6
- export const SECTIONS: unique symbol;
7
- //# sourceMappingURL=symbols.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../../../../lib/components/systems/scroll-anchors/symbols.js"],"names":[],"mappings":"AAAA;;GAEG;AACH,qCAAiC;AACjC,uCAAmC;AACnC,qCAAiC"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Symbol for register provide/inject
3
- */
4
- export const REGISTER = Symbol();
5
- export const UNREGISTER = Symbol();
6
- export const SECTIONS = Symbol();