@acusti/dropdown 0.46.0 → 0.47.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/src/helpers.ts DELETED
@@ -1,179 +0,0 @@
1
- import { getBestMatch } from '@acusti/matchmaking';
2
-
3
- import { BODY_SELECTOR } from './styles.js';
4
-
5
- export const ITEM_SELECTOR = `[data-ukt-item], [data-ukt-value]`;
6
-
7
- export const getItemElements = (dropdownElement: HTMLElement | null) => {
8
- if (!dropdownElement) return null;
9
-
10
- const bodyElement = dropdownElement.querySelector(BODY_SELECTOR);
11
- if (!bodyElement) return null;
12
-
13
- let items: HTMLCollection | NodeListOf<Element> =
14
- bodyElement.querySelectorAll(ITEM_SELECTOR);
15
-
16
- if (items.length) return items;
17
- // If no items found via [data-ukt-item] or [data-ukt-value] selector,
18
- // use first instance of multiple children found
19
- items = bodyElement.children;
20
- while (items.length === 1) {
21
- if (items[0].children == null) break;
22
- items = items[0].children;
23
- }
24
- // If unable to find an element with more than one child, treat direct child as items
25
- if (items.length === 1) {
26
- items = bodyElement.children;
27
- }
28
- return items;
29
- };
30
-
31
- export const getActiveItemElement = (dropdownElement: HTMLElement | null) => {
32
- if (!dropdownElement) return null;
33
- return dropdownElement.querySelector('[data-ukt-active]') as HTMLElement | null;
34
- };
35
-
36
- const clearItemElementsState = (itemElements: Array<HTMLElement>) => {
37
- itemElements.forEach((itemElement) => {
38
- if (itemElement.hasAttribute('data-ukt-active')) {
39
- delete itemElement.dataset.uktActive;
40
- }
41
- });
42
- };
43
-
44
- export const setActiveItem = ({
45
- dropdownElement,
46
- element,
47
- index,
48
- indexAddend,
49
- isExactMatch,
50
- text,
51
- }:
52
- | {
53
- dropdownElement: HTMLElement;
54
- element: HTMLElement;
55
- index?: null;
56
- indexAddend?: null;
57
- isExactMatch?: null;
58
- text?: null;
59
- }
60
- | {
61
- dropdownElement: HTMLElement;
62
- element?: null;
63
- index: number;
64
- indexAddend?: null;
65
- isExactMatch?: null;
66
- text?: null;
67
- }
68
- | {
69
- dropdownElement: HTMLElement;
70
- element?: null;
71
- index?: null;
72
- indexAddend: number;
73
- isExactMatch?: null;
74
- text?: null;
75
- }
76
- | {
77
- dropdownElement: HTMLElement;
78
- element?: null;
79
- index?: null;
80
- indexAddend?: null;
81
- isExactMatch?: boolean;
82
- text: string;
83
- }) => {
84
- const items = getItemElements(dropdownElement);
85
- if (!items) return;
86
-
87
- const itemElements = Array.from(items) as Array<HTMLElement>;
88
- if (!itemElements.length) return;
89
-
90
- const lastIndex = itemElements.length - 1;
91
- const currentActiveIndex = itemElements.findIndex((itemElement) =>
92
- itemElement.hasAttribute('data-ukt-active'),
93
- );
94
-
95
- let nextActiveIndex = currentActiveIndex;
96
- if (typeof index === 'number') {
97
- // Negative index means count back from the end
98
- nextActiveIndex = index < 0 ? itemElements.length + index : index;
99
- }
100
-
101
- if (element) {
102
- nextActiveIndex = itemElements.findIndex(
103
- (itemElement) => itemElement === element,
104
- );
105
- } else if (typeof indexAddend === 'number') {
106
- // If there’s no currentActiveIndex and we are handling -1, start at lastIndex
107
- if (currentActiveIndex === -1 && indexAddend === -1) {
108
- nextActiveIndex = lastIndex;
109
- } else {
110
- nextActiveIndex += indexAddend;
111
- }
112
- // Keep it within the bounds of the items list
113
- if (nextActiveIndex < 0) {
114
- nextActiveIndex = 0;
115
- } else if (nextActiveIndex > lastIndex) {
116
- nextActiveIndex = lastIndex;
117
- }
118
- } else if (typeof text === 'string') {
119
- // If text is empty, clear existing active items and early return
120
- if (!text) {
121
- clearItemElementsState(itemElements);
122
- return;
123
- }
124
-
125
- const itemTexts = itemElements.map((itemElement) => itemElement.innerText);
126
- if (isExactMatch) {
127
- const textToCompare = text.toLowerCase();
128
- nextActiveIndex = itemTexts.findIndex((itemText) =>
129
- itemText.toLowerCase().startsWith(textToCompare),
130
- );
131
- // If isExactMatch is required and no exact match was found, clear active items
132
- if (nextActiveIndex === -1) {
133
- clearItemElementsState(itemElements);
134
- }
135
- } else {
136
- const bestMatch = getBestMatch({ items: itemTexts, text });
137
- nextActiveIndex = itemTexts.findIndex((itemText) => itemText === bestMatch);
138
- }
139
- }
140
-
141
- if (nextActiveIndex === -1 || nextActiveIndex === currentActiveIndex) return;
142
-
143
- // Clear any existing active dropdown body item state
144
- clearItemElementsState(itemElements);
145
-
146
- const nextActiveItem = items[nextActiveIndex];
147
- if (nextActiveItem != null) {
148
- nextActiveItem.setAttribute('data-ukt-active', '');
149
- // Find closest scrollable parent and ensure that next active item is visible
150
- let { parentElement } = nextActiveItem;
151
- let scrollableParent = null;
152
- while (!scrollableParent && parentElement && parentElement !== dropdownElement) {
153
- const isScrollable =
154
- parentElement.scrollHeight > parentElement.clientHeight + 15;
155
- if (isScrollable) {
156
- scrollableParent = parentElement;
157
- } else {
158
- parentElement = parentElement.parentElement;
159
- }
160
- }
161
-
162
- if (scrollableParent) {
163
- const parentRect = scrollableParent.getBoundingClientRect();
164
- const itemRect = nextActiveItem.getBoundingClientRect();
165
- const isAboveTop = itemRect.top < parentRect.top;
166
- const isBelowBottom = itemRect.bottom > parentRect.bottom;
167
- if (isAboveTop || isBelowBottom) {
168
- let { scrollTop } = scrollableParent;
169
- // Item isn’t fully visible; adjust scrollTop to put item within closest edge
170
- if (isAboveTop) {
171
- scrollTop -= parentRect.top - itemRect.top;
172
- } else {
173
- scrollTop += itemRect.bottom - parentRect.bottom;
174
- }
175
- scrollableParent.scrollTop = scrollTop;
176
- }
177
- }
178
- }
179
- };
package/src/styles.ts DELETED
@@ -1,90 +0,0 @@
1
- import { SYSTEM_UI_FONT } from '@acusti/styling';
2
-
3
- export const ROOT_CLASS_NAME = 'uktdropdown';
4
- export const ROOT_SELECTOR = `.${ROOT_CLASS_NAME}`;
5
-
6
- export const BODY_CLASS_NAME = `${ROOT_CLASS_NAME}-body`;
7
- export const LABEL_CLASS_NAME = `${ROOT_CLASS_NAME}-label`;
8
- export const LABEL_TEXT_CLASS_NAME = `${ROOT_CLASS_NAME}-label-text`;
9
- export const TRIGGER_CLASS_NAME = `${ROOT_CLASS_NAME}-trigger`;
10
-
11
- export const BODY_SELECTOR = `.${BODY_CLASS_NAME}`;
12
- export const LABEL_SELECTOR = `.${LABEL_CLASS_NAME}`;
13
- export const LABEL_TEXT_SELECTOR = `.${LABEL_TEXT_CLASS_NAME}`;
14
- export const TRIGGER_SELECTOR = `.${TRIGGER_CLASS_NAME}`;
15
-
16
- export const BODY_MAX_HEIGHT_VAR = '--uktdd-body-max-height';
17
- export const BODY_MAX_WIDTH_VAR = '--uktdd-body-max-width';
18
-
19
- export const STYLES = `
20
- :root {
21
- --uktdd-font-family: ${SYSTEM_UI_FONT};
22
- --uktdd-body-bg-color: #fff;
23
- --uktdd-body-bg-color-hover: rgb(105,162,249);
24
- --uktdd-body-color-hover: #fff;
25
- --uktdd-body-buffer: 10px;
26
- ${BODY_MAX_HEIGHT_VAR}: calc(100vh - var(--uktdd-body-buffer));
27
- ${BODY_MAX_WIDTH_VAR}: calc(100vw - var(--uktdd-body-buffer));
28
- --uktdd-body-pad-bottom: 9px;
29
- --uktdd-body-pad-left: 12px;
30
- --uktdd-body-pad-right: 12px;
31
- --uktdd-body-pad-top: 9px;
32
- --uktdd-label-pad-right: 10px;
33
- }
34
- ${ROOT_SELECTOR},
35
- ${TRIGGER_SELECTOR} {
36
- font-family: var(--uktdd-font-family);
37
- }
38
- ${ROOT_SELECTOR} {
39
- position: relative;
40
- display: inline-block;
41
- }
42
- ${ROOT_SELECTOR}.disabled {
43
- pointer-events: none;
44
- }
45
- ${ROOT_SELECTOR} > * {
46
- cursor: default;
47
- }
48
- ${LABEL_SELECTOR} {
49
- display: flex;
50
- }
51
- ${LABEL_TEXT_SELECTOR} {
52
- padding-right: var(--uktdd-label-pad-right);
53
- }
54
- ${BODY_SELECTOR} {
55
- box-sizing: border-box;
56
- position: absolute;
57
- top: 100%;
58
- max-height: var(${BODY_MAX_HEIGHT_VAR});
59
- min-height: 50px;
60
- max-width: var(${BODY_MAX_WIDTH_VAR});
61
- min-width: 100%;
62
- overflow: auto;
63
- z-index: 2;
64
- padding: var(--uktdd-body-pad-top) var(--uktdd-body-pad-right) var(--uktdd-body-pad-bottom) var(--uktdd-body-pad-left);
65
- background-color: var(--uktdd-body-bg-color);
66
- box-shadow: 0 8px 18px rgba(0,0,0,0.25);
67
- }
68
- ${BODY_SELECTOR}.calculating-position {
69
- visibility: hidden;
70
- }
71
- ${BODY_SELECTOR}.out-of-bounds-bottom:not(.out-of-bounds-top) {
72
- top: auto;
73
- bottom: 100%;
74
- }
75
- ${BODY_SELECTOR}.out-of-bounds-right:not(.out-of-bounds-left) {
76
- left: auto;
77
- right: 0px;
78
- }
79
- ${LABEL_SELECTOR} + ${BODY_SELECTOR} {
80
- left: auto;
81
- right: 0;
82
- }
83
- ${BODY_SELECTOR}.has-items {
84
- user-select: none;
85
- }
86
- ${BODY_SELECTOR} [data-ukt-active] {
87
- background-color: var(--uktdd-body-bg-color-hover);
88
- color: var(--uktdd-body-color-hover);
89
- }
90
- `;