@telia-ace/knowledge-widget-components-search 1.0.4 → 1.0.9

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/dist/search.js ADDED
@@ -0,0 +1,1063 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+ import { Component } from "@telia-ace/knowledge-widget-types-grid";
33
+ import { borderTabStyle, Text, Link, SymbolBadge, useProperties, useDispatch, useRouteData, useEventListener, useKeyPress, Tooltip, ItemTree, contentBox, useScroll, useContainer, Loader, useDebounce, usePrevious, useChildren, useWidgetEvent, useTransitionEnd, convertToStringAttributes, Input, Button } from "@telia-ace/knowledge-widget-ui";
34
+ import { appendClassNames, deepClone, categoryTrail } from "@telia-ace/widget-utilities";
35
+ import React, { useRef, useState, useCallback, useEffect } from "react";
36
+ import styled, { css } from "styled-components";
37
+ const FilterBadge = (_a) => {
38
+ var _b = _a, {
39
+ text,
40
+ className,
41
+ forceFocusStyle,
42
+ filterType,
43
+ handleClick,
44
+ deleteAriaLabel = ""
45
+ } = _b, p = __objRest(_b, [
46
+ "text",
47
+ "className",
48
+ "forceFocusStyle",
49
+ "filterType",
50
+ "handleClick",
51
+ "deleteAriaLabel"
52
+ ]);
53
+ const ref = useRef();
54
+ return /* @__PURE__ */ React.createElement(Text, {
55
+ className: appendClassNames(className, "humany-filter-badge"),
56
+ onKeyDown: (e) => {
57
+ if (e.key === "Enter") {
58
+ handleClick(e, filterType);
59
+ }
60
+ }
61
+ }, text, /* @__PURE__ */ React.createElement(Link, __spreadProps(__spreadValues({}, p), {
62
+ ref,
63
+ tabIndex: 0,
64
+ role: "button",
65
+ "aria-label": deleteAriaLabel.replace("{{item}}", text),
66
+ onClick: (e) => handleClick(e, filterType)
67
+ }), /* @__PURE__ */ React.createElement(SymbolBadge, {
68
+ size: 13,
69
+ symbol: { type: "Svg", content: "close" }
70
+ })));
71
+ };
72
+ const FilterBadges = ({
73
+ inputHasFocus,
74
+ searchContainerRef,
75
+ showTag,
76
+ showGuideCategory,
77
+ position
78
+ }) => {
79
+ const { activeFilterBadges = {}, deleteFilterBadgeAriaLabel = "" } = useProperties();
80
+ const dispatch = useDispatch();
81
+ const { name, params } = useRouteData();
82
+ const [isNavigatingWithKeyBoard, setIsNavigatingWithKeyboard] = useState(false);
83
+ const onKeyDown = useCallback((event) => {
84
+ const { key } = event;
85
+ if (["ArrowLeft", "ArrowRight"].indexOf(key) === -1) {
86
+ setIsNavigatingWithKeyboard(false);
87
+ }
88
+ }, []);
89
+ useEventListener("keydown", onKeyDown, window);
90
+ useEventListener("click", onKeyDown, window);
91
+ const { guideCategory, tag, tooltip } = activeFilterBadges;
92
+ const getFocusedFilterBadge = (container) => {
93
+ const badges = container.querySelectorAll(".humany-filter-badge");
94
+ return {
95
+ badges,
96
+ index: Array.from(badges).findIndex((e) => document.activeElement === e)
97
+ };
98
+ };
99
+ const handleFilterBadgeClick = (event, filterType) => {
100
+ event.preventDefault();
101
+ dispatch("quick-filter:remove", { types: [filterType] });
102
+ };
103
+ const keyboardNavigation = (direction, inputElem) => {
104
+ if (!searchContainerRef) {
105
+ return;
106
+ }
107
+ const { badges, index: currentFocusedIndex } = getFocusedFilterBadge(searchContainerRef);
108
+ if (badges.length) {
109
+ let toFocus = null;
110
+ if (direction === "left") {
111
+ if (currentFocusedIndex === -1) {
112
+ toFocus = badges.item(badges.length - 1);
113
+ } else if (currentFocusedIndex > 0) {
114
+ toFocus = badges.item(currentFocusedIndex - 1);
115
+ }
116
+ } else if (direction === "right") {
117
+ if (currentFocusedIndex === badges.length - 1) {
118
+ inputElem && inputElem.focus();
119
+ } else if (badges.item(currentFocusedIndex + 1)) {
120
+ toFocus = badges.item(currentFocusedIndex + 1);
121
+ }
122
+ }
123
+ if (toFocus) {
124
+ toFocus.focus();
125
+ setIsNavigatingWithKeyboard(true);
126
+ }
127
+ }
128
+ };
129
+ useKeyPress("ArrowLeft", useCallback(() => {
130
+ if (!searchContainerRef) {
131
+ return;
132
+ }
133
+ const { index: focusedBadgeIndex } = getFocusedFilterBadge(searchContainerRef);
134
+ if (inputHasFocus || focusedBadgeIndex > -1) {
135
+ const inputElem = searchContainerRef.querySelector('[data-type="search"]');
136
+ const isAtBeginningOfInput = inputElem && inputElem.selectionStart !== null && inputElem.selectionStart <= 0;
137
+ const isFocusingAFilterBadge = focusedBadgeIndex > -1;
138
+ if (isAtBeginningOfInput || isFocusingAFilterBadge) {
139
+ keyboardNavigation("left", inputElem);
140
+ }
141
+ }
142
+ }, [inputHasFocus, searchContainerRef]));
143
+ useKeyPress("ArrowRight", useCallback(() => {
144
+ if (!searchContainerRef) {
145
+ return;
146
+ }
147
+ const { index: focusedBadgeIndex } = getFocusedFilterBadge(searchContainerRef);
148
+ if (focusedBadgeIndex > -1) {
149
+ const inputElem = searchContainerRef.querySelector('[data-type="search"]');
150
+ const isFocusingAFilterBadge = focusedBadgeIndex > -1;
151
+ if (isFocusingAFilterBadge) {
152
+ keyboardNavigation("right", inputElem);
153
+ }
154
+ }
155
+ }, [searchContainerRef]));
156
+ useKeyPress("Backspace", useCallback((event) => {
157
+ if (!searchContainerRef) {
158
+ return;
159
+ }
160
+ let filtersToRemove = [];
161
+ const { index: focusedBadgeIndex } = getFocusedFilterBadge(searchContainerRef);
162
+ const inputElem = searchContainerRef.querySelector('[data-type="search"]');
163
+ const isFocusingAFilterBadge = focusedBadgeIndex > -1;
164
+ if (isFocusingAFilterBadge) {
165
+ if (focusedBadgeIndex === 0) {
166
+ if (showGuideCategory) {
167
+ filtersToRemove = ["guideCategory"];
168
+ } else if (showTag) {
169
+ filtersToRemove = ["tag"];
170
+ }
171
+ } else if (focusedBadgeIndex === 1) {
172
+ filtersToRemove = ["tag"];
173
+ }
174
+ } else {
175
+ const isAtBeginningOfInput = inputElem && inputElem.selectionStart !== null && inputElem.selectionStart <= 0;
176
+ if (isAtBeginningOfInput) {
177
+ if (activeFilterBadges == null ? void 0 : activeFilterBadges.tag) {
178
+ filtersToRemove = ["tag"];
179
+ } else if (activeFilterBadges == null ? void 0 : activeFilterBadges.guideCategory) {
180
+ filtersToRemove = ["guideCategory"];
181
+ }
182
+ }
183
+ }
184
+ if (filtersToRemove.length > 0) {
185
+ event.preventDefault();
186
+ dispatch("quick-filter:remove", { types: filtersToRemove });
187
+ }
188
+ }, [searchContainerRef, activeFilterBadges, showTag, showGuideCategory]));
189
+ const renderBadge = useCallback((type) => {
190
+ if (type === "guideCategory" && guideCategory && showGuideCategory) {
191
+ return /* @__PURE__ */ React.createElement(StyledFilterBadge, {
192
+ text: `@${guideCategory.title}`,
193
+ routeName: name,
194
+ filterType: "guideCategory",
195
+ handleClick: handleFilterBadgeClick,
196
+ forceFocusStyle: isNavigatingWithKeyBoard,
197
+ deleteAriaLabel: deleteFilterBadgeAriaLabel,
198
+ params: __spreadProps(__spreadValues({}, params), {
199
+ guideCategory: void 0
200
+ })
201
+ });
202
+ }
203
+ if (type === "tag" && tag && showTag) {
204
+ return /* @__PURE__ */ React.createElement(StyledFilterBadge, {
205
+ className: "humany-filter-badge",
206
+ text: `#${tag.title}`,
207
+ routeName: name,
208
+ filterType: "tag",
209
+ handleClick: handleFilterBadgeClick,
210
+ forceFocusStyle: isNavigatingWithKeyBoard,
211
+ deleteAriaLabel: deleteFilterBadgeAriaLabel,
212
+ params: __spreadProps(__spreadValues({}, params), {
213
+ tag: void 0
214
+ })
215
+ });
216
+ }
217
+ return null;
218
+ }, [guideCategory, tag, name, params, showGuideCategory, showTag, isNavigatingWithKeyBoard]);
219
+ if (!guideCategory && !tag) {
220
+ return null;
221
+ }
222
+ return /* @__PURE__ */ React.createElement(Wrapper$1, {
223
+ className: "humany-filter-badges",
224
+ position
225
+ }, guideCategory && tooltip ? /* @__PURE__ */ React.createElement(Tooltip, {
226
+ content: /* @__PURE__ */ React.createElement(React.Fragment, null, tooltip),
227
+ sticky: false
228
+ }, renderBadge("guideCategory")) : renderBadge("guideCategory"), tag && renderBadge("tag"));
229
+ };
230
+ const Wrapper$1 = styled.div`
231
+ display: flex;
232
+ align-items: center;
233
+ flex-wrap: wrap;
234
+
235
+ ${(p) => p.position === "inside" ? css`
236
+ &:not(:first-child) {
237
+ margin: 0 0 0 ${(p2) => {
238
+ var _a;
239
+ return (_a = p2.theme.sizes) == null ? void 0 : _a.normal;
240
+ }};
241
+ }
242
+ ` : css`
243
+ margin: ${(p2) => {
244
+ var _a;
245
+ return (_a = p2.theme.sizes) == null ? void 0 : _a.small;
246
+ }} 0 0 0;
247
+ span:first-child {
248
+ margin-left: 0;
249
+ }
250
+ `}
251
+ `;
252
+ const StyledFilterBadge = styled(FilterBadge)`
253
+ display: flex;
254
+ align-items: center;
255
+ padding: ${(p) => {
256
+ var _a, _b;
257
+ return `${(_a = p.theme.sizes) == null ? void 0 : _a.small} calc(${(_b = p.theme.sizes) == null ? void 0 : _b.normal}/2) `;
258
+ }};
259
+ background-color: ${(p) => {
260
+ var _a;
261
+ return (_a = p.theme.colors) == null ? void 0 : _a.primary;
262
+ }};
263
+ border-radius: ${(p) => p.theme.borderRadius};
264
+ font-weight: 300;
265
+ font-size: ${(p) => {
266
+ var _a;
267
+ return (_a = p.theme.fonts) == null ? void 0 : _a.normal;
268
+ }};
269
+ font-style: italic;
270
+ color: #ffffff;
271
+ text-decoration: none;
272
+ white-space: nowrap;
273
+
274
+ margin: calc(${(p) => {
275
+ var _a;
276
+ return (_a = p.theme.sizes) == null ? void 0 : _a.small;
277
+ }} / 2);
278
+
279
+ &:focus-within {
280
+ ${(p) => {
281
+ var _a;
282
+ return (((_a = p.theme.accessibility) == null ? void 0 : _a.isTabbing) || p.forceFocusStyle) && css`
283
+ ${borderTabStyle}
284
+ background-color: transparent;
285
+
286
+ svg {
287
+ path {
288
+ stroke: ${(p2) => {
289
+ var _a2;
290
+ return (_a2 = p2.theme.colors) == null ? void 0 : _a2.primary;
291
+ }};
292
+ }
293
+ }
294
+ `;
295
+ }};
296
+ a {
297
+ outline: none;
298
+ }
299
+ }
300
+
301
+ svg {
302
+ width: 17px;
303
+ height: 11px;
304
+ margin: 1px 0 0 ${(p) => {
305
+ var _a;
306
+ return (_a = p.theme.sizes) == null ? void 0 : _a.small;
307
+ }};
308
+ path {
309
+ stroke: #ffffff;
310
+ stroke-width: 2px;
311
+ }
312
+ order: 1;
313
+ }
314
+ `;
315
+ const match = (title, phrase) => {
316
+ if (!phrase || !title) {
317
+ return true;
318
+ }
319
+ return !!(title.toLowerCase().indexOf(phrase.toLowerCase()) > -1);
320
+ };
321
+ const filterItems = (items = [], type, phrase) => {
322
+ if (!phrase) {
323
+ return items;
324
+ }
325
+ const cloned = deepClone(items);
326
+ if (type === "tag") {
327
+ return cloned.filter((i) => match(i.title, phrase));
328
+ }
329
+ const matchTrail = buildMatchTrail(cloned, phrase);
330
+ const filtered = recursiveFilter(cloned, (c) => matchTrail.indexOf(c.id) > -1);
331
+ return filtered;
332
+ };
333
+ const recursiveFilter = (items, fn) => {
334
+ return items.filter((i) => {
335
+ if (i.items && i.items.length) {
336
+ i.items = recursiveFilter(i.items, fn);
337
+ }
338
+ return fn(i);
339
+ });
340
+ };
341
+ const buildMatchTrail = (categories, phrase) => {
342
+ const flattened = flatten(categories);
343
+ const matching = flattened.filter((c) => match(c.title, phrase)).map((c) => c.id);
344
+ let res = [];
345
+ matching.forEach((id) => {
346
+ const trail = categoryTrail(id, categories);
347
+ res = res.concat(trail);
348
+ });
349
+ return res;
350
+ };
351
+ const flatten = (categories) => {
352
+ let result = [];
353
+ categories.forEach((category) => {
354
+ result.push(category);
355
+ if (Array.isArray(category.items)) {
356
+ result = result.concat(flatten(category.items));
357
+ }
358
+ });
359
+ return result;
360
+ };
361
+ const getFocusedIndex = (elem) => {
362
+ const anchors = Array.from((elem == null ? void 0 : elem.getElementsByTagName("A")) || []).filter((a) => a.getAttribute("disabled") === null);
363
+ return {
364
+ anchors,
365
+ focusedIndex: anchors.findIndex((e) => document.activeElement === e)
366
+ };
367
+ };
368
+ const getInputElem = (searchContainerRef) => {
369
+ return searchContainerRef ? searchContainerRef.querySelector('[data-type="search"], input[type="search"]') : null;
370
+ };
371
+ const focusInput = (searchContainerRef) => {
372
+ if (!searchContainerRef) {
373
+ return;
374
+ }
375
+ const inputElem = getInputElem(searchContainerRef);
376
+ if (inputElem) {
377
+ inputElem.focus();
378
+ }
379
+ };
380
+ const QuickFilterItemList = ({
381
+ filterPhrase,
382
+ items: rawItems,
383
+ filterType,
384
+ filterContainer,
385
+ searchContainer,
386
+ inputHasFocus
387
+ }) => {
388
+ const [items, setItems] = useState(rawItems || []);
389
+ const [first, setFirst] = useState(null);
390
+ const dispatch = useDispatch();
391
+ const { activeFilterBadges, quickFilters } = useProperties();
392
+ const autoSelect = typeof quickFilters === "object" && !!quickFilters.autoSelect || typeof quickFilters === "boolean" && !!quickFilters;
393
+ useEffect(() => {
394
+ setItems(filterItems(rawItems, filterType, filterPhrase));
395
+ }, [filterPhrase, filterType, rawItems]);
396
+ useEffect(() => {
397
+ var _a;
398
+ if (filterContainer) {
399
+ const { anchors } = getFocusedIndex(filterContainer);
400
+ const id = (_a = anchors[0]) == null ? void 0 : _a.getAttribute("data-id");
401
+ if (id) {
402
+ setFirst(id);
403
+ }
404
+ }
405
+ }, [items, filterContainer]);
406
+ const handleItemClicked = useCallback((item, type) => {
407
+ const data = {
408
+ category: (activeFilterBadges == null ? void 0 : activeFilterBadges.guideCategory) ? activeFilterBadges.guideCategory.id : void 0,
409
+ tag: (activeFilterBadges == null ? void 0 : activeFilterBadges.tag) ? activeFilterBadges.tag.id : void 0
410
+ };
411
+ if (type === "guideCategory") {
412
+ data.category = item.id;
413
+ } else if (type === "tag") {
414
+ data.tag = item.id;
415
+ }
416
+ dispatch("quick-filter:add", data);
417
+ }, [activeFilterBadges, dispatch]);
418
+ const buildListProps = (type) => {
419
+ if (type === "guideCategory") {
420
+ return {
421
+ renderItem: (item, level) => /* @__PURE__ */ React.createElement(StyledLink, {
422
+ autoSelect: autoSelect && inputHasFocus && item.id === first,
423
+ disabled: !match(item.title, filterPhrase),
424
+ tabIndex: !match(item.title, filterPhrase) ? -1 : 0,
425
+ onKeyDown: (e) => {
426
+ if (e.key === "Enter") {
427
+ handleItemClicked(item, type);
428
+ }
429
+ },
430
+ "data-level": level,
431
+ onClick: () => {
432
+ handleItemClicked(item, type);
433
+ },
434
+ "data-id": item.id
435
+ }, /* @__PURE__ */ React.createElement(Title, {
436
+ title: item.title,
437
+ phrase: filterPhrase,
438
+ matches: match(item.title, filterPhrase)
439
+ }))
440
+ };
441
+ }
442
+ return {
443
+ renderLi: true,
444
+ renderItem: (item) => /* @__PURE__ */ React.createElement(StyledLink, {
445
+ autoSelect: autoSelect && inputHasFocus && item.id === first,
446
+ onKeyDown: (e) => {
447
+ if (e.key === "Enter") {
448
+ handleItemClicked(item, type);
449
+ }
450
+ },
451
+ onClick: () => {
452
+ handleItemClicked(item, type);
453
+ },
454
+ "data-id": item.id
455
+ }, /* @__PURE__ */ React.createElement(Title, {
456
+ symbol: "#",
457
+ title: item.title,
458
+ phrase: filterPhrase,
459
+ matches: match(item.title, filterPhrase)
460
+ }))
461
+ };
462
+ };
463
+ useEventListener("keydown", (e) => {
464
+ var _a;
465
+ const { key } = e;
466
+ if (!filterContainer || !(key === "ArrowDown" || key === "ArrowUp" || key === "Enter")) {
467
+ return;
468
+ }
469
+ const { anchors, focusedIndex } = getFocusedIndex(filterContainer);
470
+ if (key === "Enter") {
471
+ if (inputHasFocus && autoSelect) {
472
+ const id = (_a = anchors[0]) == null ? void 0 : _a.getAttribute("data-id");
473
+ if (id) {
474
+ handleItemClicked({ id }, filterType);
475
+ }
476
+ }
477
+ return;
478
+ }
479
+ const focusFirst = () => {
480
+ var _a2;
481
+ return (_a2 = anchors[0]) == null ? void 0 : _a2.focus();
482
+ };
483
+ const focusLast = () => {
484
+ var _a2;
485
+ return (_a2 = anchors[anchors.length - 1]) == null ? void 0 : _a2.focus();
486
+ };
487
+ const focusNext = () => {
488
+ var _a2;
489
+ return (_a2 = anchors[focusedIndex + 1]) == null ? void 0 : _a2.focus();
490
+ };
491
+ const focusPrev = () => {
492
+ var _a2;
493
+ return (_a2 = anchors[focusedIndex - 1]) == null ? void 0 : _a2.focus();
494
+ };
495
+ if (inputHasFocus) {
496
+ e.preventDefault();
497
+ if (key === "ArrowDown") {
498
+ focusFirst();
499
+ } else {
500
+ focusLast();
501
+ }
502
+ } else if (focusedIndex > -1) {
503
+ e.preventDefault();
504
+ if (key === "ArrowDown") {
505
+ if (anchors.length > focusedIndex + 1) {
506
+ focusNext();
507
+ } else {
508
+ focusInput(searchContainer);
509
+ }
510
+ } else {
511
+ if (focusedIndex - 1 < 0) {
512
+ focusInput(searchContainer);
513
+ } else {
514
+ focusPrev();
515
+ }
516
+ }
517
+ }
518
+ }, window);
519
+ return /* @__PURE__ */ React.createElement(ItemTree, __spreadValues({
520
+ tree: items,
521
+ renderEmpty: false,
522
+ ulProps: { role: "listbox" },
523
+ liProps: { role: "option" }
524
+ }, buildListProps(filterType)));
525
+ };
526
+ const Title = ({ title, phrase, matches, symbol = "" }) => {
527
+ const createTitle = () => {
528
+ if (!matches || !phrase) {
529
+ return [title];
530
+ }
531
+ const index = title.toLowerCase().indexOf(phrase.toLowerCase());
532
+ const start2 = title.substr(0, index);
533
+ const bold2 = title.slice(index, index + phrase.length);
534
+ const end2 = title.substr(index + phrase.length);
535
+ return [start2, bold2, end2];
536
+ };
537
+ const [start, bold, end] = createTitle();
538
+ return /* @__PURE__ */ React.createElement(Text, null, symbol, start, bold ? /* @__PURE__ */ React.createElement("strong", null, bold) : null, end ? end : null);
539
+ };
540
+ const disabledCss = css`
541
+ opacity: 0.5;
542
+ pointer-events: none;
543
+ `;
544
+ const focused = css`
545
+ background-color: ${(p) => {
546
+ var _a;
547
+ return (_a = p.theme.colors) == null ? void 0 : _a.text;
548
+ }};
549
+ color: #ffffff;
550
+ outline: none;
551
+
552
+ span:first-child {
553
+ border-color: ${(p) => {
554
+ var _a;
555
+ return (_a = p.theme.colors) == null ? void 0 : _a.text;
556
+ }};
557
+ }
558
+ `;
559
+ const StyledLink = styled(Link)`
560
+ display: block;
561
+ text-decoration: none;
562
+ font-size: ${(p) => {
563
+ var _a;
564
+ return (_a = p.theme.fonts) == null ? void 0 : _a.normal;
565
+ }};
566
+
567
+ ${(p) => p.disabled && disabledCss}
568
+ color: ${(p) => {
569
+ var _a;
570
+ return (_a = p.theme.colors) == null ? void 0 : _a.text;
571
+ }};
572
+ background-color: transparent;
573
+
574
+ span {
575
+ display: block;
576
+ ${(p) => {
577
+ var _a, _b, _c, _d;
578
+ return p["data-level"] ? `padding: calc(${(_a = p.theme.sizes) == null ? void 0 : _a.normal} / 2) ${(_b = p.theme.sizes) == null ? void 0 : _b.normal};` : `padding: calc(${(_c = p.theme.sizes) == null ? void 0 : _c.normal} / 2) ${(_d = p.theme.sizes) == null ? void 0 : _d.medium}; `;
579
+ }}
580
+ ${(p) => p["data-level"] && "border-left: 2px solid"}
581
+ }
582
+
583
+ span:first-child {
584
+ border-color: #e7e7e7;
585
+ }
586
+
587
+ ${(p) => {
588
+ var _a;
589
+ return p["data-level"] && `padding: 0 calc(${(_a = p.theme.sizes) == null ? void 0 : _a.medium} * ${p["data-level"]});`;
590
+ }}
591
+
592
+ ${(p) => p.autoSelect && focused}
593
+
594
+ &:hover,
595
+ &:focus {
596
+ ${focused}
597
+ }
598
+ `;
599
+ var QuickFilter = ({ phrase = "", inputHasFocus, searchContainerRef }) => {
600
+ const {
601
+ quickFilter = {
602
+ open: false,
603
+ loading: false,
604
+ symbol: "",
605
+ type: "",
606
+ items: []
607
+ }
608
+ } = useProperties();
609
+ const [css2, ref] = useScroll(true);
610
+ const container = useContainer();
611
+ const { events } = container.get("$widget");
612
+ const wrapperRef = useRef();
613
+ const setWrapperRef = useCallback((node) => {
614
+ if (node) {
615
+ node.addEventListener("keydown", () => {
616
+ const listItemFocused = getFocusedIndex(node).focusedIndex > -1;
617
+ if (listItemFocused) {
618
+ events.subscribeOnce("router:changed", () => {
619
+ focusInput(searchContainerRef);
620
+ });
621
+ }
622
+ }, true);
623
+ }
624
+ wrapperRef.current = node;
625
+ return node;
626
+ }, [searchContainerRef]);
627
+ const { items = [], symbol, type, open, loading } = quickFilter;
628
+ if (!open || !symbol) {
629
+ return null;
630
+ }
631
+ const filterPhrase = phrase.slice(phrase.indexOf(symbol) + 1);
632
+ return /* @__PURE__ */ React.createElement(Wrapper, {
633
+ ref: setWrapperRef,
634
+ "data-loading": loading,
635
+ className: appendClassNames("humany-quick-filter-dropdown", [type === "guideCategory", "humany-quick-filter-guide-categories"], [type === "tag", "humany-quick-filter-tags"])
636
+ }, /* @__PURE__ */ React.createElement(Inner$1, {
637
+ className: "humany-quick-filter-dropdown-inner",
638
+ css: css2,
639
+ ref
640
+ }, /* @__PURE__ */ React.createElement(QuickFilterItemList, {
641
+ filterType: type,
642
+ items,
643
+ filterPhrase,
644
+ filterContainer: ref.current,
645
+ searchContainer: searchContainerRef,
646
+ inputHasFocus
647
+ })), /* @__PURE__ */ React.createElement(Loader, {
648
+ loading
649
+ }));
650
+ };
651
+ const Wrapper = styled.div`
652
+ ${contentBox};
653
+ position: absolute;
654
+ top: calc(100% + ${(p) => {
655
+ var _a;
656
+ return (_a = p.theme.sizes) == null ? void 0 : _a.normal;
657
+ }});
658
+ left: 0;
659
+ right: 0;
660
+ z-index: 1;
661
+ overflow: hidden;
662
+ `;
663
+ const Inner$1 = styled.div`
664
+ max-height: 300px;
665
+ overflow: auto;
666
+ padding: ${(p) => {
667
+ var _a;
668
+ return (_a = p.theme.sizes) == null ? void 0 : _a.medium;
669
+ }} 0;
670
+ ${(p) => p.css}
671
+ ul {
672
+ list-style: none;
673
+ padding: 0;
674
+ margin: 0;
675
+ }
676
+ li div {
677
+ ${(p) => {
678
+ var _a;
679
+ return `margin: ${(_a = p.theme.sizes) == null ? void 0 : _a.normal} 0;`;
680
+ }}
681
+ }
682
+ `;
683
+ const filterChanged = (newValue, oldValue) => {
684
+ if (newValue && !oldValue) {
685
+ return true;
686
+ }
687
+ if (!newValue && oldValue) {
688
+ return true;
689
+ }
690
+ if (newValue && oldValue) {
691
+ if (newValue.id !== oldValue.id) {
692
+ return true;
693
+ }
694
+ }
695
+ return false;
696
+ };
697
+ var useSearch = (phrase, params = {}, debounce = true) => {
698
+ const dispatch = useDispatch();
699
+ const {
700
+ quickFilters = false,
701
+ quickFilter = { open: false },
702
+ activeFilterBadges,
703
+ filterBadges = false,
704
+ incremental = 600
705
+ } = useProperties();
706
+ const debouncedPhrase = useDebounce(phrase, typeof incremental === "boolean" ? 600 : incremental);
707
+ const previousDebouncedPhrase = usePrevious(debouncedPhrase);
708
+ const previousFilters = usePrevious(activeFilterBadges);
709
+ const ref = useRef(null);
710
+ const dispatchSearch = useCallback((value = "") => {
711
+ if (filterBadges) {
712
+ const filtersChanged = filterChanged(activeFilterBadges == null ? void 0 : activeFilterBadges.guideCategory, previousFilters == null ? void 0 : previousFilters.guideCategory) || filterChanged(activeFilterBadges == null ? void 0 : activeFilterBadges.tag, previousFilters == null ? void 0 : previousFilters.tag);
713
+ return dispatch("search", { value, filtersChanged, filters: activeFilterBadges });
714
+ }
715
+ return dispatch("search", { value });
716
+ }, [dispatch, activeFilterBadges]);
717
+ const dispatchClear = useCallback((navigateToHome) => {
718
+ dispatch("clear", { navigateToHome });
719
+ }, [dispatch]);
720
+ const removeFilterKeywords = (input) => {
721
+ if (quickFilters) {
722
+ return input.replace(/([@#].*)/g, "");
723
+ }
724
+ return input;
725
+ };
726
+ const submitSearch = useCallback((arg) => {
727
+ if (quickFilter.open && arg.preventDefault) {
728
+ arg.preventDefault();
729
+ return;
730
+ }
731
+ const activeElement = document.activeElement;
732
+ const isSearching = ref.current && typeof ref.current.contains === "function" ? ref.current.contains(activeElement) : false;
733
+ const cleanedPhrase = removeFilterKeywords(phrase);
734
+ if (typeof arg === "boolean" && arg || cleanedPhrase.length && isSearching || (activeFilterBadges == null ? void 0 : activeFilterBadges.guideCategory) || (activeFilterBadges == null ? void 0 : activeFilterBadges.tag) && isSearching) {
735
+ dispatchSearch(cleanedPhrase);
736
+ } else {
737
+ dispatchClear(isSearching);
738
+ }
739
+ }, [ref, phrase, quickFilter, dispatchClear, activeFilterBadges, dispatchSearch]);
740
+ useEffect(() => {
741
+ const filtersChanged = filterChanged(activeFilterBadges == null ? void 0 : activeFilterBadges.guideCategory, previousFilters == null ? void 0 : previousFilters.guideCategory) || filterChanged(activeFilterBadges == null ? void 0 : activeFilterBadges.tag, previousFilters == null ? void 0 : previousFilters.tag);
742
+ const phrase2 = debouncedPhrase ? removeFilterKeywords(debouncedPhrase) : debouncedPhrase;
743
+ const previousPhrase = previousDebouncedPhrase ? removeFilterKeywords(previousDebouncedPhrase) : previousDebouncedPhrase;
744
+ if (typeof previousPhrase !== "undefined" && previousPhrase !== phrase2 && phrase2 !== params.phrase && debounce && !quickFilter.open || filtersChanged && !(activeFilterBadges == null ? void 0 : activeFilterBadges.initial)) {
745
+ submitSearch(filtersChanged);
746
+ }
747
+ }, [debouncedPhrase, quickFilter.open, activeFilterBadges, submitSearch]);
748
+ useEffect(() => {
749
+ if (phrase && quickFilters) {
750
+ const quickFiltersGuideCategory = typeof quickFilters === "boolean" && !!quickFilters || typeof quickFilters === "object" && !!quickFilters.guideCategory;
751
+ const quickFiltersTag = typeof quickFilters === "boolean" && !!quickFilters || typeof quickFilters === "object" && !!quickFilters.tag;
752
+ if (quickFiltersGuideCategory && phrase.indexOf("@") === phrase.length - 1) {
753
+ dispatch("quick-filter:open", { type: "guideCategory", symbol: "@" });
754
+ }
755
+ if (quickFiltersTag && phrase.indexOf("#") === phrase.length - 1) {
756
+ dispatch("quick-filter:open", { type: "tag", symbol: "#" });
757
+ }
758
+ }
759
+ if (quickFilter.open && phrase.indexOf("@") === -1 && phrase.indexOf("#") === -1) {
760
+ dispatch("quick-filter:close");
761
+ }
762
+ }, [phrase]);
763
+ return [submitSearch, dispatchClear, ref];
764
+ };
765
+ const Search = (_c) => {
766
+ var _d = _c, { className } = _d, other = __objRest(_d, ["className"]);
767
+ const {
768
+ showSearchButton,
769
+ showClearButton,
770
+ role = "search",
771
+ autoFocus,
772
+ placeholder,
773
+ searchButtonLabel,
774
+ clearButtonLabel,
775
+ ariaLabel,
776
+ route: searchRoute = "search",
777
+ incremental = true,
778
+ showChildren = false,
779
+ quickFilter,
780
+ quickFilters: quickFiltersEnabled,
781
+ filterBadges
782
+ } = useProperties();
783
+ const { params } = useRouteData();
784
+ const [hasFocus, setHasFocus] = useState(false);
785
+ const [phrase, setPhrase] = useState(params.phrase || "");
786
+ const [showFilterBadges, setShowFilterBadges] = useState({
787
+ guideCategory: false,
788
+ tag: false
789
+ });
790
+ const container = useContainer();
791
+ const children = useChildren();
792
+ const dispatch = useDispatch();
793
+ const { position: filterBadgePosition = "inside" } = typeof filterBadges === "object" ? filterBadges : {};
794
+ useEffect(() => {
795
+ const { guideCategory, tag } = params;
796
+ const showGuideCategory = typeof filterBadges === "object" ? !!filterBadges.guideCategory : !!filterBadges;
797
+ const showTag = typeof filterBadges === "object" ? !!filterBadges.tag : !!filterBadges;
798
+ setShowFilterBadges({
799
+ guideCategory: showGuideCategory && !!guideCategory,
800
+ tag: showTag && !!tag
801
+ });
802
+ }, [filterBadges, params]);
803
+ useWidgetEvent("router:changed", () => {
804
+ if (quickFilter) {
805
+ const { open, symbol } = quickFilter;
806
+ if (open) {
807
+ if (symbol) {
808
+ const cleaned = phrase.replace(phrase.slice(phrase.indexOf(symbol)), "");
809
+ setPhrase(cleaned);
810
+ }
811
+ dispatch("quick-filter:close");
812
+ }
813
+ }
814
+ }, [quickFilter, phrase, setPhrase, dispatch]);
815
+ const [search, clear, elem] = useSearch(phrase, params, !!incremental);
816
+ useEffect(() => {
817
+ setPhrase(params.phrase || "");
818
+ }, [params.phrase]);
819
+ const onChange = useCallback((value) => {
820
+ setPhrase(value);
821
+ }, [setPhrase]);
822
+ const onEscape = useCallback(() => {
823
+ dispatch("quick-filter:close").then(() => clear(true));
824
+ }, [dispatch, clear]);
825
+ const autoFocusHandler = useCallback(() => {
826
+ var _a, _b;
827
+ if (autoFocus && !hasFocus) {
828
+ (_b = (_a = elem.current) == null ? void 0 : _a.getElementsByTagName("input")[0]) == null ? void 0 : _b.focus();
829
+ }
830
+ }, [autoFocus, hasFocus, elem]);
831
+ const onClearButtonClick = useCallback(() => {
832
+ container.getAsync("router").then((router) => {
833
+ const initialRoute = router.getInitialRoute();
834
+ const currentRoute = router.getRouteData();
835
+ if ((initialRoute == null ? void 0 : initialRoute.name) === currentRoute.name && (initialRoute == null ? void 0 : initialRoute.name) !== searchRoute) {
836
+ setPhrase("");
837
+ } else {
838
+ clear(true);
839
+ }
840
+ });
841
+ }, [container, setPhrase, clear]);
842
+ const renderFilterBadges = (position) => {
843
+ if (position !== filterBadgePosition) {
844
+ return null;
845
+ }
846
+ if (!!showFilterBadges.guideCategory || !!showFilterBadges.tag) {
847
+ return /* @__PURE__ */ React.createElement(FilterBadges, {
848
+ position,
849
+ inputHasFocus: hasFocus,
850
+ showGuideCategory: showFilterBadges.guideCategory,
851
+ showTag: showFilterBadges.tag,
852
+ searchContainerRef: elem.current
853
+ });
854
+ }
855
+ };
856
+ useTransitionEnd(autoFocusHandler);
857
+ const showClear = showClearButton && (showFilterBadges.guideCategory || showFilterBadges.tag || phrase);
858
+ const quickFilterAccessibilityProps = quickFiltersEnabled ? convertToStringAttributes({
859
+ role: "combobox",
860
+ "aria-autocomplete": "list",
861
+ autoComplete: "off",
862
+ "aria-haspopup": "listbox"
863
+ }) : {};
864
+ return /* @__PURE__ */ React.createElement(StyledSearchWrapper, __spreadProps(__spreadValues({}, other), {
865
+ ref: elem,
866
+ role,
867
+ className: appendClassNames(className, "humany-search"),
868
+ "data-has-phrase": !!phrase,
869
+ "data-has-focus": hasFocus,
870
+ "data-has-search-symbol": showSearchButton ? "true" : "false"
871
+ }), /* @__PURE__ */ React.createElement(Inner, {
872
+ action: ".",
873
+ onSubmit: (e) => {
874
+ var _a, _b;
875
+ e.preventDefault();
876
+ (_b = (_a = elem.current) == null ? void 0 : _a.getElementsByTagName("input")[0]) == null ? void 0 : _b.blur();
877
+ },
878
+ "data-has-focus": hasFocus
879
+ }, showSearchButton && /* @__PURE__ */ React.createElement(SearchButton, {
880
+ "aria-label": searchButtonLabel,
881
+ type: "submit",
882
+ title: searchButtonLabel,
883
+ disabled: !phrase,
884
+ onClick: search,
885
+ "data-has-phrase": !!phrase,
886
+ "data-has-focus": hasFocus
887
+ }, /* @__PURE__ */ React.createElement(SymbolBadge, {
888
+ size: 32,
889
+ symbol: { type: "Svg", content: "search" }
890
+ })), renderFilterBadges("inside"), /* @__PURE__ */ React.createElement(Input, __spreadProps(__spreadValues({
891
+ type: "search",
892
+ "aria-label": ariaLabel
893
+ }, quickFilterAccessibilityProps), {
894
+ placeholder: showFilterBadges.guideCategory || showFilterBadges.tag ? "" : placeholder,
895
+ onFocusChange: setHasFocus,
896
+ onEscape,
897
+ onEnter: search,
898
+ onChange,
899
+ value: phrase
900
+ })), showClear && /* @__PURE__ */ React.createElement(ClearButton, {
901
+ "aria-label": clearButtonLabel,
902
+ type: "reset",
903
+ hasFocus,
904
+ title: clearButtonLabel,
905
+ onClick: onClearButtonClick
906
+ }, /* @__PURE__ */ React.createElement(SymbolBadge, {
907
+ size: 27,
908
+ symbol: { type: "Svg", content: "clear" }
909
+ })), showChildren && !showClear && children.map((child) => /* @__PURE__ */ React.createElement(Component, {
910
+ key: child.id,
911
+ id: child.id,
912
+ branch: "default"
913
+ })), /* @__PURE__ */ React.createElement(QuickFilter, {
914
+ inputHasFocus: hasFocus,
915
+ phrase,
916
+ searchContainerRef: elem.current
917
+ })), renderFilterBadges("below"));
918
+ };
919
+ const StyledSearchWrapper = styled.div`
920
+ ${contentBox};
921
+ width: 100%;
922
+ padding: ${(p) => {
923
+ var _a;
924
+ return (_a = p.theme.sizes) == null ? void 0 : _a.large;
925
+ }};
926
+
927
+ input {
928
+ background-color: transparent;
929
+ border: none;
930
+ outline: none;
931
+ min-width: 25%;
932
+ flex: 1;
933
+ font-size: ${(p) => {
934
+ var _a;
935
+ return (_a = p.theme.fonts) == null ? void 0 : _a.normal;
936
+ }};
937
+ font-weight: 300;
938
+ font-style: italic;
939
+ padding: 1em 0;
940
+ color: ${(p) => {
941
+ var _a;
942
+ return (_a = p.theme.colors) == null ? void 0 : _a.text;
943
+ }};
944
+ -webkit-appearance: none;
945
+
946
+ ${(p) => {
947
+ var _a;
948
+ return p["data-has-search-symbol"] === "true" && `text-indent: ${(_a = p.theme.sizes) == null ? void 0 : _a.normal};`;
949
+ }}
950
+
951
+ ::placeholder {
952
+ color: ${(p) => {
953
+ var _a;
954
+ return (_a = p.theme.colors) == null ? void 0 : _a.text;
955
+ }};
956
+ }
957
+ /* removes the 'X' from IE */
958
+ &[type='search']::-ms-clear {
959
+ display: none;
960
+ width: 0;
961
+ height: 0;
962
+ }
963
+ &[type='search']::-ms-reveal {
964
+ display: none;
965
+ width: 0;
966
+ height: 0;
967
+ }
968
+
969
+ /* removes the 'X' from Chrome */
970
+ &[type='search']::-webkit-search-decoration,
971
+ &[type='search']::-webkit-search-cancel-button,
972
+ &[type='search']::-webkit-search-results-button,
973
+ &[type='search']::-webkit-search-results-decoration {
974
+ display: none;
975
+ }
976
+
977
+ &:focus {
978
+ outline: none;
979
+ }
980
+ }
981
+
982
+ button {
983
+ background: transparent;
984
+ border: none;
985
+ padding: 0;
986
+ font-size: ${(p) => {
987
+ var _a;
988
+ return (_a = p.theme.fonts) == null ? void 0 : _a.large;
989
+ }};
990
+ transition: color 200ms ease-out;
991
+ color: ${(p) => {
992
+ var _a;
993
+ return p["data-has-focus"] || p["data-has-phrase"] ? (_a = p.theme.colors) == null ? void 0 : _a.primary : "#000000";
994
+ }};
995
+ cursor: pointer;
996
+ > svg {
997
+ height: 100%;
998
+ }
999
+ }
1000
+ `;
1001
+ const Inner = styled.form`
1002
+ display: flex;
1003
+ flex-wrap: nowrap;
1004
+ align-items: center;
1005
+ border: ${(p) => p.theme.inputBorder};
1006
+ border-radius: ${(p) => p.theme.borderRadius};
1007
+ background-color: #ffffff;
1008
+ position: relative;
1009
+ padding: 0 ${(p) => {
1010
+ var _a;
1011
+ return (_a = p.theme.sizes) == null ? void 0 : _a.normal;
1012
+ }};
1013
+
1014
+ ${(p) => p["data-has-focus"] && css`
1015
+ ${(p2) => {
1016
+ var _a, _b;
1017
+ return ((_a = p2.theme.accessibility) == null ? void 0 : _a.isTabbing) ? borderTabStyle : `
1018
+ border-color: ${(_b = p2.theme.colors) == null ? void 0 : _b.primary};
1019
+ outline: none;
1020
+ input {
1021
+ outline: none;
1022
+ }
1023
+ `;
1024
+ }}
1025
+ `};
1026
+ `;
1027
+ const SearchButton = styled(Button)`
1028
+ padding: 0;
1029
+ height: 2em;
1030
+
1031
+ svg {
1032
+ circle,
1033
+ line {
1034
+ stroke: ${(p) => {
1035
+ var _a;
1036
+ return p["data-has-focus"] ? (_a = p.theme.colors) == null ? void 0 : _a.primary : "#000000";
1037
+ }};
1038
+ }
1039
+ }
1040
+ `;
1041
+ const ClearButton = styled(Button)`
1042
+ &:focus svg {
1043
+ ${(p) => {
1044
+ var _a;
1045
+ return ((_a = p.theme.accessibility) == null ? void 0 : _a.isTabbing) && borderTabStyle;
1046
+ }}
1047
+ }
1048
+
1049
+ svg {
1050
+ vertical-align: top;
1051
+
1052
+ circle,
1053
+ line,
1054
+ path {
1055
+ stroke: ${(p) => {
1056
+ var _a, _b;
1057
+ return p.hasFocus ? (_a = p.theme.colors) == null ? void 0 : _a.primary : (_b = p.theme.colors) == null ? void 0 : _b.text;
1058
+ }};
1059
+ }
1060
+ }
1061
+ `;
1062
+ export { Search as default };
1063
+ //# sourceMappingURL=search.js.map