@teselagen/ui 0.8.4 → 0.8.6
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/DataTable/utils/filterLocalEntitiesToHasura.d.ts +5 -0
- package/DataTable/utils/initializeHasuraWhereAndFilter.d.ts +2 -0
- package/DataTable/utils/tableQueryParamsToHasuraClauses.d.ts +26 -0
- package/autoTooltip.d.ts +1 -1
- package/index.cjs.js +1 -78
- package/index.es.js +1 -78
- package/package.json +1 -1
- package/src/DataTable/utils/filterLocalEntitiesToHasura.js +236 -0
- package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +587 -0
- package/src/DataTable/utils/initializeHasuraWhereAndFilter.js +26 -0
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +260 -0
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.test.js +206 -0
- package/src/autoTooltip.js +3 -115
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { camelCase, set } from "lodash-es";
|
|
2
|
+
|
|
3
|
+
export function tableQueryParamsToHasuraClauses({
|
|
4
|
+
page,
|
|
5
|
+
pageSize,
|
|
6
|
+
searchTerm,
|
|
7
|
+
filters,
|
|
8
|
+
order,
|
|
9
|
+
schema, // Add schema as a parameter
|
|
10
|
+
additionalFilter
|
|
11
|
+
}) {
|
|
12
|
+
const ccFields = getFieldsMappedByCCDisplayName(schema);
|
|
13
|
+
let where = {};
|
|
14
|
+
const order_by = {};
|
|
15
|
+
const limit = pageSize || 25;
|
|
16
|
+
const offset = page && pageSize ? (page - 1) * pageSize : 0;
|
|
17
|
+
|
|
18
|
+
if (searchTerm) {
|
|
19
|
+
const searchTermFilters = [];
|
|
20
|
+
schema.fields.forEach(field => {
|
|
21
|
+
const { type, path, searchDisabled } = field;
|
|
22
|
+
if (searchDisabled || field.filterDisabled || type === "color") return;
|
|
23
|
+
const filterValue = searchTerm; // No cleaning needed here, we're using _ilike
|
|
24
|
+
|
|
25
|
+
if (type === "string" || type === "lookup") {
|
|
26
|
+
const o = set({}, path, { _ilike: `%${filterValue}%` });
|
|
27
|
+
searchTermFilters.push(o);
|
|
28
|
+
} else if (type === "boolean") {
|
|
29
|
+
let regex;
|
|
30
|
+
try {
|
|
31
|
+
regex = new RegExp("^" + searchTerm, "ig");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
//ignore
|
|
34
|
+
}
|
|
35
|
+
if (regex) {
|
|
36
|
+
if ("true".replace(regex, "") !== "true") {
|
|
37
|
+
const o = set({}, path, { _eq: true });
|
|
38
|
+
searchTermFilters.push(o);
|
|
39
|
+
} else if ("false".replace(regex, "") !== "false") {
|
|
40
|
+
const o = set({}, path, { _eq: false });
|
|
41
|
+
searchTermFilters.push(o);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else if (
|
|
45
|
+
(type === "number" || type === "integer") &&
|
|
46
|
+
!isNaN(filterValue)
|
|
47
|
+
) {
|
|
48
|
+
const o = set({}, path, { _eq: parseFloat(filterValue) });
|
|
49
|
+
searchTermFilters.push(o);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (searchTermFilters.length > 0) {
|
|
53
|
+
if (Object.keys(where).length > 0) {
|
|
54
|
+
where = { _and: [where, { _or: searchTermFilters }] };
|
|
55
|
+
} else {
|
|
56
|
+
where = { _or: searchTermFilters };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (filters && filters.length > 0) {
|
|
62
|
+
const filterClauses = filters
|
|
63
|
+
.map(filter => {
|
|
64
|
+
let { selectedFilter, filterOn, filterValue } = filter;
|
|
65
|
+
const fieldSchema = ccFields[filterOn] || {};
|
|
66
|
+
|
|
67
|
+
const { path, reference, type } = fieldSchema;
|
|
68
|
+
let stringFilterValue =
|
|
69
|
+
filterValue && filterValue.toString
|
|
70
|
+
? filterValue.toString()
|
|
71
|
+
: filterValue;
|
|
72
|
+
if (stringFilterValue === false) {
|
|
73
|
+
// we still want to be able to search for the string "false" which will get parsed to false
|
|
74
|
+
stringFilterValue = "false";
|
|
75
|
+
} else {
|
|
76
|
+
stringFilterValue = stringFilterValue || "";
|
|
77
|
+
}
|
|
78
|
+
const arrayFilterValue = Array.isArray(filterValue)
|
|
79
|
+
? filterValue
|
|
80
|
+
: stringFilterValue.split(";");
|
|
81
|
+
|
|
82
|
+
if (type === "number" || type === "integer") {
|
|
83
|
+
filterValue = Array.isArray(filterValue)
|
|
84
|
+
? filterValue.map(val => Number(val))
|
|
85
|
+
: Number(filterValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (fieldSchema.normalizeFilter) {
|
|
89
|
+
filterValue = fieldSchema.normalizeFilter(
|
|
90
|
+
filterValue,
|
|
91
|
+
selectedFilter,
|
|
92
|
+
filterOn
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (reference) {
|
|
97
|
+
filterOn = reference.sourceField;
|
|
98
|
+
} else {
|
|
99
|
+
filterOn = path || filterOn;
|
|
100
|
+
}
|
|
101
|
+
switch (selectedFilter) {
|
|
102
|
+
case "none":
|
|
103
|
+
return {};
|
|
104
|
+
case "startsWith":
|
|
105
|
+
return { [filterOn]: { _ilike: `${filterValue}%` } };
|
|
106
|
+
case "endsWith":
|
|
107
|
+
return { [filterOn]: { _ilike: `%${filterValue}` } };
|
|
108
|
+
case "contains":
|
|
109
|
+
return { [filterOn]: { _ilike: `%${filterValue}%` } };
|
|
110
|
+
case "notContains":
|
|
111
|
+
return { [filterOn]: { _not_ilike: `%${filterValue}%` } };
|
|
112
|
+
case "isExactly":
|
|
113
|
+
return { [filterOn]: { _eq: filterValue } };
|
|
114
|
+
case "isEmpty":
|
|
115
|
+
return {
|
|
116
|
+
_or: [
|
|
117
|
+
{ [filterOn]: { _eq: "" } },
|
|
118
|
+
{ [filterOn]: { _is_null: true } }
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
case "notEmpty":
|
|
122
|
+
return {
|
|
123
|
+
_and: [
|
|
124
|
+
{ [filterOn]: { _neq: "" } },
|
|
125
|
+
{ [filterOn]: { _is_null: false } }
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
case "inList":
|
|
129
|
+
return { [filterOn]: { _in: filterValue } };
|
|
130
|
+
case "notInList":
|
|
131
|
+
return { [filterOn]: { _nin: filterValue } };
|
|
132
|
+
case "true":
|
|
133
|
+
return { [filterOn]: { _eq: true } };
|
|
134
|
+
case "false":
|
|
135
|
+
return { [filterOn]: { _eq: false } };
|
|
136
|
+
case "dateIs":
|
|
137
|
+
return { [filterOn]: { _eq: filterValue } };
|
|
138
|
+
case "notBetween":
|
|
139
|
+
return {
|
|
140
|
+
_or: [
|
|
141
|
+
{
|
|
142
|
+
[filterOn]: {
|
|
143
|
+
_lt: new Date(arrayFilterValue[0])
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
[filterOn]: {
|
|
148
|
+
_gt: new Date(
|
|
149
|
+
new Date(arrayFilterValue[1]).setHours(23, 59)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
case "isBetween":
|
|
156
|
+
return {
|
|
157
|
+
[filterOn]: {
|
|
158
|
+
_gte: new Date(arrayFilterValue[0]),
|
|
159
|
+
_lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
case "isBefore":
|
|
163
|
+
return { [filterOn]: { _lt: new Date(filterValue) } };
|
|
164
|
+
case "isAfter":
|
|
165
|
+
return { [filterOn]: { _gt: new Date(filterValue) } };
|
|
166
|
+
case "greaterThan":
|
|
167
|
+
return { [filterOn]: { _gt: parseFloat(filterValue) } };
|
|
168
|
+
case "lessThan":
|
|
169
|
+
return { [filterOn]: { _lt: parseFloat(filterValue) } };
|
|
170
|
+
case "inRange":
|
|
171
|
+
return {
|
|
172
|
+
[filterOn]: {
|
|
173
|
+
_gte: parseFloat(arrayFilterValue[0]),
|
|
174
|
+
_lte: parseFloat(arrayFilterValue[1])
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
case "outsideRange":
|
|
178
|
+
return {
|
|
179
|
+
_or: [
|
|
180
|
+
{
|
|
181
|
+
[filterOn]: {
|
|
182
|
+
_lt: parseFloat(arrayFilterValue[0])
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
[filterOn]: {
|
|
187
|
+
_gt: parseFloat(arrayFilterValue[1])
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
};
|
|
192
|
+
case "equalTo":
|
|
193
|
+
return {
|
|
194
|
+
[filterOn]: {
|
|
195
|
+
_eq:
|
|
196
|
+
type === "number" || type === "integer"
|
|
197
|
+
? parseFloat(filterValue)
|
|
198
|
+
: filterValue
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
case "regex":
|
|
202
|
+
return { [filterOn]: { _regex: filterValue } };
|
|
203
|
+
default:
|
|
204
|
+
console.warn(`Unsupported filter type: ${selectedFilter}`);
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
.map(filter => {
|
|
209
|
+
const o = {};
|
|
210
|
+
set(o, Object.keys(filter)[0], filter[Object.keys(filter)[0]]);
|
|
211
|
+
return o;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (filterClauses.length > 0) {
|
|
215
|
+
if (Object.keys(where).length > 0) {
|
|
216
|
+
where = { _and: [where, ...filterClauses] };
|
|
217
|
+
} else {
|
|
218
|
+
where = { _and: filterClauses };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (order && order.length > 0) {
|
|
224
|
+
order.forEach(item => {
|
|
225
|
+
const field = item.startsWith("-") ? item.substring(1) : item;
|
|
226
|
+
const direction = item.startsWith("-") ? "desc" : "asc";
|
|
227
|
+
order_by[field] = direction;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (additionalFilter) {
|
|
232
|
+
where = { _and: [where, additionalFilter] };
|
|
233
|
+
}
|
|
234
|
+
return { where, order_by, limit, offset };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Takes a schema and returns an object with the fields mapped by their camelCased display name.
|
|
239
|
+
* If the displayName is not set or is a jsx element, the path is used instead.
|
|
240
|
+
* The same conversion must be done when using the result of this method
|
|
241
|
+
*/
|
|
242
|
+
export function getFieldsMappedByCCDisplayName(schema) {
|
|
243
|
+
if (!schema || !schema.fields) return {};
|
|
244
|
+
return schema.fields.reduce((acc, field) => {
|
|
245
|
+
const ccDisplayName = getCCDisplayName(field);
|
|
246
|
+
acc[ccDisplayName] = field;
|
|
247
|
+
return acc;
|
|
248
|
+
}, {});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
*
|
|
253
|
+
* @param {object} field
|
|
254
|
+
* @returns the camelCase display name of the field, to be used for filters, sorting, etc
|
|
255
|
+
*/
|
|
256
|
+
export function getCCDisplayName(field) {
|
|
257
|
+
return camelCase(
|
|
258
|
+
typeof field.displayName === "string" ? field.displayName : field.path
|
|
259
|
+
);
|
|
260
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { tableQueryParamsToHasuraClauses } from "./tableQueryParamsToHasuraClauses";
|
|
2
|
+
|
|
3
|
+
describe("tableQueryParamsToHasuraClauses", () => {
|
|
4
|
+
const schema = {
|
|
5
|
+
fields: [
|
|
6
|
+
{ path: "name", type: "string" },
|
|
7
|
+
{ path: "age", type: "number" },
|
|
8
|
+
{ path: "isActive", type: "boolean" },
|
|
9
|
+
{ path: "email", type: "string" }
|
|
10
|
+
]
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it("should handle empty query params", () => {
|
|
14
|
+
const result = tableQueryParamsToHasuraClauses({});
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
where: {},
|
|
17
|
+
order_by: {},
|
|
18
|
+
limit: 25,
|
|
19
|
+
offset: 0
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should handle page and pageSize", () => {
|
|
24
|
+
const result = tableQueryParamsToHasuraClauses({ page: 2, pageSize: 10 });
|
|
25
|
+
expect(result).toEqual({
|
|
26
|
+
where: {},
|
|
27
|
+
order_by: {},
|
|
28
|
+
limit: 10,
|
|
29
|
+
offset: 10
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should handle searchTerm with string fields", () => {
|
|
34
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
35
|
+
searchTerm: "test",
|
|
36
|
+
schema
|
|
37
|
+
});
|
|
38
|
+
expect(result).toEqual({
|
|
39
|
+
where: {
|
|
40
|
+
_or: [{ name: { _ilike: "%test%" } }, { email: { _ilike: "%test%" } }]
|
|
41
|
+
},
|
|
42
|
+
order_by: {},
|
|
43
|
+
limit: 25,
|
|
44
|
+
offset: 0
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle searchTerm with number fields", () => {
|
|
49
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
50
|
+
searchTerm: "30",
|
|
51
|
+
schema
|
|
52
|
+
});
|
|
53
|
+
expect(result).toEqual({
|
|
54
|
+
where: {
|
|
55
|
+
_or: [
|
|
56
|
+
{ name: { _ilike: "%30%" } },
|
|
57
|
+
{ age: { _eq: 30 } },
|
|
58
|
+
{ email: { _ilike: "%30%" } }
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
order_by: {},
|
|
62
|
+
limit: 25,
|
|
63
|
+
offset: 0
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should handle searchTerm with boolean fields", () => {
|
|
68
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
69
|
+
searchTerm: "true",
|
|
70
|
+
schema
|
|
71
|
+
});
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
where: {
|
|
74
|
+
_or: [
|
|
75
|
+
{ name: { _ilike: "%true%" } },
|
|
76
|
+
{ isActive: { _eq: true } },
|
|
77
|
+
{ email: { _ilike: "%true%" } }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
order_by: {},
|
|
81
|
+
limit: 25,
|
|
82
|
+
offset: 0
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle searchTerm with multiple field types", () => {
|
|
87
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
88
|
+
searchTerm: "test",
|
|
89
|
+
schema
|
|
90
|
+
});
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
where: {
|
|
93
|
+
_or: [{ name: { _ilike: "%test%" } }, { email: { _ilike: "%test%" } }]
|
|
94
|
+
},
|
|
95
|
+
order_by: {},
|
|
96
|
+
limit: 25,
|
|
97
|
+
offset: 0
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle contains filter", () => {
|
|
102
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
103
|
+
filters: [
|
|
104
|
+
{
|
|
105
|
+
selectedFilter: "contains",
|
|
106
|
+
filterOn: "name",
|
|
107
|
+
filterValue: "test"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
expect(result).toEqual({
|
|
112
|
+
where: { _and: [{ name: { _ilike: "%test%" } }] },
|
|
113
|
+
order_by: {},
|
|
114
|
+
limit: 25,
|
|
115
|
+
offset: 0
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle equalTo filter for number", () => {
|
|
120
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
121
|
+
filters: [
|
|
122
|
+
{ selectedFilter: "equalTo", filterOn: "age", filterValue: "30" }
|
|
123
|
+
],
|
|
124
|
+
schema
|
|
125
|
+
});
|
|
126
|
+
expect(result).toEqual({
|
|
127
|
+
where: { _and: [{ age: { _eq: 30 } }] },
|
|
128
|
+
order_by: {},
|
|
129
|
+
limit: 25,
|
|
130
|
+
offset: 0
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should handle order", () => {
|
|
135
|
+
const result = tableQueryParamsToHasuraClauses({ order: ["name", "-age"] });
|
|
136
|
+
expect(result).toEqual({
|
|
137
|
+
where: {},
|
|
138
|
+
order_by: { name: "asc", age: "desc" },
|
|
139
|
+
limit: 25,
|
|
140
|
+
offset: 0
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should combine all params", () => {
|
|
145
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
146
|
+
page: 2,
|
|
147
|
+
pageSize: 10,
|
|
148
|
+
searchTerm: "test",
|
|
149
|
+
filters: [
|
|
150
|
+
{
|
|
151
|
+
selectedFilter: "greaterThan",
|
|
152
|
+
filterOn: "age",
|
|
153
|
+
filterValue: "30"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
order: ["name"],
|
|
157
|
+
schema
|
|
158
|
+
});
|
|
159
|
+
expect(result).toEqual({
|
|
160
|
+
where: {
|
|
161
|
+
_and: [
|
|
162
|
+
{
|
|
163
|
+
_or: [
|
|
164
|
+
{ name: { _ilike: "%test%" } },
|
|
165
|
+
{ email: { _ilike: "%test%" } }
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
{ age: { _gt: 30 } }
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
order_by: { name: "asc" },
|
|
172
|
+
limit: 10,
|
|
173
|
+
offset: 10
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should combine searchTerm and filters", () => {
|
|
178
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
179
|
+
searchTerm: "test",
|
|
180
|
+
filters: [
|
|
181
|
+
{
|
|
182
|
+
selectedFilter: "greaterThan",
|
|
183
|
+
filterOn: "age",
|
|
184
|
+
filterValue: "30"
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
schema
|
|
188
|
+
});
|
|
189
|
+
expect(result).toEqual({
|
|
190
|
+
where: {
|
|
191
|
+
_and: [
|
|
192
|
+
{
|
|
193
|
+
_or: [
|
|
194
|
+
{ name: { _ilike: "%test%" } },
|
|
195
|
+
{ email: { _ilike: "%test%" } }
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{ age: { _gt: 30 } }
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
order_by: {},
|
|
202
|
+
limit: 25,
|
|
203
|
+
offset: 0
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
package/src/autoTooltip.js
CHANGED
|
@@ -17,125 +17,13 @@ document.addEventListener("mouseup", () => {
|
|
|
17
17
|
isDragging = false;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
// Track elements that had their tooltip attributes moved to parent
|
|
21
|
-
const processedDisabledElements = new WeakMap();
|
|
22
|
-
|
|
23
|
-
// Move tooltip attributes from disabled elements to their parent
|
|
24
|
-
function moveTooltipToParent(element) {
|
|
25
|
-
// Check if element already processed
|
|
26
|
-
if (processedDisabledElements.has(element)) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check if the element is disabled and has tooltip attributes
|
|
31
|
-
const isDisabled = element.disabled === true || element.getAttribute("disabled") !== null;
|
|
32
|
-
const hasTipData = element.getAttribute("data-tip") ||
|
|
33
|
-
element.getAttribute("data-title") ||
|
|
34
|
-
(element.offsetWidth < element.scrollWidth && element.textContent?.trim().length > 0);
|
|
35
|
-
|
|
36
|
-
if (!isDisabled || !hasTipData) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const parent = element.parentElement;
|
|
41
|
-
if (!parent) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Copy tooltip-relevant attributes to the parent
|
|
46
|
-
const tooltipAttrs = ["data-tip", "data-title", "data-avoid", "data-avoid-backup"];
|
|
47
|
-
let attrsMoved = false;
|
|
48
|
-
const movedAttrs = []; // Track which attributes were moved
|
|
49
|
-
|
|
50
|
-
tooltipAttrs.forEach(attr => {
|
|
51
|
-
const value = element.getAttribute(attr);
|
|
52
|
-
if (value) {
|
|
53
|
-
// Add a data attribute to the parent only if it doesn't already have one
|
|
54
|
-
if (!parent.hasAttribute(attr)) {
|
|
55
|
-
parent.setAttribute(attr, value);
|
|
56
|
-
movedAttrs.push(attr); // Record this attribute was moved
|
|
57
|
-
attrsMoved = true;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// If element is ellipsized, add its text content as a data-tip to parent
|
|
63
|
-
if (element.offsetWidth < element.scrollWidth && element.textContent?.trim().length > 0) {
|
|
64
|
-
if (!parent.hasAttribute("data-tip")) {
|
|
65
|
-
parent.setAttribute("data-tip", element.textContent);
|
|
66
|
-
movedAttrs.push("data-tip"); // Record this attribute was moved
|
|
67
|
-
attrsMoved = true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Store information about moved attributes
|
|
72
|
-
if (attrsMoved) {
|
|
73
|
-
processedDisabledElements.set(element, {
|
|
74
|
-
parent,
|
|
75
|
-
movedAttrs
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Function to clear tooltips from parent elements
|
|
81
|
-
function clearParentTooltips(element) {
|
|
82
|
-
if (!processedDisabledElements.has(element)) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { parent, movedAttrs } = processedDisabledElements.get(element);
|
|
87
|
-
if (parent && movedAttrs) {
|
|
88
|
-
// Remove all attributes that were added to the parent
|
|
89
|
-
movedAttrs.forEach(attr => {
|
|
90
|
-
parent.removeAttribute(attr);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Remove the element from our tracking map
|
|
94
|
-
processedDisabledElements.delete(element);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Function to scan for and process disabled elements
|
|
99
|
-
function scanForDisabledElements() {
|
|
100
|
-
// First, check if any previously disabled elements are now enabled and clear their parent tooltips
|
|
101
|
-
processedDisabledElements.forEach((value, element) => {
|
|
102
|
-
const isStillDisabled = element.disabled === true || element.getAttribute("disabled") !== null;
|
|
103
|
-
const isConnected = element.isConnected;
|
|
104
|
-
|
|
105
|
-
if (!isStillDisabled || !isConnected) {
|
|
106
|
-
clearParentTooltips(element);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Then process currently disabled elements
|
|
111
|
-
document.querySelectorAll("[disabled][data-tip], [disabled][data-title], button[disabled], input[disabled]").forEach(el => {
|
|
112
|
-
moveTooltipToParent(el);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Initialize on load and periodically check for new disabled elements
|
|
117
|
-
window.addEventListener('DOMContentLoaded', scanForDisabledElements);
|
|
118
|
-
setInterval(scanForDisabledElements, 2000);
|
|
119
|
-
|
|
120
20
|
let tippys = [];
|
|
121
21
|
let recentlyHidden = false;
|
|
122
22
|
let clearMe;
|
|
123
23
|
(function () {
|
|
124
24
|
let lastMouseOverElement = null;
|
|
125
25
|
document.addEventListener("mouseover", function (event) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Special handling for disabled elements - we need to process their parent
|
|
129
|
-
if (element instanceof Element &&
|
|
130
|
-
(element.disabled === true || element.getAttribute("disabled") !== null)) {
|
|
131
|
-
// If this is a disabled element, we want to also process its parent
|
|
132
|
-
// since that's where we moved the tooltip attributes
|
|
133
|
-
const parent = element.parentElement;
|
|
134
|
-
if (parent && processedDisabledElements.has(element)) {
|
|
135
|
-
// Only process the parent if we've previously moved attributes to it
|
|
136
|
-
element = parent;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
26
|
+
const element = event.target;
|
|
139
27
|
|
|
140
28
|
if (element instanceof Element && element !== lastMouseOverElement) {
|
|
141
29
|
lastMouseOverElement = element;
|
|
@@ -218,7 +106,7 @@ let clearMe;
|
|
|
218
106
|
|
|
219
107
|
if (!customBoundary) return;
|
|
220
108
|
const a = customBoundary.getBoundingClientRect();
|
|
221
|
-
|
|
109
|
+
|
|
222
110
|
if (a.top < state.rects.reference.y) {
|
|
223
111
|
const b = Math.abs(
|
|
224
112
|
Math.abs(a.top - state.rects.reference.y) - 10
|
|
@@ -312,4 +200,4 @@ function parentIncludesNoChildDataTip(el, count) {
|
|
|
312
200
|
}
|
|
313
201
|
|
|
314
202
|
// Export the function to clear parent tooltips so it can be used elsewhere
|
|
315
|
-
export { clearParentTooltips };
|
|
203
|
+
// export { clearParentTooltips };
|