@fluid-topics/ft-wc-utils 2.0.14 → 2.0.16
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/build/ClusteringHelper.d.ts +1 -0
- package/build/ClusteringHelper.js +5 -1
- package/build/FtLitElement.js +1 -2
- package/build/SearchPlaceConverter.d.ts +2 -0
- package/build/SearchPlaceConverter.js +23 -1
- package/build/directives/focusTrap.d.ts +44 -0
- package/build/directives/focusTrap.js +169 -0
- package/build/directives/index.d.ts +1 -0
- package/build/directives/index.js +1 -0
- package/build/globals.min.js +16 -16
- package/build/helpers.d.ts +1 -0
- package/build/helpers.js +19 -1
- package/package.json +3 -3
|
@@ -24,6 +24,7 @@ export declare class ClusteringHelper {
|
|
|
24
24
|
private clusteringMetadata;
|
|
25
25
|
private metadataDescriptors;
|
|
26
26
|
constructor(allResults: Array<FtSearchResultClusterEntry>, selectedResult: FtSearchResultClusterEntry, clusteringMetadata: Set<string>, metadataDescriptors: Array<FtMetadataDescriptor>);
|
|
27
|
+
static findResultWithMetadataValue(results: FtSearchResultClusterEntry[], metadataKey: string, metadataValue: string): FtSearchResultClusterEntry | undefined;
|
|
27
28
|
static extractResultMetadata(options: Omit<ExtractResultMetadataOptions, "key">): FlatMetadata[];
|
|
28
29
|
static extractResultMetadata(options: ExtractResultMetadataOptions): Optional<FlatMetadata>;
|
|
29
30
|
static flattenMetadata(metadata: Optional<FtMetadata>[], hierarchicalValueTruncatingLevel?: number, dateFormatter?: ((value: string) => string)): FlatMetadata[];
|
|
@@ -12,8 +12,12 @@ export class ClusteringHelper {
|
|
|
12
12
|
this.clusteringMetadata = clusteringMetadata;
|
|
13
13
|
this.metadataDescriptors = metadataDescriptors;
|
|
14
14
|
}
|
|
15
|
+
static findResultWithMetadataValue(results, metadataKey, metadataValue) {
|
|
16
|
+
return results === null || results === void 0 ? void 0 : results.find((result) => { var _a; return ((_a = this.extractResultMetadata({ result }).find((meta) => meta.key == metadataKey)) === null || _a === void 0 ? void 0 : _a.value) == metadataValue; });
|
|
17
|
+
}
|
|
15
18
|
static extractResultMetadata(options) {
|
|
16
|
-
|
|
19
|
+
var _a, _b;
|
|
20
|
+
const metadata = (_b = (_a = accessResult(options.result)) === null || _a === void 0 ? void 0 : _a.metadata) !== null && _b !== void 0 ? _b : [];
|
|
17
21
|
if (options.key == null) {
|
|
18
22
|
return ClusteringHelper.flattenMetadata(metadata, options.hierarchicalValueTruncatingLevel, options.dateFormatter);
|
|
19
23
|
}
|
package/build/FtLitElement.js
CHANGED
|
@@ -14,13 +14,12 @@ const constructorPrototype = Symbol("constructorPrototype");
|
|
|
14
14
|
const constructorName = Symbol("constructorName");
|
|
15
15
|
const exportpartsDebouncer = Symbol("exportpartsDebouncer");
|
|
16
16
|
const dynamicDependenciesLoaded = Symbol("dynamicDependenciesLoaded");
|
|
17
|
-
class ExportpartsUpdatedEvent extends CustomEvent {
|
|
17
|
+
export class ExportpartsUpdatedEvent extends CustomEvent {
|
|
18
18
|
constructor() {
|
|
19
19
|
super(ExportpartsUpdatedEvent.eventName, { bubbles: true });
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
ExportpartsUpdatedEvent.eventName = "exportparts-updated";
|
|
23
|
-
export { ExportpartsUpdatedEvent };
|
|
24
23
|
export class FtLitElement extends ScopedRegistryLitElement {
|
|
25
24
|
constructor() {
|
|
26
25
|
super();
|
|
@@ -42,4 +42,6 @@ export declare class SearchPlaceConverter {
|
|
|
42
42
|
private superEscapeFilters;
|
|
43
43
|
private unescapeFilterValue;
|
|
44
44
|
private parseLegacyPeriodFilter;
|
|
45
|
+
static clearSearchParamsInUrl(href: string): string;
|
|
46
|
+
static updateLocaleParamInUrl(href: string, locale?: string): string;
|
|
45
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FtDateFilterType, FtSearchKeywordMatch, isFtDateFilterType, isFtVirtualField } from "@fluid-topics/public-api";
|
|
1
|
+
import { FtDateFilterType, FtSearchKeywordMatch, isFtDateFilterType, isFtVirtualField, } from "@fluid-topics/public-api";
|
|
2
2
|
import { minmax } from "./helpers";
|
|
3
3
|
function quote(value) {
|
|
4
4
|
return `"${value}"`;
|
|
@@ -285,4 +285,26 @@ export class SearchPlaceConverter {
|
|
|
285
285
|
}
|
|
286
286
|
return [];
|
|
287
287
|
}
|
|
288
|
+
static clearSearchParamsInUrl(href) {
|
|
289
|
+
const url = new URL(href);
|
|
290
|
+
const paramsToClear = [
|
|
291
|
+
SearchPlaceQueryParams.LOCALE,
|
|
292
|
+
SearchPlaceQueryParams.FILTERS,
|
|
293
|
+
SearchPlaceQueryParams.VALUE_FILTERS,
|
|
294
|
+
SearchPlaceQueryParams.DATE_FILTERS,
|
|
295
|
+
SearchPlaceQueryParams.RANGE_FILTERS,
|
|
296
|
+
];
|
|
297
|
+
paramsToClear.forEach((param) => {
|
|
298
|
+
url.searchParams.delete(param);
|
|
299
|
+
});
|
|
300
|
+
return url.toString();
|
|
301
|
+
}
|
|
302
|
+
static updateLocaleParamInUrl(href, locale) {
|
|
303
|
+
const url = new URL(href);
|
|
304
|
+
url.searchParams.delete(SearchPlaceQueryParams.LOCALE);
|
|
305
|
+
if (locale !== undefined) {
|
|
306
|
+
url.searchParams.append(SearchPlaceQueryParams.LOCALE, locale);
|
|
307
|
+
}
|
|
308
|
+
return url.toString();
|
|
309
|
+
}
|
|
288
310
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ElementPart } from "lit";
|
|
2
|
+
import { AsyncDirective, PartInfo } from "lit/async-directive.js";
|
|
3
|
+
/**
|
|
4
|
+
* This directive will trap the focus inside the element it's attached to, by creating 2 invisible elements at the top and bottom of it.
|
|
5
|
+
* Once the focus is trapped, it will circle from top to bottom.
|
|
6
|
+
* This accessibility behaviour can be used in modals and other popover elements, but there should always be a mean to quit the trapping element
|
|
7
|
+
* (for example by pressing Escape) which should be handled separately from this directive.
|
|
8
|
+
*
|
|
9
|
+
* Use directly on the container you want to trap the focus inside.
|
|
10
|
+
* <pre><div id="my-container" ${ focusTrap() }>
|
|
11
|
+
* <!-- elements to cycle on... -->
|
|
12
|
+
* <div></pre>
|
|
13
|
+
*/
|
|
14
|
+
export declare class FocusTrapDirective extends AsyncDirective {
|
|
15
|
+
private firstTrap?;
|
|
16
|
+
private lastTrap?;
|
|
17
|
+
private part?;
|
|
18
|
+
private get element();
|
|
19
|
+
private readonly onFirstTrapFocus;
|
|
20
|
+
private readonly onLastTrapFocus;
|
|
21
|
+
constructor(partInfo: PartInfo);
|
|
22
|
+
render(): symbol;
|
|
23
|
+
update(part: ElementPart): symbol;
|
|
24
|
+
reconnected(): void;
|
|
25
|
+
disconnected(): void;
|
|
26
|
+
private isElementTrappable;
|
|
27
|
+
private createFocusTrap;
|
|
28
|
+
/**
|
|
29
|
+
* Creates an invisible focusable element
|
|
30
|
+
*/
|
|
31
|
+
private createTrap;
|
|
32
|
+
private getChildren;
|
|
33
|
+
private focusFirstDescendant;
|
|
34
|
+
private focusLastDescendant;
|
|
35
|
+
private updateCompleteIfNecessary;
|
|
36
|
+
private isInsideElement;
|
|
37
|
+
/**
|
|
38
|
+
* Try to focus an element if focusable and return true, otherwise return false
|
|
39
|
+
*/
|
|
40
|
+
private attemptFocus;
|
|
41
|
+
private isFocusable;
|
|
42
|
+
private isElementDisabled;
|
|
43
|
+
}
|
|
44
|
+
export declare const focusTrap: () => import("lit-html/directive").DirectiveResult<typeof FocusTrapDirective>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { noChange, } from "lit";
|
|
2
|
+
import { AsyncDirective, directive, } from "lit/async-directive.js";
|
|
3
|
+
import { ReactiveElement } from "lit-element";
|
|
4
|
+
/**
|
|
5
|
+
* This directive will trap the focus inside the element it's attached to, by creating 2 invisible elements at the top and bottom of it.
|
|
6
|
+
* Once the focus is trapped, it will circle from top to bottom.
|
|
7
|
+
* This accessibility behaviour can be used in modals and other popover elements, but there should always be a mean to quit the trapping element
|
|
8
|
+
* (for example by pressing Escape) which should be handled separately from this directive.
|
|
9
|
+
*
|
|
10
|
+
* Use directly on the container you want to trap the focus inside.
|
|
11
|
+
* <pre><div id="my-container" ${ focusTrap() }>
|
|
12
|
+
* <!-- elements to cycle on... -->
|
|
13
|
+
* <div></pre>
|
|
14
|
+
*/
|
|
15
|
+
export class FocusTrapDirective extends AsyncDirective {
|
|
16
|
+
get element() {
|
|
17
|
+
var _a;
|
|
18
|
+
return (_a = this.part) === null || _a === void 0 ? void 0 : _a.element;
|
|
19
|
+
}
|
|
20
|
+
constructor(partInfo) {
|
|
21
|
+
super(partInfo);
|
|
22
|
+
this.onFirstTrapFocus = async (event) => {
|
|
23
|
+
if (!this.element || !event.relatedTarget) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const previousFocusedElement = event.relatedTarget; // In case of a focus event, should always be an Element
|
|
27
|
+
const isPreviousFocusInsideOfTrap = this.isInsideElement(previousFocusedElement, this.element);
|
|
28
|
+
if (isPreviousFocusInsideOfTrap) {
|
|
29
|
+
await this.focusLastDescendant(this.element);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
await this.focusFirstDescendant(this.element);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
this.onLastTrapFocus = async (event) => {
|
|
36
|
+
if (!this.element || !event.relatedTarget) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const previousFocusedElement = event.relatedTarget; // In case of a focus event, should always be an Element
|
|
40
|
+
const isPreviousFocusInsideOfTrap = this.isInsideElement(previousFocusedElement, this.element);
|
|
41
|
+
if (isPreviousFocusInsideOfTrap) {
|
|
42
|
+
await this.focusFirstDescendant(this.element);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await this.focusLastDescendant(this.element);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
render() {
|
|
50
|
+
return noChange;
|
|
51
|
+
}
|
|
52
|
+
update(part) {
|
|
53
|
+
this.part = part;
|
|
54
|
+
if (this.isElementTrappable(part.element)) {
|
|
55
|
+
this.createFocusTrap(part.element);
|
|
56
|
+
}
|
|
57
|
+
return noChange;
|
|
58
|
+
}
|
|
59
|
+
reconnected() {
|
|
60
|
+
var _a;
|
|
61
|
+
if (this.isElementTrappable((_a = this.part) === null || _a === void 0 ? void 0 : _a.element)) {
|
|
62
|
+
this.createFocusTrap(this.part.element);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
disconnected() {
|
|
66
|
+
var _a, _b, _c, _d;
|
|
67
|
+
(_a = this.firstTrap) === null || _a === void 0 ? void 0 : _a.removeEventListener("focus", this.onFirstTrapFocus);
|
|
68
|
+
(_b = this.lastTrap) === null || _b === void 0 ? void 0 : _b.removeEventListener("focus", this.onLastTrapFocus);
|
|
69
|
+
(_c = this.firstTrap) === null || _c === void 0 ? void 0 : _c.remove();
|
|
70
|
+
(_d = this.lastTrap) === null || _d === void 0 ? void 0 : _d.remove();
|
|
71
|
+
this.firstTrap = undefined;
|
|
72
|
+
this.lastTrap = undefined;
|
|
73
|
+
}
|
|
74
|
+
isElementTrappable(element) {
|
|
75
|
+
return !!element && (element instanceof HTMLElement) && element.childElementCount > 0;
|
|
76
|
+
}
|
|
77
|
+
createFocusTrap(element) {
|
|
78
|
+
this.firstTrap = this.createTrap();
|
|
79
|
+
this.lastTrap = this.createTrap();
|
|
80
|
+
element.insertBefore(this.firstTrap, element.firstChild);
|
|
81
|
+
element.appendChild(this.lastTrap);
|
|
82
|
+
this.lastTrap.addEventListener("focus", this.onLastTrapFocus);
|
|
83
|
+
this.firstTrap.addEventListener("focus", this.onFirstTrapFocus);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Creates an invisible focusable element
|
|
87
|
+
*/
|
|
88
|
+
createTrap() {
|
|
89
|
+
const trapElement = document.createElement("div");
|
|
90
|
+
trapElement.tabIndex = 0;
|
|
91
|
+
return trapElement;
|
|
92
|
+
}
|
|
93
|
+
getChildren(element) {
|
|
94
|
+
if (element instanceof HTMLSlotElement) {
|
|
95
|
+
return element.assignedElements();
|
|
96
|
+
}
|
|
97
|
+
if (element.shadowRoot) {
|
|
98
|
+
return element.shadowRoot.children;
|
|
99
|
+
}
|
|
100
|
+
return element.children;
|
|
101
|
+
}
|
|
102
|
+
async focusFirstDescendant(element) {
|
|
103
|
+
await this.updateCompleteIfNecessary(element);
|
|
104
|
+
const children = this.getChildren(element);
|
|
105
|
+
for (let i = 0; i < children.length; i++) {
|
|
106
|
+
const child = children[i];
|
|
107
|
+
if (child instanceof HTMLElement && (this.attemptFocus(child) || await this.focusFirstDescendant(child))) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
async focusLastDescendant(element) {
|
|
114
|
+
await this.updateCompleteIfNecessary(element);
|
|
115
|
+
const children = this.getChildren(element);
|
|
116
|
+
for (let i = children.length - 1; i >= 0; i--) { // We cycle through the list backwards to find the latest element focusable
|
|
117
|
+
const child = children[i];
|
|
118
|
+
if (child instanceof HTMLElement && (this.attemptFocus(child) || await this.focusLastDescendant(child))) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
async updateCompleteIfNecessary(element) {
|
|
125
|
+
if (element instanceof ReactiveElement) {
|
|
126
|
+
await element.updateComplete;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
isInsideElement(target, container) {
|
|
130
|
+
for (let node = target; node; node = node.parentNode) {
|
|
131
|
+
if (node === container) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (node instanceof ShadowRoot) {
|
|
135
|
+
node = node.host;
|
|
136
|
+
}
|
|
137
|
+
else if (node.assignedSlot) {
|
|
138
|
+
node = node.assignedSlot;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Try to focus an element if focusable and return true, otherwise return false
|
|
145
|
+
*/
|
|
146
|
+
attemptFocus(element) {
|
|
147
|
+
if (element === this.firstTrap || element === this.lastTrap || !this.isFocusable(element)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
element.focus();
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
isFocusable(element) {
|
|
154
|
+
if (!element.checkVisibility() || this.isElementDisabled(element)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (element instanceof HTMLAnchorElement) {
|
|
158
|
+
return !!element.href && element.rel !== "ignore";
|
|
159
|
+
}
|
|
160
|
+
if (element instanceof HTMLInputElement) {
|
|
161
|
+
return element.type !== "hidden";
|
|
162
|
+
}
|
|
163
|
+
return element.tabIndex >= 0;
|
|
164
|
+
}
|
|
165
|
+
isElementDisabled(element) {
|
|
166
|
+
return "disabled" in element && !!element.disabled;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export const focusTrap = directive(FocusTrapDirective);
|