@reuters-graphics/graphics-components 0.0.4 → 0.0.7
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/@types/components/Framer/Typeahead/fuzzy.d.ts +10 -0
- package/dist/@types/components/Framer/Typeahead/index.svelte.d.ts +143 -0
- package/dist/components/Framer/Framer.svelte +50 -36
- package/dist/components/Framer/Typeahead/fuzzy.js +133 -0
- package/dist/components/Framer/Typeahead/index.svelte +349 -0
- package/package.json +4 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default fuzzy;
|
|
2
|
+
declare namespace fuzzy {
|
|
3
|
+
function simpleFilter(pattern: any, array: any): any;
|
|
4
|
+
function test(pattern: any, str: any): boolean;
|
|
5
|
+
function match(pattern: any, str: any, opts: any): {
|
|
6
|
+
rendered: string;
|
|
7
|
+
score: number;
|
|
8
|
+
};
|
|
9
|
+
function filter(pattern: any, arr: any, opts: any): any;
|
|
10
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} IndexProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} IndexEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} IndexSlots */
|
|
4
|
+
export default class Index extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
value?: string;
|
|
7
|
+
extract?: (item: {
|
|
8
|
+
index: number;
|
|
9
|
+
embed: string;
|
|
10
|
+
}) => any;
|
|
11
|
+
id?: string;
|
|
12
|
+
data?: {
|
|
13
|
+
index: number;
|
|
14
|
+
embed: string;
|
|
15
|
+
}[];
|
|
16
|
+
disable?: (item: {
|
|
17
|
+
index: number;
|
|
18
|
+
embed: string;
|
|
19
|
+
}) => boolean;
|
|
20
|
+
filter?: (item: {
|
|
21
|
+
index: number;
|
|
22
|
+
embed: string;
|
|
23
|
+
}) => boolean;
|
|
24
|
+
autoselect?: boolean;
|
|
25
|
+
inputAfterSelect?: "update" | "clear" | "keep";
|
|
26
|
+
results?: {
|
|
27
|
+
original: {
|
|
28
|
+
index: number;
|
|
29
|
+
embed: string;
|
|
30
|
+
};
|
|
31
|
+
index: number;
|
|
32
|
+
score: number;
|
|
33
|
+
string: string;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
focusAfterSelect?: boolean;
|
|
37
|
+
showDropdownOnFocus?: boolean;
|
|
38
|
+
limit?: number;
|
|
39
|
+
}, {
|
|
40
|
+
type: CustomEvent<string>;
|
|
41
|
+
input: Event;
|
|
42
|
+
change: Event;
|
|
43
|
+
focus: FocusEvent;
|
|
44
|
+
clear: CustomEvent<any>;
|
|
45
|
+
blur: FocusEvent;
|
|
46
|
+
keydown: KeyboardEvent;
|
|
47
|
+
select: CustomEvent<any>;
|
|
48
|
+
} & {
|
|
49
|
+
[evt: string]: CustomEvent<any>;
|
|
50
|
+
}, {
|
|
51
|
+
default: {
|
|
52
|
+
result: {
|
|
53
|
+
original: {
|
|
54
|
+
index: number;
|
|
55
|
+
embed: string;
|
|
56
|
+
};
|
|
57
|
+
index: number;
|
|
58
|
+
score: number;
|
|
59
|
+
string: string;
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
};
|
|
62
|
+
index: any;
|
|
63
|
+
value: string;
|
|
64
|
+
};
|
|
65
|
+
'no-results': {
|
|
66
|
+
value: string;
|
|
67
|
+
};
|
|
68
|
+
}> {
|
|
69
|
+
}
|
|
70
|
+
export type IndexProps = typeof __propDef.props;
|
|
71
|
+
export type IndexEvents = typeof __propDef.events;
|
|
72
|
+
export type IndexSlots = typeof __propDef.slots;
|
|
73
|
+
import { SvelteComponentTyped } from "svelte";
|
|
74
|
+
declare const __propDef: {
|
|
75
|
+
props: {
|
|
76
|
+
[x: string]: any;
|
|
77
|
+
value?: string;
|
|
78
|
+
extract?: (item: {
|
|
79
|
+
index: number;
|
|
80
|
+
embed: string;
|
|
81
|
+
}) => any;
|
|
82
|
+
id?: string;
|
|
83
|
+
data?: {
|
|
84
|
+
index: number;
|
|
85
|
+
embed: string;
|
|
86
|
+
}[];
|
|
87
|
+
disable?: (item: {
|
|
88
|
+
index: number;
|
|
89
|
+
embed: string;
|
|
90
|
+
}) => boolean;
|
|
91
|
+
filter?: (item: {
|
|
92
|
+
index: number;
|
|
93
|
+
embed: string;
|
|
94
|
+
}) => boolean;
|
|
95
|
+
autoselect?: boolean;
|
|
96
|
+
inputAfterSelect?: "update" | "clear" | "keep";
|
|
97
|
+
results?: {
|
|
98
|
+
original: {
|
|
99
|
+
index: number;
|
|
100
|
+
embed: string;
|
|
101
|
+
};
|
|
102
|
+
index: number;
|
|
103
|
+
score: number;
|
|
104
|
+
string: string;
|
|
105
|
+
disabled?: boolean;
|
|
106
|
+
}[];
|
|
107
|
+
focusAfterSelect?: boolean;
|
|
108
|
+
showDropdownOnFocus?: boolean;
|
|
109
|
+
limit?: number;
|
|
110
|
+
};
|
|
111
|
+
events: {
|
|
112
|
+
type: CustomEvent<string>;
|
|
113
|
+
input: Event;
|
|
114
|
+
change: Event;
|
|
115
|
+
focus: FocusEvent;
|
|
116
|
+
clear: CustomEvent<any>;
|
|
117
|
+
blur: FocusEvent;
|
|
118
|
+
keydown: KeyboardEvent;
|
|
119
|
+
select: CustomEvent<any>;
|
|
120
|
+
} & {
|
|
121
|
+
[evt: string]: CustomEvent<any>;
|
|
122
|
+
};
|
|
123
|
+
slots: {
|
|
124
|
+
default: {
|
|
125
|
+
result: {
|
|
126
|
+
original: {
|
|
127
|
+
index: number;
|
|
128
|
+
embed: string;
|
|
129
|
+
};
|
|
130
|
+
index: number;
|
|
131
|
+
score: number;
|
|
132
|
+
string: string;
|
|
133
|
+
disabled?: boolean;
|
|
134
|
+
};
|
|
135
|
+
index: any;
|
|
136
|
+
value: string;
|
|
137
|
+
};
|
|
138
|
+
'no-results': {
|
|
139
|
+
value: string;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
export {};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import Resizer from './Resizer/index.svelte';
|
|
8
8
|
import { width } from './stores.js';
|
|
9
9
|
import getUniqNames from './uniqNames.js';
|
|
10
|
+
import Typeahead from './Typeahead/index.svelte';
|
|
10
11
|
|
|
11
12
|
export let embeds;
|
|
12
13
|
export let breakpoints = [330, 510, 660, 930, 1200];
|
|
@@ -46,22 +47,33 @@
|
|
|
46
47
|
/>
|
|
47
48
|
</header>
|
|
48
49
|
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
50
|
+
<div id="typeahead-container">
|
|
51
|
+
<div class="embed-link">
|
|
52
|
+
<a
|
|
53
|
+
rel="external"
|
|
54
|
+
target="_blank"
|
|
55
|
+
href="{activeEmbed}"
|
|
56
|
+
title="{activeEmbed}"
|
|
56
57
|
>
|
|
57
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
Live link <Fa icon="{faLink}" />
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
<Typeahead
|
|
62
|
+
label="Select an embed"
|
|
63
|
+
value="{embedTitles[0]}"
|
|
64
|
+
extract="{(d) => embedTitles[d.index]}"
|
|
65
|
+
data="{embeds.map((embed, index) => ({ index, embed }))}"
|
|
66
|
+
placeholder="{'Search'}"
|
|
67
|
+
showDropdownOnFocus="{true}"
|
|
68
|
+
on:select="{({ detail }) => {
|
|
69
|
+
activeEmbed = detail.original.embed;
|
|
70
|
+
}}"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
64
73
|
|
|
74
|
+
<div id="preview-label" style="width:{$width}px;">
|
|
75
|
+
<p>Preview</p>
|
|
76
|
+
</div>
|
|
65
77
|
<div id="frame-parent" style="width:{$width}px;"></div>
|
|
66
78
|
</div>
|
|
67
79
|
|
|
@@ -86,36 +98,38 @@
|
|
|
86
98
|
margin: 20px 0;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
margin: 0 auto
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
nav button {
|
|
95
|
-
margin: 0 4px 5px;
|
|
96
|
-
background-color: transparent;
|
|
97
|
-
border: 0;
|
|
98
|
-
color: #999;
|
|
99
|
-
padding: 2px 2px;
|
|
100
|
-
cursor: pointer;
|
|
101
|
-
font-family: "Knowledge", "Source Sans Pro", Arial, sans-serif;
|
|
102
|
-
font-weight: 400;
|
|
103
|
-
}
|
|
104
|
-
nav button.active {
|
|
105
|
-
border-bottom: 2px solid #666;
|
|
106
|
-
color: #666;
|
|
101
|
+
div#typeahead-container {
|
|
102
|
+
max-width: 660px;
|
|
103
|
+
margin: 0 auto 15px;
|
|
104
|
+
position: relative;
|
|
107
105
|
}
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
div#typeahead-container div.embed-link {
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 0;
|
|
109
|
+
right: 0;
|
|
110
|
+
display: inline-block;
|
|
111
|
+
z-index: 2;
|
|
110
112
|
}
|
|
111
|
-
|
|
113
|
+
div#typeahead-container div.embed-link a {
|
|
114
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, sans-serif;
|
|
112
115
|
color: #bbb;
|
|
113
116
|
font-size: 12px;
|
|
117
|
+
text-decoration: none !important;
|
|
114
118
|
}
|
|
115
|
-
|
|
119
|
+
div#typeahead-container div.embed-link a:hover {
|
|
116
120
|
color: #666;
|
|
117
121
|
}
|
|
118
122
|
|
|
123
|
+
div#preview-label {
|
|
124
|
+
margin: 0 auto;
|
|
125
|
+
}
|
|
126
|
+
div#preview-label p {
|
|
127
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, sans-serif;
|
|
128
|
+
color: #aaa;
|
|
129
|
+
font-size: 0.75rem;
|
|
130
|
+
margin: 0 0 0.25rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
119
133
|
#frame-parent {
|
|
120
134
|
border: 1px solid #ddd;
|
|
121
135
|
margin: 0 auto;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Fuzzy
|
|
3
|
+
* https://github.com/myork/fuzzy
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2012 Matt York
|
|
6
|
+
* Licensed under the MIT license.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fuzzy = {};
|
|
10
|
+
|
|
11
|
+
// Return all elements of `array` that have a fuzzy
|
|
12
|
+
// match against `pattern`.
|
|
13
|
+
fuzzy.simpleFilter = function (pattern, array) {
|
|
14
|
+
return array.filter(function (str) {
|
|
15
|
+
return fuzzy.test(pattern, str);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Does `pattern` fuzzy match `str`?
|
|
20
|
+
fuzzy.test = function (pattern, str) {
|
|
21
|
+
return fuzzy.match(pattern, str) !== null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// If `pattern` matches `str`, wrap each matching character
|
|
25
|
+
// in `opts.pre` and `opts.post`. If no match, return null
|
|
26
|
+
fuzzy.match = function (pattern, str, opts) {
|
|
27
|
+
opts = opts || {};
|
|
28
|
+
let patternIdx = 0;
|
|
29
|
+
const result = [];
|
|
30
|
+
const len = str.length;
|
|
31
|
+
let totalScore = 0;
|
|
32
|
+
let currScore = 0;
|
|
33
|
+
// prefix
|
|
34
|
+
const pre = opts.pre || '';
|
|
35
|
+
// suffix
|
|
36
|
+
const post = opts.post || '';
|
|
37
|
+
// String to compare against. This might be a lowercase version of the
|
|
38
|
+
// raw string
|
|
39
|
+
const compareString = (opts.caseSensitive && str) || str.toLowerCase();
|
|
40
|
+
let ch;
|
|
41
|
+
|
|
42
|
+
pattern = (opts.caseSensitive && pattern) || pattern.toLowerCase();
|
|
43
|
+
|
|
44
|
+
// For each character in the string, either add it to the result
|
|
45
|
+
// or wrap in template if it's the next string in the pattern
|
|
46
|
+
for (let idx = 0; idx < len; idx++) {
|
|
47
|
+
ch = str[idx];
|
|
48
|
+
if (compareString[idx] === pattern[patternIdx]) {
|
|
49
|
+
ch = pre + ch + post;
|
|
50
|
+
patternIdx += 1;
|
|
51
|
+
|
|
52
|
+
// consecutive characters should increase the score more than linearly
|
|
53
|
+
currScore += 1 + currScore;
|
|
54
|
+
} else {
|
|
55
|
+
currScore = 0;
|
|
56
|
+
}
|
|
57
|
+
totalScore += currScore;
|
|
58
|
+
result[result.length] = ch;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// return rendered string if we have a match for every char
|
|
62
|
+
if (patternIdx === pattern.length) {
|
|
63
|
+
// if the string is an exact match with pattern, totalScore should be maxed
|
|
64
|
+
totalScore = compareString === pattern ? Infinity : totalScore;
|
|
65
|
+
return { rendered: result.join(''), score: totalScore };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// The normal entry point. Filters `arr` for matches against `pattern`.
|
|
72
|
+
// It returns an array with matching values of the type:
|
|
73
|
+
//
|
|
74
|
+
// [{
|
|
75
|
+
// string: '<b>lah' // The rendered string
|
|
76
|
+
// , index: 2 // The index of the element in `arr`
|
|
77
|
+
// , original: 'blah' // The original element in `arr`
|
|
78
|
+
// }]
|
|
79
|
+
//
|
|
80
|
+
// `opts` is an optional argument bag. Details:
|
|
81
|
+
//
|
|
82
|
+
// opts = {
|
|
83
|
+
// // string to put before a matching character
|
|
84
|
+
// pre: '<b>'
|
|
85
|
+
//
|
|
86
|
+
// // string to put after matching character
|
|
87
|
+
// , post: '</b>'
|
|
88
|
+
//
|
|
89
|
+
// // Optional function. Input is an entry in the given arr`,
|
|
90
|
+
// // output should be the string to test `pattern` against.
|
|
91
|
+
// // In this example, if `arr = [{crying: 'koala'}]` we would return
|
|
92
|
+
// // 'koala'.
|
|
93
|
+
// , extract: function(arg) { return arg.crying; }
|
|
94
|
+
// }
|
|
95
|
+
fuzzy.filter = function (pattern, arr, opts) {
|
|
96
|
+
if (!arr || arr.length === 0) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
if (typeof pattern !== 'string') {
|
|
100
|
+
return arr;
|
|
101
|
+
}
|
|
102
|
+
opts = opts || {};
|
|
103
|
+
return (
|
|
104
|
+
arr
|
|
105
|
+
.reduce(function (prev, element, idx, arr) {
|
|
106
|
+
let str = element;
|
|
107
|
+
if (opts.extract) {
|
|
108
|
+
str = opts.extract(element);
|
|
109
|
+
}
|
|
110
|
+
const rendered = fuzzy.match(pattern, str, opts);
|
|
111
|
+
if (rendered != null) {
|
|
112
|
+
prev[prev.length] = {
|
|
113
|
+
string: rendered.rendered,
|
|
114
|
+
score: rendered.score,
|
|
115
|
+
index: idx,
|
|
116
|
+
original: element,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return prev;
|
|
120
|
+
}, [])
|
|
121
|
+
|
|
122
|
+
// Sort by score. Browsers are inconsistent wrt stable/unstable
|
|
123
|
+
// sorting, so force stable by using the index in the case of tie.
|
|
124
|
+
// See http://ofb.net/~sethml/is-sort-stable.html
|
|
125
|
+
.sort(function (a, b) {
|
|
126
|
+
const compare = b.score - a.score;
|
|
127
|
+
if (compare) return compare;
|
|
128
|
+
return a.index - b.index;
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default fuzzy;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} TItem
|
|
4
|
+
* @property {number} index
|
|
5
|
+
* @property {string} embed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export let id = 'typeahead-' + Math.random().toString(36);
|
|
9
|
+
export let value = '';
|
|
10
|
+
|
|
11
|
+
/** @type {TItem[]} */
|
|
12
|
+
export let data = [];
|
|
13
|
+
|
|
14
|
+
/** @type {(item: TItem) => any} */
|
|
15
|
+
export let extract = (item) => item;
|
|
16
|
+
|
|
17
|
+
/** @type {(item: TItem) => boolean} */
|
|
18
|
+
export let disable = (item) => false;
|
|
19
|
+
|
|
20
|
+
/** @type {(item: TItem) => boolean} */
|
|
21
|
+
export let filter = (item) => false;
|
|
22
|
+
|
|
23
|
+
/** Set to `false` to prevent the first result from being selected */
|
|
24
|
+
export let autoselect = true;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Set to `keep` to keep the search field unchanged after select, set to `clear` to auto-clear search field
|
|
28
|
+
* @type {"update" | "clear" | "keep"}
|
|
29
|
+
*/
|
|
30
|
+
export let inputAfterSelect = 'update';
|
|
31
|
+
|
|
32
|
+
/** @type {{ original: TItem; index: number; score: number; string: string; disabled?: boolean; }[]} */
|
|
33
|
+
export let results = [];
|
|
34
|
+
|
|
35
|
+
/** Set to `true` to re-focus the input after selecting a result */
|
|
36
|
+
export let focusAfterSelect = false;
|
|
37
|
+
|
|
38
|
+
/** Set to `true` to only show results when the input is focused */
|
|
39
|
+
export let showDropdownOnFocus = false;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Specify the maximum number of results to return
|
|
43
|
+
* @type {number}
|
|
44
|
+
*/
|
|
45
|
+
export let limit = Infinity;
|
|
46
|
+
|
|
47
|
+
import fuzzy from './fuzzy.js';
|
|
48
|
+
import Search from 'svelte-search';
|
|
49
|
+
import { tick, createEventDispatcher, afterUpdate } from 'svelte';
|
|
50
|
+
|
|
51
|
+
const dispatch = createEventDispatcher();
|
|
52
|
+
|
|
53
|
+
let comboboxRef = null;
|
|
54
|
+
let searchRef = null;
|
|
55
|
+
let hideDropdown = false;
|
|
56
|
+
let selectedIndex = -1;
|
|
57
|
+
let prevResults = '';
|
|
58
|
+
let isFocused = false;
|
|
59
|
+
|
|
60
|
+
afterUpdate(() => {
|
|
61
|
+
if (prevResults !== resultsId && autoselect) {
|
|
62
|
+
selectedIndex = getNextNonDisabledIndex();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (prevResults !== resultsId && !$$slots['no-results']) {
|
|
66
|
+
hideDropdown = results.length === 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
prevResults = resultsId;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
async function select() {
|
|
73
|
+
const result = results[selectedIndex];
|
|
74
|
+
|
|
75
|
+
if (result.disabled) return;
|
|
76
|
+
|
|
77
|
+
const selectedValue = extract(result.original);
|
|
78
|
+
const searchedValue = value;
|
|
79
|
+
|
|
80
|
+
if (inputAfterSelect === 'clear') value = '';
|
|
81
|
+
if (inputAfterSelect === 'update') value = selectedValue;
|
|
82
|
+
|
|
83
|
+
dispatch('select', {
|
|
84
|
+
selectedIndex,
|
|
85
|
+
searched: searchedValue,
|
|
86
|
+
selected: selectedValue,
|
|
87
|
+
original: result.original,
|
|
88
|
+
originalIndex: result.index,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await tick();
|
|
92
|
+
|
|
93
|
+
if (focusAfterSelect) searchRef.focus();
|
|
94
|
+
close();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @type {() => number} */
|
|
98
|
+
function getNextNonDisabledIndex() {
|
|
99
|
+
let index = 0;
|
|
100
|
+
let disabled = results[index]?.disabled ?? false;
|
|
101
|
+
|
|
102
|
+
while (disabled) {
|
|
103
|
+
if (index === results.length) {
|
|
104
|
+
index = 0;
|
|
105
|
+
} else {
|
|
106
|
+
index += 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
disabled = results[index]?.disabled ?? false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return index;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @type {(direction: -1 | 1) => void} */
|
|
116
|
+
function change(direction) {
|
|
117
|
+
let index =
|
|
118
|
+
direction === 1 && selectedIndex === results.length - 1
|
|
119
|
+
? 0
|
|
120
|
+
: selectedIndex + direction;
|
|
121
|
+
if (index < 0) index = results.length - 1;
|
|
122
|
+
|
|
123
|
+
let disabled = results[index].disabled;
|
|
124
|
+
|
|
125
|
+
while (disabled) {
|
|
126
|
+
if (index === results.length) {
|
|
127
|
+
index = 0;
|
|
128
|
+
} else {
|
|
129
|
+
index += direction;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
disabled = results[index].disabled;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
selectedIndex = index;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const open = () => (hideDropdown = false);
|
|
139
|
+
const close = () => (hideDropdown = true);
|
|
140
|
+
|
|
141
|
+
$: options = { pre: '<mark>', post: '</mark>', extract };
|
|
142
|
+
$: results =
|
|
143
|
+
value !== ''
|
|
144
|
+
? fuzzy
|
|
145
|
+
.filter(value, data, options)
|
|
146
|
+
.filter(({ score }) => score > 0)
|
|
147
|
+
.slice(0, limit)
|
|
148
|
+
.filter((result) => !filter(result.original))
|
|
149
|
+
.map((result) => ({ ...result, disabled: disable(result.original) }))
|
|
150
|
+
: data.map((d) => ({ string: extract(d), original: d }));
|
|
151
|
+
|
|
152
|
+
$: resultsId = results.map((result) => extract(result.original)).join('');
|
|
153
|
+
$: showResults = !hideDropdown && results.length > 0 && isFocused;
|
|
154
|
+
$: if (showDropdownOnFocus) {
|
|
155
|
+
showResults = showResults && isFocused;
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<svelte:window
|
|
160
|
+
on:click="{({ target }) => {
|
|
161
|
+
if (!hideDropdown && !comboboxRef?.contains(target)) {
|
|
162
|
+
close();
|
|
163
|
+
}
|
|
164
|
+
}}"
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
<div
|
|
168
|
+
data-svelte-typeahead
|
|
169
|
+
bind:this="{comboboxRef}"
|
|
170
|
+
role="combobox"
|
|
171
|
+
aria-haspopup="listbox"
|
|
172
|
+
aria-owns="{id}-listbox"
|
|
173
|
+
class:dropdown="{results.length > 0}"
|
|
174
|
+
aria-expanded="{showResults}"
|
|
175
|
+
id="{id}-typeahead"
|
|
176
|
+
>
|
|
177
|
+
<Search
|
|
178
|
+
id="{id}"
|
|
179
|
+
removeFormAriaAttributes="{true}"
|
|
180
|
+
{...$$restProps}
|
|
181
|
+
bind:ref="{searchRef}"
|
|
182
|
+
aria-autocomplete="list"
|
|
183
|
+
aria-controls="{id}-listbox"
|
|
184
|
+
aria-labelledby="{id}-label"
|
|
185
|
+
aria-activedescendant="{selectedIndex >= 0 &&
|
|
186
|
+
!hideDropdown &&
|
|
187
|
+
results.length > 0
|
|
188
|
+
? `${id}-result-${selectedIndex}`
|
|
189
|
+
: null}"
|
|
190
|
+
bind:value
|
|
191
|
+
on:type
|
|
192
|
+
on:input
|
|
193
|
+
on:change
|
|
194
|
+
on:focus
|
|
195
|
+
on:focus="{() => {
|
|
196
|
+
open();
|
|
197
|
+
if (showDropdownOnFocus) {
|
|
198
|
+
showResults = true;
|
|
199
|
+
isFocused = true;
|
|
200
|
+
}
|
|
201
|
+
}}"
|
|
202
|
+
on:clear
|
|
203
|
+
on:clear="{open}"
|
|
204
|
+
on:blur
|
|
205
|
+
on:keydown
|
|
206
|
+
on:keydown="{(e) => {
|
|
207
|
+
if (results.length === 0) return;
|
|
208
|
+
|
|
209
|
+
switch (e.key) {
|
|
210
|
+
case 'Enter':
|
|
211
|
+
select();
|
|
212
|
+
break;
|
|
213
|
+
case 'ArrowDown':
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
change(1);
|
|
216
|
+
break;
|
|
217
|
+
case 'ArrowUp':
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
change(-1);
|
|
220
|
+
break;
|
|
221
|
+
case 'Escape':
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
value = '';
|
|
224
|
+
searchRef?.focus();
|
|
225
|
+
close();
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}}"
|
|
229
|
+
/>
|
|
230
|
+
<ul
|
|
231
|
+
class:svelte-typeahead-list="{true}"
|
|
232
|
+
role="listbox"
|
|
233
|
+
aria-labelledby="{id}-label"
|
|
234
|
+
id="{id}-listbox"
|
|
235
|
+
>
|
|
236
|
+
{#if showResults}
|
|
237
|
+
{#each results as result, index}
|
|
238
|
+
<li
|
|
239
|
+
role="option"
|
|
240
|
+
id="{id}-result-{index}"
|
|
241
|
+
class:selected="{selectedIndex === index}"
|
|
242
|
+
class:disabled="{result.disabled}"
|
|
243
|
+
aria-selected="{selectedIndex === index}"
|
|
244
|
+
on:click="{() => {
|
|
245
|
+
if (result.disabled) return;
|
|
246
|
+
selectedIndex = index;
|
|
247
|
+
select();
|
|
248
|
+
}}"
|
|
249
|
+
on:mouseenter="{() => {
|
|
250
|
+
if (result.disabled) return;
|
|
251
|
+
selectedIndex = index;
|
|
252
|
+
}}"
|
|
253
|
+
>
|
|
254
|
+
<slot result="{result}" index="{index}" value="{value}">
|
|
255
|
+
{@html result.string}
|
|
256
|
+
</slot>
|
|
257
|
+
</li>
|
|
258
|
+
{/each}
|
|
259
|
+
{/if}
|
|
260
|
+
{#if $$slots['no-results'] && !hideDropdown && value.length > 0 && results.length === 0}
|
|
261
|
+
<div class:no-results="{true}">
|
|
262
|
+
<slot name="no-results" value="{value}" />
|
|
263
|
+
</div>
|
|
264
|
+
{/if}
|
|
265
|
+
</ul>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<style>[data-svelte-typeahead] {
|
|
269
|
+
position: relative;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
ul {
|
|
273
|
+
position: absolute;
|
|
274
|
+
top: 100%;
|
|
275
|
+
left: 0;
|
|
276
|
+
width: calc(100% - 2px);
|
|
277
|
+
padding: 0;
|
|
278
|
+
margin: 0;
|
|
279
|
+
list-style: none;
|
|
280
|
+
background-color: inherit;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[aria-expanded=true] ul {
|
|
284
|
+
z-index: 1;
|
|
285
|
+
border: 1px solid #ddd;
|
|
286
|
+
max-height: 50vh;
|
|
287
|
+
overflow-y: scroll;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
li,
|
|
291
|
+
.no-results {
|
|
292
|
+
padding: 0.25rem 1rem;
|
|
293
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, sans-serif;
|
|
294
|
+
color: #333;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
li {
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
}
|
|
300
|
+
li :global(mark) {
|
|
301
|
+
padding: 0;
|
|
302
|
+
background-color: #ffff9a;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
li:not(:last-of-type) {
|
|
306
|
+
border-bottom: 1px solid #e0e0e0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
li:hover {
|
|
310
|
+
background-color: #efefef;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.selected {
|
|
314
|
+
background-color: #efefef;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.selected:hover {
|
|
318
|
+
background-color: #e5e5e5;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.disabled {
|
|
322
|
+
opacity: 0.4;
|
|
323
|
+
cursor: not-allowed;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
:global([data-svelte-search] label) {
|
|
327
|
+
margin-bottom: 0.25rem;
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
font-size: 0.75rem;
|
|
330
|
+
color: #aaa;
|
|
331
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, sans-serif;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
:global([data-svelte-search] input) {
|
|
335
|
+
width: 100%;
|
|
336
|
+
padding: 0.5rem 0.75rem;
|
|
337
|
+
background: none;
|
|
338
|
+
font-size: 1rem;
|
|
339
|
+
border: 0;
|
|
340
|
+
border-radius: 0 !important;
|
|
341
|
+
background-color: #fff;
|
|
342
|
+
border: 1px solid #ddd;
|
|
343
|
+
font-family: var(--theme-font-family-sans-serif, "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
:global([data-svelte-search] input:focus) {
|
|
347
|
+
outline: none;
|
|
348
|
+
border: 1px solid #ccc;
|
|
349
|
+
}</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reuters-graphics/graphics-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://reuters-graphics.github.io/graphics-components",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"pym.js": "^1.3.2",
|
|
90
90
|
"svelte-fa": "^2.4.0",
|
|
91
91
|
"svelte-intersection-observer": "^0.10.0",
|
|
92
|
+
"svelte-search": "^2.0.1",
|
|
92
93
|
"ua-parser-js": "^0.7.27"
|
|
93
94
|
},
|
|
94
95
|
"exports": {
|
|
@@ -105,6 +106,8 @@
|
|
|
105
106
|
"./components/FeaturePhoto/FeaturePhoto.svelte": "./dist/components/FeaturePhoto/FeaturePhoto.svelte",
|
|
106
107
|
"./components/Framer/Framer.svelte": "./dist/components/Framer/Framer.svelte",
|
|
107
108
|
"./components/Framer/Resizer/index.svelte": "./dist/components/Framer/Resizer/index.svelte",
|
|
109
|
+
"./components/Framer/Typeahead/fuzzy": "./dist/components/Framer/Typeahead/fuzzy.js",
|
|
110
|
+
"./components/Framer/Typeahead/index.svelte": "./dist/components/Framer/Typeahead/index.svelte",
|
|
108
111
|
"./components/Framer/stores": "./dist/components/Framer/stores.js",
|
|
109
112
|
"./components/Framer/uniqNames": "./dist/components/Framer/uniqNames.js",
|
|
110
113
|
"./components/GraphicBlock/AriaHidden.svelte": "./dist/components/GraphicBlock/AriaHidden.svelte",
|