perf: replace Svelte with vanilla JS (#381)
This commit is contained in:
parent
750e8493e3
commit
56992858c0
|
@ -123,8 +123,6 @@ dist
|
|||
/trimEmojiData.js.map
|
||||
/trimEmojiData.cjs
|
||||
/trimEmojiData.cjs.map
|
||||
/svelte.js
|
||||
/svelte.js.map
|
||||
|
||||
/docs-tmp
|
||||
/ts-tmp
|
||||
|
|
|
@ -59,15 +59,32 @@ Build the GitHub Pages docs site:
|
|||
|
||||
Some explanations of why the code is structured the way it is, in case it's confusing.
|
||||
|
||||
### Why is it one big Svelte component?
|
||||
### Why a custom framework?
|
||||
|
||||
When you build Svelte components with `customElement: true`, it makes _each individual component_ into a web component. This can be bad for perf reasons (lots of repetition, [constructible stylesheets](https://wicg.github.io/construct-stylesheets/) aren't a thing yet, event and prop overhead) as well as correctness reasons (e.g. I want an `<li>` inside of a `<ul>`, not a `<custom-element>` with a shadow DOM and the `<li>` inside of it).
|
||||
It was [a good learning exercise](https://nolanlawson.com/2023/12/02/lets-learn-how-modern-javascript-frameworks-work-by-building-one/), and it reduced the bundle size quite a bit to switch from Svelte to a custom framework. Plus, `emoji-picker-element` no longer needs to keep
|
||||
up with breaking changes in Svelte or the tools in the Svelte ecosystem (e.g. Rollup and Jest plugins).
|
||||
|
||||
So for now: it's one big component.
|
||||
### What are some of the quirks of the custom framework?
|
||||
|
||||
### Why use svelte-preprocess?
|
||||
The framework mostly gets the job done, but I took a few shortcuts since we didn't need all the possible bells and whistles. Here is a brief description.
|
||||
|
||||
Since it's one big component, it's more readable if we split up the HTML/CSS/JS. Plus, we can lint the JS more easily that way. Plus, I like SCSS.
|
||||
First, all the DOM nodes and update functions for those nodes are kept in-memory via a `WeakMap` where the key is the `state`. There's one `state` per instance of the `Picker.js` Svelte-esque component. So when the instance is GC'ed, everything related to the DOM and update functions should be GC'ed. (The exception is the global `parseCache`, which only contains the clone-able `template` and bindings for each unique `tokens` array, which is unique per `html` tag template literal. These templates/bindings never changes per component instance, so it makes sense to just parse once and cache them forever, in case the `<emoji-picker>` element itself is constantly unmounted and re-created.)
|
||||
|
||||
Second, I took a shortcut, which is that all unique (non-`<template>`) DOM nodes and update functions are keyed off of 1) the unique tokens for the tag template literal plus 2) a unique `key` from the `map` function (if it exists). These are only GC'ed when the whole `state` is GC'ed. So in the worst case, every DOM node for every emoji in the picker is kept in memory (e.g. if you click on every tab button), but this seemed like a reasonable tradeoff for simplicity, plus the perf improvement of avoiding re-rendering the same node when it's unchanged (this is especially important if the reactivity system is kind of chatty, and is constantly setting the same arrays over and over – the framework just notices that all the `children` are the same objects and doesn't re-render). This also works because the `map`ed DOM nodes are not highly dynamic.
|
||||
|
||||
Third, all refs and event listeners are only bound once – this just happens to work since most of the event listeners are hoisted (delegated) anyway.
|
||||
|
||||
Fourth, `map`ped iterations without a single top-level element are unsupported – this makes updating iterations much easier, since I can just use `Element.replaceChildren()` instead of having to keep bookmark comment nodes or something.
|
||||
|
||||
Fifth, the reactivity system is really bare-bones and doesn't check for cycles or avoid wasteful re-renderings or anything. So there's a lot of guardrails to avoid setting the same object over and over to avoid infinite cycles or to avoid excessive re-renders.
|
||||
|
||||
Sixth, I tried to get fine-grained reactivity working but gave up, so basically the whole top-level `PickerTemplate.js` function is executed over and over again anytime anything changes. So there are guardrails in place to make sure this isn't expensive (e.g. the caching mechanisms described above).
|
||||
|
||||
There's also a long tail of things that aren't supported in the HTML parser, like funky characters like `<` and `=` inside of text nodes, which could confuse the parser (so I just don't support them).
|
||||
|
||||
Also, it's assumed that we're using some kind of minifier for the HTML tagged template literals – it would be annoying to have to author `PickerTemplate.js` without any whitespace. So the parser doesn't support comments since those are assumed to be stripped out anyway.
|
||||
|
||||
That's about it, there are probably bugs in the framework if you tried to use it for something other than `emoji-picker-element`, but that's fine – it only needs to support one component anyway.
|
||||
|
||||
### Why are the built JS files at the root of the project?
|
||||
|
||||
|
@ -77,4 +94,4 @@ I could also build a `pkg/` directory and copy the `package.json` into it (this
|
|||
|
||||
### Why build two separate bundles?
|
||||
|
||||
`picker.js` and `database.js` are designed to be independentally `import`-able. The only way to do this correctly with the right behavior from bundlers like Rollup and Webpack is to create two separate files. Otherwise the bundler would not be able to tree-shake `picker` from `database`.
|
||||
`picker.js` and `database.js` are designed to be independently `import`-able. The only way to do this correctly with the right behavior from bundlers like Rollup and Webpack is to create two separate files. Otherwise the bundler would not be able to tree-shake `picker` from `database`.
|
||||
|
|
15
README.md
15
README.md
|
@ -827,17 +827,22 @@ The reason for this is that `Picker` automatically registers itself as a custom
|
|||
|
||||
### Within a Svelte project
|
||||
|
||||
`emoji-picker-element` is explicitly designed as a custom element, and won't work
|
||||
as a direct Svelte component. However, if you're already using Svelte 3, then you
|
||||
can avoid importing Svelte twice by using:
|
||||
> [!WARNING]
|
||||
> `emoji-picker-element` is no longer based on Svelte, so importing from `emoji-picker-element/svelte` is now deprecated.
|
||||
|
||||
Previously, `emoji-picker-element` was based on Svelte v3/v4, and you could do:
|
||||
|
||||
```js
|
||||
import Picker from 'emoji-picker-element/svelte';
|
||||
```
|
||||
|
||||
`svelte.js` is the same as `picker.js`, except it `import`s Svelte rather than bundling it.
|
||||
The goal was to slightly reduce the bundle size by sharing common `svelte` imports.
|
||||
|
||||
While this option can reduce your bundle size, note that it only works with compatible Svelte versions. Currently Svelte v3 and v4 are supported.
|
||||
This is still supported for backwards compatibility, but it is deprecated and just re-exports the Picker. Instead, do:
|
||||
|
||||
```js
|
||||
import Picker from 'emoji-picker-element/picker';
|
||||
```
|
||||
|
||||
## Data and offline
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import { promisify } from 'node:util'
|
|||
import prettyBytes from 'pretty-bytes'
|
||||
import fs from 'node:fs/promises'
|
||||
|
||||
const MAX_SIZE_MIN = '42.7 kB'
|
||||
const MAX_SIZE_MINGZ = '15 kB'
|
||||
const MAX_SIZE_MIN = '37 kB'
|
||||
const MAX_SIZE_MINGZ = '13 kB'
|
||||
|
||||
const FILENAME = './bundle.js'
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { minifyHTMLLiterals } from 'minify-html-literals'
|
||||
|
||||
export default {
|
||||
processAsync (source, fileName) {
|
||||
return minifyHTMLLiterals(source, {
|
||||
fileName
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import preprocess from 'svelte-preprocess'
|
||||
|
||||
export default {
|
||||
preprocess: preprocess()
|
||||
}
|
|
@ -4,15 +4,9 @@ module.exports = {
|
|||
'<rootDir>/test/spec/**/*.{spec,test}.{js,jsx,ts,tsx}'
|
||||
],
|
||||
transform: {
|
||||
'^.+\\.svelte$': ['svelte-jester', {
|
||||
preprocess: './config/svelte.config.js',
|
||||
compilerOptions: {
|
||||
dev: false
|
||||
}
|
||||
}]
|
||||
'^.*PickerTemplate.js$': './config/minifyHtmlInJest.js'
|
||||
},
|
||||
moduleFileExtensions: ['js', 'svelte'],
|
||||
extensionsToTreatAsEsm: ['.svelte'],
|
||||
moduleFileExtensions: ['js'],
|
||||
testPathIgnorePatterns: ['node_modules'],
|
||||
bail: true,
|
||||
verbose: true,
|
||||
|
@ -31,12 +25,6 @@ module.exports = {
|
|||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100
|
||||
},
|
||||
'./src/picker/components/Picker/Picker.svelte': {
|
||||
statements: 90,
|
||||
branches: 85,
|
||||
functions: 90,
|
||||
lines: 90
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
package.json
12
package.json
|
@ -62,8 +62,7 @@
|
|||
"custom",
|
||||
"element",
|
||||
"web",
|
||||
"component",
|
||||
"svelte"
|
||||
"component"
|
||||
],
|
||||
"author": "Nolan Lawson <nolan@nolanlawson.com>",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -101,9 +100,9 @@
|
|||
"jest-environment-jsdom": "^29.7.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"magic-string": "^0.30.5",
|
||||
"markdown-table": "^3.0.2",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"minify-html-literals": "^1.3.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"playwright": "^1.40.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
|
@ -111,7 +110,6 @@
|
|||
"recursive-readdir": "^2.2.3",
|
||||
"rollup": "^4.7.0",
|
||||
"rollup-plugin-analyzer": "^4.0.0",
|
||||
"rollup-plugin-svelte": "^7.1.6",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"sass": "^1.69.5",
|
||||
"shx": "^0.3.4",
|
||||
|
@ -120,9 +118,6 @@
|
|||
"stylelint": "^15.11.0",
|
||||
"stylelint-config-recommended-scss": "^13.1.0",
|
||||
"stylelint-scss": "^5.3.1",
|
||||
"svelte": "^4.2.8",
|
||||
"svelte-jester": "^3.0.0",
|
||||
"svelte-preprocess": "^5.1.1",
|
||||
"svgo": "^3.0.5",
|
||||
"tachometer": "^0.7.0",
|
||||
"terser": "^5.26.0"
|
||||
|
@ -151,11 +146,14 @@
|
|||
"Event",
|
||||
"fetch",
|
||||
"getComputedStyle",
|
||||
"Element",
|
||||
"indexedDB",
|
||||
"IDBKeyRange",
|
||||
"Headers",
|
||||
"HTMLElement",
|
||||
"matchMedia",
|
||||
"Node",
|
||||
"NodeFilter",
|
||||
"performance",
|
||||
"ResizeObserver",
|
||||
"Response",
|
||||
|
|
|
@ -1,31 +1,14 @@
|
|||
import MagicString from 'magic-string'
|
||||
import inject from '@rollup/plugin-inject'
|
||||
import cjs from '@rollup/plugin-commonjs'
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import strip from '@rollup/plugin-strip'
|
||||
import svelte from 'rollup-plugin-svelte'
|
||||
import preprocess from 'svelte-preprocess'
|
||||
import analyze from 'rollup-plugin-analyzer'
|
||||
import { buildStyles } from './bin/buildStyles.js'
|
||||
import { minifyHTMLLiterals } from 'minify-html-literals'
|
||||
|
||||
const { NODE_ENV, DEBUG } = process.env
|
||||
const dev = NODE_ENV !== 'production'
|
||||
|
||||
const preprocessConfig = preprocess()
|
||||
|
||||
const origMarkup = preprocessConfig.markup
|
||||
// minify the HTML by removing extra whitespace
|
||||
// TODO: this is fragile, but it also results in a lot of bundlesize savings. let's find a better solution
|
||||
preprocessConfig.markup = async function () {
|
||||
const res = await origMarkup.apply(this, arguments)
|
||||
|
||||
// remove whitespace
|
||||
res.code = res.code.replace(/([>}])\s+([<{])/sg, '$1$2')
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Build Database.test.js and Picker.js as separate modules at build times so that they are properly tree-shakeable.
|
||||
// Most of this has to happen because customElements.define() has side effects
|
||||
const baseConfig = {
|
||||
|
@ -43,25 +26,18 @@ const baseConfig = {
|
|||
delimiters: ['', ''],
|
||||
preventAssignment: true
|
||||
}),
|
||||
svelte({
|
||||
compilerOptions: {
|
||||
dev,
|
||||
discloseVersion: false
|
||||
},
|
||||
preprocess: preprocessConfig
|
||||
}),
|
||||
// make the svelte output slightly smaller
|
||||
replace({
|
||||
'options.anchor': 'undefined',
|
||||
'options.context': 'undefined',
|
||||
'options.customElement': 'undefined',
|
||||
'options.hydrate': 'undefined',
|
||||
'options.intro': 'undefined',
|
||||
delimiters: ['', ''],
|
||||
preventAssignment: true
|
||||
}),
|
||||
{
|
||||
name: 'minify-html-in-tag-template-literals',
|
||||
transform (content, id) {
|
||||
if (id.includes('PickerTemplate.js')) {
|
||||
return minifyHTMLLiterals(content, {
|
||||
fileName: id
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
strip({
|
||||
include: ['**/*.js', '**/*.svelte'],
|
||||
include: ['**/*.js'],
|
||||
functions: [
|
||||
(!dev && !process.env.PERF) && 'performance.*',
|
||||
!dev && 'console.log'
|
||||
|
@ -78,18 +54,7 @@ const baseConfig = {
|
|||
const entryPoints = [
|
||||
{
|
||||
input: './src/picker/PickerElement.js',
|
||||
output: './picker.js',
|
||||
plugins: [
|
||||
// Replace newer syntax in Svelte v4 to avoid breaking iOS <13.4
|
||||
// https://github.com/nolanlawson/emoji-picker-element/pull/379
|
||||
replace({
|
||||
'array_like_or_iterator?.length': 'array_like_or_iterator && array_like_or_iterator.length',
|
||||
'$$ = undefined;': '', // not necessary to initialize class prop to undefined
|
||||
'$$set = undefined;': '', // not necessary to initialize class prop to undefined
|
||||
delimiters: ['', ''],
|
||||
preventAssignment: true
|
||||
})
|
||||
]
|
||||
output: './picker.js'
|
||||
},
|
||||
{
|
||||
input: './src/database/Database.js',
|
||||
|
@ -103,42 +68,10 @@ const entryPoints = [
|
|||
input: './src/trimEmojiData.js',
|
||||
output: './trimEmojiData.cjs',
|
||||
format: 'cjs'
|
||||
},
|
||||
{
|
||||
input: './src/picker/PickerElement.js',
|
||||
output: './svelte.js',
|
||||
external: ['svelte', 'svelte/internal'],
|
||||
// TODO: drop Svelte v3 support
|
||||
// ensure_array_like was added in Svelte v4 - we shim it to avoid breaking Svelte v3 users
|
||||
plugins: [
|
||||
{
|
||||
name: 'svelte-v3-compat',
|
||||
transform (source) {
|
||||
const magicString = new MagicString(source)
|
||||
magicString.replaceAll('ensure_array_like(', 'ensure_array_like_shim(')
|
||||
|
||||
return {
|
||||
code: magicString.toString(),
|
||||
map: magicString.generateMap()
|
||||
}
|
||||
}
|
||||
},
|
||||
inject({
|
||||
ensure_array_like_shim: [
|
||||
'../../../../shims/svelte-v3-shim.js',
|
||||
'ensure_array_like_shim'
|
||||
]
|
||||
})
|
||||
],
|
||||
onwarn (warning) {
|
||||
if (!warning.message.includes('ensure_array_like')) { // intentionally ignore warning for unused import
|
||||
console.warn(warning.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export default entryPoints.map(({ input, output, format = 'es', external = [], plugins = [], onwarn }) => {
|
||||
export default entryPoints.map(({ input, output, format = 'es', external = [], onwarn }) => {
|
||||
return {
|
||||
input,
|
||||
output: {
|
||||
|
@ -148,7 +81,7 @@ export default entryPoints.map(({ input, output, format = 'es', external = [], p
|
|||
exports: 'auto'
|
||||
},
|
||||
external: [...baseConfig.external, ...external],
|
||||
plugins: [...baseConfig.plugins, ...plugins],
|
||||
plugins: baseConfig.plugins,
|
||||
onwarn
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// TODO: drop Svelte v3 support
|
||||
// ensure_array_like was added in Svelte v4 - we shim it to avoid breaking Svelte v3 users
|
||||
// this code is copied from svelte v4
|
||||
/* eslint-disable camelcase */
|
||||
export function ensure_array_like_shim (array_like_or_iterator) {
|
||||
return (array_like_or_iterator && array_like_or_iterator.length !== undefined)
|
||||
? array_like_or_iterator
|
||||
: Array.from(array_like_or_iterator)
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import SveltePicker from './components/Picker/Picker.svelte'
|
||||
import { createRoot } from './components/Picker/Picker.js'
|
||||
import { DEFAULT_DATA_SOURCE, DEFAULT_LOCALE } from '../database/constants'
|
||||
import { DEFAULT_CATEGORY_SORTING, DEFAULT_SKIN_TONE_EMOJI, FONT_FAMILY } from './constants'
|
||||
import enI18n from './i18n/en.js'
|
||||
import Database from './ImportedDatabase'
|
||||
import { queueMicrotask } from './utils/queueMicrotask.js'
|
||||
|
||||
const PROPS = [
|
||||
'customEmoji',
|
||||
|
@ -51,17 +52,14 @@ export default class PickerElement extends HTMLElement {
|
|||
// The _cmp may be defined if the component was immediately disconnected and then reconnected. In that case,
|
||||
// do nothing (preserve the state)
|
||||
if (!this._cmp) {
|
||||
this._cmp = new SveltePicker({
|
||||
target: this.shadowRoot,
|
||||
props: this._ctx
|
||||
})
|
||||
this._cmp = createRoot(this.shadowRoot, this._ctx)
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
// Check in a microtask if the element is still connected. If so, treat this as a "move" rather than a disconnect
|
||||
// Inspired by Vue: https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue
|
||||
Promise.resolve().then(() => {
|
||||
queueMicrotask(() => {
|
||||
// this._cmp may be defined if connect-disconnect-connect-disconnect occurs synchronously
|
||||
if (!this.isConnected && this._cmp) {
|
||||
this._cmp.$destroy()
|
||||
|
@ -110,7 +108,7 @@ export default class PickerElement extends HTMLElement {
|
|||
// Update the Database in one microtask if the locale/dataSource change. We do one microtask
|
||||
// so we don't create two Databases if e.g. both the locale and the dataSource change
|
||||
_dbFlush () {
|
||||
Promise.resolve().then(() => (
|
||||
queueMicrotask(() => (
|
||||
this._dbCreate()
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
<svelte:options customElement={null} /><section
|
||||
class="picker"
|
||||
aria-label={i18n.regionLabel}
|
||||
style={pickerStyle}
|
||||
bind:this={rootElement}>
|
||||
<!-- using a spacer div because this allows us to cover up the skintone picker animation -->
|
||||
<div class="pad-top"></div>
|
||||
<div class="search-row">
|
||||
<div class="search-wrapper">
|
||||
<!-- no need for aria-haspopup=listbox, it's the default for role=combobox
|
||||
https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/examples/combobox/aria1.1pattern/listbox-combo.html
|
||||
-->
|
||||
<input
|
||||
id="search"
|
||||
class="search"
|
||||
type="search"
|
||||
role="combobox"
|
||||
enterkeyhint="search"
|
||||
placeholder={i18n.searchLabel}
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
spellcheck="true"
|
||||
aria-expanded={!!(searchMode && currentEmojis.length)}
|
||||
aria-controls="search-results"
|
||||
aria-describedby="search-description"
|
||||
aria-autocomplete="list"
|
||||
aria-activedescendant={activeSearchItemId ? `emo-${activeSearchItemId}` : ''}
|
||||
bind:value={rawSearchText}
|
||||
on:keydown={onSearchKeydown}
|
||||
>
|
||||
<label class="sr-only" for="search">{i18n.searchLabel}</label>
|
||||
<span id="search-description" class="sr-only">{i18n.searchDescription}</span>
|
||||
</div>
|
||||
<!-- For the pattern used for the skintone dropdown, see:
|
||||
https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
|
||||
The one case where we deviate from the example is that we move focus from the button to the
|
||||
listbox. (The example uses a combobox, so it's not exactly the same.) This was tested in NVDA and VoiceOver.
|
||||
Note that Svelte's a11y checker will warn if the listbox does not have a tabindex.
|
||||
https://github.com/sveltejs/svelte/blob/3bc791b/site/content/docs/06-accessibility-warnings.md#a11y-aria-activedescendant-has-tabindex
|
||||
-->
|
||||
<div class="skintone-button-wrapper {skinTonePickerExpandedAfterAnimation ? 'expanded' : ''}">
|
||||
<button id="skintone-button"
|
||||
class="emoji {skinTonePickerExpanded ? 'hide-focus' : ''}"
|
||||
aria-label={skinToneButtonLabel}
|
||||
title={skinToneButtonLabel}
|
||||
aria-describedby="skintone-description"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={skinTonePickerExpanded}
|
||||
aria-controls="skintone-list"
|
||||
on:click={onClickSkinToneButton}>
|
||||
{skinToneButtonText}
|
||||
</button>
|
||||
</div>
|
||||
<span id="skintone-description" class="sr-only">{i18n.skinToneDescription}</span>
|
||||
<div id="skintone-list"
|
||||
class="skintone-list hide-focus {skinTonePickerExpanded ? '' : 'hidden no-animate'}"
|
||||
style="transform:translateY({ skinTonePickerExpanded ? 0 : 'calc(-1 * var(--num-skintones) * var(--total-emoji-size))'})"
|
||||
role="listbox"
|
||||
aria-label={i18n.skinTonesLabel}
|
||||
aria-activedescendant="skintone-{activeSkinTone}"
|
||||
aria-hidden={!skinTonePickerExpanded}
|
||||
tabindex="-1"
|
||||
on:focusout={onSkinToneOptionsFocusOut}
|
||||
on:click={onSkinToneOptionsClick}
|
||||
on:keydown={onSkinToneOptionsKeydown}
|
||||
on:keyup={onSkinToneOptionsKeyup}
|
||||
bind:this={skinToneDropdown}>
|
||||
{#each skinTones as skinTone, i (skinTone)}
|
||||
<div id="skintone-{i}"
|
||||
class="emoji {i === activeSkinTone ? 'active' : ''}"
|
||||
aria-selected={i === activeSkinTone}
|
||||
role="option"
|
||||
title={i18n.skinTones[i]}
|
||||
aria-label={i18n.skinTones[i]}>
|
||||
{skinTone}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- this is interactive because of keydown; it doesn't really need focus -->
|
||||
<!-- svelte-ignore a11y-interactive-supports-focus -->
|
||||
<div class="nav"
|
||||
role="tablist"
|
||||
style="grid-template-columns: repeat({groups.length}, 1fr)"
|
||||
aria-label={i18n.categoriesLabel}
|
||||
on:keydown={onNavKeydown}>
|
||||
{#each groups as group (group.id)}
|
||||
<button role="tab"
|
||||
class="nav-button"
|
||||
aria-controls="tab-{group.id}"
|
||||
aria-label={i18n.categories[group.name]}
|
||||
aria-selected={!searchMode && currentGroup.id === group.id}
|
||||
title={i18n.categories[group.name]}
|
||||
on:click={() => onNavClick(group)}>
|
||||
<div class="nav-emoji emoji">
|
||||
{group.emoji}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="indicator-wrapper">
|
||||
<div class="indicator"
|
||||
style="transform: translateX({(isRtl ? -1 : 1) * currentGroupIndex * 100}%)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message {message ? '' : 'gone'}"
|
||||
role="alert"
|
||||
aria-live="polite">
|
||||
{message}
|
||||
</div>
|
||||
|
||||
<!-- The tabindex=0 is so people can scroll up and down with the keyboard. The element has a role and a label, so I
|
||||
feel it's appropriate to have the tabindex. -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- This on:click is a delegated click listener -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="tabpanel {(!databaseLoaded || message) ? 'gone': ''}"
|
||||
role={searchMode ? 'region' : 'tabpanel'}
|
||||
aria-label={searchMode ? i18n.searchResultsLabel : i18n.categories[currentGroup.name]}
|
||||
id={searchMode ? '' : `tab-${currentGroup.id}`}
|
||||
tabindex="0"
|
||||
on:click={onEmojiClick}
|
||||
bind:this={tabpanelElement}
|
||||
>
|
||||
<div use:calculateEmojiGridStyle>
|
||||
{#each currentEmojisWithCategories as emojiWithCategory, i (emojiWithCategory.category)}
|
||||
<div
|
||||
id="menu-label-{i}"
|
||||
class="category {currentEmojisWithCategories.length === 1 && currentEmojisWithCategories[0].category === '' ? 'gone' : ''}"
|
||||
aria-hidden="true">
|
||||
<!-- This logic is a bit complicated in order to avoid a flash of the word "Custom" while switching
|
||||
from a tabpanel with custom emoji to a regular group. I.e. we don't want it to suddenly flash
|
||||
from "Custom" to "Smileys and emoticons" when you click the second nav button. The easiest
|
||||
way to repro this is to add an artificial delay to the IndexedDB operations. -->
|
||||
{
|
||||
searchMode ?
|
||||
i18n.searchResultsLabel : (
|
||||
emojiWithCategory.category ?
|
||||
emojiWithCategory.category : (
|
||||
currentEmojisWithCategories.length > 1 ?
|
||||
i18n.categories.custom :
|
||||
i18n.categories[currentGroup.name]
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="emoji-menu"
|
||||
role={searchMode ? 'listbox' : 'menu'}
|
||||
aria-labelledby="menu-label-{i}"
|
||||
id={searchMode ? 'search-results' : ''}>
|
||||
{#each emojiWithCategory.emojis as emoji, i (emoji.id)}
|
||||
<button role={searchMode ? 'option' : 'menuitem'}
|
||||
aria-selected={searchMode ? i == activeSearchItem : ''}
|
||||
aria-label={labelWithSkin(emoji, currentSkinTone)}
|
||||
title={titleForEmoji(emoji)}
|
||||
class="emoji {searchMode && i === activeSearchItem ? 'active' : ''}"
|
||||
id="emo-{emoji.id}">
|
||||
{#if emoji.unicode}
|
||||
{unicodeWithSkin(emoji, currentSkinTone)}
|
||||
{:else}
|
||||
<img class="custom-emoji" src={emoji.url} alt="" loading="lazy" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<!-- This on:click is a delegated click listener -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-interactive-supports-focus -->
|
||||
<div class="favorites emoji-menu {message ? 'gone': ''}"
|
||||
role="menu"
|
||||
aria-label={i18n.favoritesLabel}
|
||||
style="padding-inline-end: {scrollbarWidth}px"
|
||||
on:click={onEmojiClick}>
|
||||
<!-- The reason the emoji logic below is largely duplicated is because it turns out we get a smaller
|
||||
bundle size from just repeating it twice, rather than creating a second Svelte component. -->
|
||||
{#each currentFavorites as emoji, i (emoji.id)}
|
||||
<button role="menuitem"
|
||||
aria-label={labelWithSkin(emoji, currentSkinTone)}
|
||||
title={titleForEmoji(emoji)}
|
||||
class="emoji"
|
||||
id="fav-{emoji.id}">
|
||||
{#if emoji.unicode}
|
||||
{unicodeWithSkin(emoji, currentSkinTone)}
|
||||
{:else}
|
||||
<img class="custom-emoji" src={emoji.url} alt="" loading="lazy" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- This serves as a baseline emoji for measuring against and determining emoji support -->
|
||||
<button aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji" bind:this={baselineEmoji}>😀</button>
|
||||
</section>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +0,0 @@
|
|||
<template src="./Picker.html"></template>
|
||||
<script src="./Picker.js"></script>
|
|
@ -0,0 +1,258 @@
|
|||
import { createFramework } from './framework.js'
|
||||
|
||||
export function render (container, state, helpers, events, actions, refs, abortSignal, firstRender) {
|
||||
const { labelWithSkin, titleForEmoji, unicodeWithSkin } = helpers
|
||||
const { html, map } = createFramework(state)
|
||||
|
||||
function emojiList (emojis, searchMode, prefix) {
|
||||
return map(emojis, (emoji, i) => {
|
||||
return html`
|
||||
<button role="${searchMode ? 'option' : 'menuitem'}"
|
||||
aria-selected="${state.searchMode ? i === state.activeSearchItem : ''}"
|
||||
aria-label="${labelWithSkin(emoji, state.currentSkinTone)}"
|
||||
title="${titleForEmoji(emoji)}"
|
||||
class="emoji ${searchMode && i === state.activeSearchItem ? 'active' : ''}"
|
||||
id=${`${prefix}-${emoji.id}`}>
|
||||
${
|
||||
emoji.unicode
|
||||
? unicodeWithSkin(emoji, state.currentSkinTone)
|
||||
: html`<img class="custom-emoji" src="${emoji.url}" alt="" loading="lazy"/>`
|
||||
}
|
||||
</button>
|
||||
`
|
||||
// It's important for the cache key to be unique based on the prefix, because the framework caches based on the
|
||||
// unique tokens + cache key, and the same emoji may be used in the tab as well as in the fav bar
|
||||
}, emoji => `${prefix}-${emoji.id}`)
|
||||
}
|
||||
|
||||
const section = () => {
|
||||
return html`
|
||||
<section
|
||||
data-ref="rootElement"
|
||||
class="picker"
|
||||
aria-label="${state.i18n.regionLabel}"
|
||||
style="${state.pickerStyle}">
|
||||
<!-- using a spacer div because this allows us to cover up the skintone picker animation -->
|
||||
<div class="pad-top"></div>
|
||||
<div class="search-row">
|
||||
<div class="search-wrapper">
|
||||
<!-- no need for aria-haspopup=listbox, it's the default for role=combobox
|
||||
https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/examples/combobox/aria1.1pattern/listbox-combo.html
|
||||
-->
|
||||
<input
|
||||
id="search"
|
||||
class="search"
|
||||
type="search"
|
||||
role="combobox"
|
||||
enterkeyhint="search"
|
||||
placeholder="${state.i18n.searchLabel}"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
spellcheck="true"
|
||||
aria-expanded="${!!(state.searchMode && state.currentEmojis.length)}"
|
||||
aria-controls="search-results"
|
||||
aria-describedby="search-description"
|
||||
aria-autocomplete="list"
|
||||
aria-activedescendant="${state.activeSearchItemId ? `emo-${state.activeSearchItemId}` : ''}"
|
||||
data-ref="searchElement"
|
||||
data-on-input="onSearchInput"
|
||||
data-on-keydown="onSearchKeydown"
|
||||
></input>
|
||||
<label class="sr-only" for="search">${state.i18n.searchLabel}</label>
|
||||
<span id="search-description" class="sr-only">${state.i18n.searchDescription}</span>
|
||||
</div>
|
||||
<!-- For the pattern used for the skintone dropdown, see:
|
||||
https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
|
||||
The one case where we deviate from the example is that we move focus from the button to the
|
||||
listbox. (The example uses a combobox, so it's not exactly the same.) This was tested in NVDA and VoiceOver. -->
|
||||
<div class="skintone-button-wrapper ${state.skinTonePickerExpandedAfterAnimation ? 'expanded' : ''}">
|
||||
<button id="skintone-button"
|
||||
class="emoji ${state.skinTonePickerExpanded ? 'hide-focus' : ''}"
|
||||
aria-label="${state.skinToneButtonLabel}"
|
||||
title="${state.skinToneButtonLabel}"
|
||||
aria-describedby="skintone-description"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="${state.skinTonePickerExpanded}"
|
||||
aria-controls="skintone-list"
|
||||
data-on-click="onClickSkinToneButton">
|
||||
${state.skinToneButtonText}
|
||||
</button>
|
||||
</div>
|
||||
<span id="skintone-description" class="sr-only">${state.i18n.skinToneDescription}</span>
|
||||
<div
|
||||
data-ref="skinToneDropdown"
|
||||
id="skintone-list"
|
||||
class="skintone-list hide-focus ${state.skinTonePickerExpanded ? '' : 'hidden no-animate'}"
|
||||
style="transform:translateY(${state.skinTonePickerExpanded ? 0 : 'calc(-1 * var(--num-skintones) * var(--total-emoji-size))'})"
|
||||
role="listbox"
|
||||
aria-label="${state.i18n.skinTonesLabel}"
|
||||
aria-activedescendant="skintone-${state.activeSkinTone}"
|
||||
aria-hidden="${!state.skinTonePickerExpanded}"
|
||||
tabIndex="-1"
|
||||
data-on-focusout="onSkinToneOptionsFocusOut"
|
||||
data-on-click="onSkinToneOptionsClick"
|
||||
data-on-keydown="onSkinToneOptionsKeydown"
|
||||
data-on-keyup="onSkinToneOptionsKeyup">
|
||||
${
|
||||
map(state.skinTones, (skinTone, i) => {
|
||||
return html`
|
||||
<div id="skintone-${i}"
|
||||
class="emoji ${i === state.activeSkinTone ? 'active' : ''}"
|
||||
aria-selected="${i === state.activeSkinTone}"
|
||||
role="option"
|
||||
title="${state.i18n.skinTones[i]}"
|
||||
aria-label="${state.i18n.skinTones[i]}">
|
||||
${skinTone}
|
||||
</div>
|
||||
`
|
||||
}, skinTone => skinTone)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- this is interactive because of keydown; it doesn't really need focus -->
|
||||
<div class="nav"
|
||||
role="tablist"
|
||||
style="grid-template-columns: repeat(${state.groups.length}, 1fr)"
|
||||
aria-label="${state.i18n.categoriesLabel}"
|
||||
data-on-keydown="onNavKeydown"
|
||||
data-on-click="onNavClick"
|
||||
>
|
||||
${
|
||||
map(state.groups, (group) => {
|
||||
return html`
|
||||
<button role="tab"
|
||||
class="nav-button"
|
||||
aria-controls="tab-${group.id}"
|
||||
aria-label="${state.i18n.categories[group.name]}"
|
||||
aria-selected="${!state.searchMode && state.currentGroup.id === group.id}"
|
||||
title="${state.i18n.categories[group.name]}"
|
||||
data-group-id=${group.id}
|
||||
>
|
||||
<div class="nav-emoji emoji">
|
||||
${group.emoji}
|
||||
</div>
|
||||
</button>
|
||||
`
|
||||
}, group => group.id)
|
||||
}
|
||||
</div>
|
||||
<div class="indicator-wrapper">
|
||||
<!-- Note we cannot test RTL in Jest because of lack of getComputedStyle() -->
|
||||
<div class="indicator"
|
||||
style="transform: translateX(${(/* istanbul ignore next */ (state.isRtl ? -1 : 1)) * state.currentGroupIndex * 100}%)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message ${state.message ? '' : 'gone'}"
|
||||
role="alert"
|
||||
aria-live="polite">
|
||||
${state.message}
|
||||
</div>
|
||||
|
||||
<!--The tabindex=0 is so people can scroll up and down with the keyboard. The element has a role and a label, so I
|
||||
feel it's appropriate to have the tabindex.
|
||||
This on:click is a delegated click listener -->
|
||||
<div data-ref="tabpanelElement" class="tabpanel ${(!state.databaseLoaded || state.message) ? 'gone' : ''}"
|
||||
role="${state.searchMode ? 'region' : 'tabpanel'}"
|
||||
aria-label="${state.searchMode ? state.i18n.searchResultsLabel : state.i18n.categories[state.currentGroup.name]}"
|
||||
id="${state.searchMode ? '' : `tab-${state.currentGroup.id}`}"
|
||||
tabIndex="0"
|
||||
data-on-click="onEmojiClick"
|
||||
>
|
||||
<div data-action="calculateEmojiGridStyle">
|
||||
${
|
||||
map(state.currentEmojisWithCategories, (emojiWithCategory, i) => {
|
||||
return html`
|
||||
<!-- wrapper div so there's one top level element for this loop -->
|
||||
<div>
|
||||
<div
|
||||
id="menu-label-${i}"
|
||||
class="category ${state.currentEmojisWithCategories.length === 1 && state.currentEmojisWithCategories[0].category === '' ? 'gone' : ''}"
|
||||
aria-hidden="true">
|
||||
<!-- This logic is a bit complicated in order to avoid a flash of the word "Custom" while switching
|
||||
from a tabpanel with custom emoji to a regular group. I.e. we don't want it to suddenly flash
|
||||
from "Custom" to "Smileys and emoticons" when you click the second nav button. The easiest
|
||||
way to repro this is to add an artificial delay to the IndexedDB operations. -->
|
||||
${
|
||||
state.searchMode
|
||||
? state.i18n.searchResultsLabel
|
||||
: (
|
||||
emojiWithCategory.category
|
||||
? emojiWithCategory.category
|
||||
: (
|
||||
state.currentEmojisWithCategories.length > 1
|
||||
? state.i18n.categories.custom
|
||||
: state.i18n.categories[state.currentGroup.name]
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="emoji-menu"
|
||||
role="${state.searchMode ? 'listbox' : 'menu'}"
|
||||
aria-labelledby="menu-label-${i}"
|
||||
id=${state.searchMode ? 'search-results' : ''}>
|
||||
${
|
||||
emojiList(emojiWithCategory.emojis, state.searchMode, /* prefix */ 'emo')
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}, emojiWithCategory => emojiWithCategory.category)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- This on:click is a delegated click listener -->
|
||||
<div class="favorites emoji-menu ${state.message ? 'gone' : ''}"
|
||||
role="menu"
|
||||
aria-label="${state.i18n.favoritesLabel}"
|
||||
style="padding-inline-end: ${`${state.scrollbarWidth}px`}"
|
||||
data-on-click="onEmojiClick">
|
||||
${
|
||||
emojiList(state.currentFavorites, /* searchMode */ false, /* prefix */ 'fav')
|
||||
}
|
||||
</div>
|
||||
<!-- This serves as a baseline emoji for measuring against and determining emoji support -->
|
||||
<button data-ref="baselineEmoji" aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji baseline-emoji">
|
||||
😀
|
||||
</button>
|
||||
</section>
|
||||
`
|
||||
}
|
||||
|
||||
const rootDom = section()
|
||||
|
||||
if (firstRender) { // not a re-render
|
||||
container.appendChild(rootDom)
|
||||
|
||||
// we only bind events/refs/actions once - there is no need to find them again given this component structure
|
||||
|
||||
// helper for traversing the dom, finding elements by an attribute, and getting the attribute value
|
||||
const forElementWithAttribute = (attributeName, callback) => {
|
||||
for (const element of container.querySelectorAll(`[${attributeName}]`)) {
|
||||
callback(element, element.getAttribute(attributeName))
|
||||
}
|
||||
}
|
||||
|
||||
// bind events
|
||||
for (const eventName of ['click', 'focusout', 'input', 'keydown', 'keyup']) {
|
||||
forElementWithAttribute(`data-on-${eventName}`, (element, listenerName) => {
|
||||
element.addEventListener(eventName, events[listenerName])
|
||||
})
|
||||
}
|
||||
|
||||
// find refs
|
||||
forElementWithAttribute('data-ref', (element, ref) => {
|
||||
refs[ref] = element
|
||||
})
|
||||
|
||||
// set up actions
|
||||
forElementWithAttribute('data-action', (element, action) => {
|
||||
actions[action](element)
|
||||
})
|
||||
|
||||
// destroy/abort logic
|
||||
abortSignal.addEventListener('abort', () => {
|
||||
container.removeChild(rootDom)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
import { getFromMap, parseTemplate, toString } from './utils.js'
|
||||
|
||||
const parseCache = new WeakMap()
|
||||
const domInstancesCache = new WeakMap()
|
||||
const unkeyedSymbol = Symbol('un-keyed')
|
||||
|
||||
// for debugging
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.parseCache = parseCache
|
||||
window.domInstancesCache = domInstancesCache
|
||||
}
|
||||
|
||||
// Not supported in Safari <=13
|
||||
const hasReplaceChildren = 'replaceChildren' in Element.prototype
|
||||
function replaceChildren (parentNode, newChildren) {
|
||||
/* istanbul ignore else */
|
||||
if (hasReplaceChildren) {
|
||||
parentNode.replaceChildren(...newChildren)
|
||||
} else { // minimal polyfill for Element.prototype.replaceChildren
|
||||
parentNode.innerHTML = ''
|
||||
parentNode.append(...newChildren)
|
||||
}
|
||||
}
|
||||
|
||||
function doChildrenNeedRerender (parentNode, newChildren) {
|
||||
let oldChild = parentNode.firstChild
|
||||
let oldChildrenCount = 0
|
||||
// iterate using firstChild/nextSibling because browsers use a linked list under the hood
|
||||
while (oldChild) {
|
||||
const newChild = newChildren[oldChildrenCount]
|
||||
// check if the old child and new child are the same
|
||||
if (newChild !== oldChild) {
|
||||
return true
|
||||
}
|
||||
oldChild = oldChild.nextSibling
|
||||
oldChildrenCount++
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && oldChildrenCount !== parentNode.children.length) {
|
||||
throw new Error('parentNode.children.length is different from oldChildrenCount, it should not be')
|
||||
}
|
||||
// if new children length is different from old, we must re-render
|
||||
return oldChildrenCount !== newChildren.length
|
||||
}
|
||||
|
||||
function patchChildren (newChildren, instanceBinding) {
|
||||
const { targetNode } = instanceBinding
|
||||
let { targetParentNode } = instanceBinding
|
||||
|
||||
let needsRerender = false
|
||||
|
||||
if (targetParentNode) { // already rendered once
|
||||
needsRerender = doChildrenNeedRerender(targetParentNode, newChildren)
|
||||
} else { // first render of list
|
||||
needsRerender = true
|
||||
instanceBinding.targetNode = undefined // placeholder comment not needed anymore, free memory
|
||||
instanceBinding.targetParentNode = targetParentNode = targetNode.parentNode
|
||||
}
|
||||
// avoid re-rendering list if the dom nodes are exactly the same before and after
|
||||
if (needsRerender) {
|
||||
replaceChildren(targetParentNode, newChildren)
|
||||
}
|
||||
}
|
||||
|
||||
function patch (expressions, instanceBindings) {
|
||||
for (const instanceBinding of instanceBindings) {
|
||||
const {
|
||||
targetNode,
|
||||
currentExpression,
|
||||
binding: {
|
||||
expressionIndex,
|
||||
attributeName,
|
||||
attributeValuePre,
|
||||
attributeValuePost
|
||||
}
|
||||
} = instanceBinding
|
||||
|
||||
const expression = expressions[expressionIndex]
|
||||
|
||||
if (currentExpression === expression) {
|
||||
// no need to update, same as before
|
||||
continue
|
||||
}
|
||||
|
||||
instanceBinding.currentExpression = expression
|
||||
|
||||
if (attributeName) { // attribute replacement
|
||||
targetNode.setAttribute(attributeName, attributeValuePre + toString(expression) + attributeValuePost)
|
||||
} else { // text node / child element / children replacement
|
||||
let newNode
|
||||
if (Array.isArray(expression)) { // array of DOM elements produced by tag template literals
|
||||
patchChildren(expression, instanceBinding)
|
||||
} else if (expression instanceof Element) { // html tag template returning a DOM element
|
||||
newNode = expression
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && newNode === targetNode) {
|
||||
// it seems impossible for the framework to get into this state, may as well assert on it
|
||||
// worst case scenario is we lose focus if we call replaceWith on the same node
|
||||
throw new Error('the newNode and targetNode are the same, this should never happen')
|
||||
}
|
||||
targetNode.replaceWith(newNode)
|
||||
} else { // primitive - string, number, etc
|
||||
if (targetNode.nodeType === Node.TEXT_NODE) { // already transformed into a text node
|
||||
// nodeValue is faster than textContent supposedly https://www.youtube.com/watch?v=LY6y3HbDVmg
|
||||
targetNode.nodeValue = toString(expression)
|
||||
} else { // replace comment or whatever was there before with a text node
|
||||
newNode = document.createTextNode(toString(expression))
|
||||
targetNode.replaceWith(newNode)
|
||||
}
|
||||
}
|
||||
if (newNode) {
|
||||
instanceBinding.targetNode = newNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parse (tokens) {
|
||||
let htmlString = ''
|
||||
|
||||
let withinTag = false
|
||||
let withinAttribute = false
|
||||
let elementIndexCounter = -1 // depth-first traversal order
|
||||
|
||||
const elementsToBindings = new Map()
|
||||
const elementIndexes = []
|
||||
|
||||
for (let i = 0, len = tokens.length; i < len; i++) {
|
||||
const token = tokens[i]
|
||||
htmlString += token
|
||||
|
||||
if (i === len - 1) {
|
||||
break // no need to process characters - no more expressions to be found
|
||||
}
|
||||
|
||||
for (let j = 0; j < token.length; j++) {
|
||||
const char = token.charAt(j)
|
||||
switch (char) {
|
||||
case '<': {
|
||||
const nextChar = token.charAt(j + 1)
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && !/[/a-z]/.test(nextChar)) {
|
||||
// we don't need to support comments ('<!') because we always use html-minify-literals
|
||||
// also we don't support '<' inside tags, e.g. '<div> 2 < 3 </div>'
|
||||
throw new Error('framework currently only supports a < followed by / or a-z')
|
||||
}
|
||||
if (nextChar === '/') { // closing tag
|
||||
// leaving an element
|
||||
elementIndexes.pop()
|
||||
} else { // not a closing tag
|
||||
withinTag = true
|
||||
elementIndexes.push(++elementIndexCounter)
|
||||
}
|
||||
break
|
||||
}
|
||||
case '>': {
|
||||
withinTag = false
|
||||
withinAttribute = false
|
||||
break
|
||||
}
|
||||
case '=': {
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && !withinTag) {
|
||||
// we don't currently support '=' anywhere but inside a tag, e.g.
|
||||
// we don't support '<div>2 + 2 = 4</div>'
|
||||
throw new Error('framework currently does not support = anywhere but inside a tag')
|
||||
}
|
||||
withinAttribute = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const elementIndex = elementIndexes[elementIndexes.length - 1]
|
||||
const bindings = getFromMap(elementsToBindings, elementIndex, () => [])
|
||||
|
||||
let attributeName
|
||||
let attributeValuePre
|
||||
let attributeValuePost
|
||||
if (withinAttribute) {
|
||||
// I never use single-quotes for attribute values in HTML, so just support double-quotes or no-quotes
|
||||
const match = /(\S+)="?([^"=]*)$/.exec(token)
|
||||
attributeName = match[1]
|
||||
attributeValuePre = match[2]
|
||||
attributeValuePost = /^[^">]*/.exec(tokens[i + 1])[0]
|
||||
}
|
||||
|
||||
const binding = {
|
||||
attributeName,
|
||||
attributeValuePre,
|
||||
attributeValuePost,
|
||||
expressionIndex: i
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// remind myself that this object is supposed to be immutable
|
||||
Object.freeze(binding)
|
||||
}
|
||||
|
||||
bindings.push(binding)
|
||||
|
||||
// add a placeholder comment that we can find later
|
||||
htmlString += (!withinTag && !withinAttribute) ? `<!--${bindings.length - 1}-->` : ''
|
||||
}
|
||||
|
||||
const template = parseTemplate(htmlString)
|
||||
|
||||
return {
|
||||
template,
|
||||
elementsToBindings
|
||||
}
|
||||
}
|
||||
|
||||
function findPlaceholderComment (element, bindingId) {
|
||||
// If we had a lot of placeholder comments to find, it would make more sense to build up a map once
|
||||
// rather than search the DOM every time. But it turns out that we always only have one child,
|
||||
// and it's the comment node, so searching every time is actually faster.
|
||||
let childNode = element.firstChild
|
||||
while (childNode) {
|
||||
// Note that minify-html-literals has already removed all non-framework comments
|
||||
// So we just need to look for comments that have exactly the bindingId as its text content
|
||||
if (childNode.nodeType === Node.COMMENT_NODE && childNode.nodeValue === toString(bindingId)) {
|
||||
return childNode
|
||||
}
|
||||
childNode = childNode.nextSibling
|
||||
}
|
||||
}
|
||||
|
||||
function traverseAndSetupBindings (dom, elementsToBindings) {
|
||||
const instanceBindings = []
|
||||
// traverse dom
|
||||
const treeWalker = document.createTreeWalker(dom, NodeFilter.SHOW_ELEMENT)
|
||||
|
||||
let element = dom
|
||||
let elementIndex = -1
|
||||
do {
|
||||
const bindings = elementsToBindings.get(++elementIndex)
|
||||
if (bindings) {
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i]
|
||||
|
||||
const targetNode = binding.attributeName
|
||||
? element // attribute binding, just use the element itself
|
||||
: findPlaceholderComment(element, i) // not an attribute binding, so has a placeholder comment
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && !targetNode) {
|
||||
throw new Error('targetNode should not be undefined')
|
||||
}
|
||||
|
||||
const instanceBinding = {
|
||||
binding,
|
||||
targetNode,
|
||||
targetParentNode: undefined,
|
||||
currentExpression: undefined
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// remind myself that this object is supposed to be monomorphic (for better JS engine perf)
|
||||
Object.seal(instanceBinding)
|
||||
}
|
||||
|
||||
instanceBindings.push(instanceBinding)
|
||||
}
|
||||
}
|
||||
} while ((element = treeWalker.nextNode()))
|
||||
|
||||
return instanceBindings
|
||||
}
|
||||
|
||||
function parseHtml (tokens) {
|
||||
// All templates and bound expressions are unique per tokens array
|
||||
const { template, elementsToBindings } = getFromMap(parseCache, tokens, () => parse(tokens))
|
||||
|
||||
// When we parseHtml, we always return a fresh DOM instance ready to be updated
|
||||
const dom = template.cloneNode(true).content.firstElementChild
|
||||
const instanceBindings = traverseAndSetupBindings(dom, elementsToBindings)
|
||||
|
||||
return function updateDomInstance (expressions) {
|
||||
patch(expressions, instanceBindings)
|
||||
return dom
|
||||
}
|
||||
}
|
||||
|
||||
export function createFramework (state) {
|
||||
const domInstances = getFromMap(domInstancesCache, state, () => new Map())
|
||||
let domInstanceCacheKey = unkeyedSymbol
|
||||
|
||||
function html (tokens, ...expressions) {
|
||||
// Each unique lexical usage of map() is considered unique due to the html`` tagged template call it makes,
|
||||
// which has lexically unique tokens. The unkeyed symbol is just used for html`` usage outside of a map().
|
||||
const domInstancesForTokens = getFromMap(domInstances, tokens, () => new Map())
|
||||
const updateDomInstance = getFromMap(domInstancesForTokens, domInstanceCacheKey, () => parseHtml(tokens))
|
||||
|
||||
return updateDomInstance(expressions) // update with expressions
|
||||
}
|
||||
|
||||
function map (array, callback, keyFunction) {
|
||||
return array.map((item, index) => {
|
||||
const originalCacheKey = domInstanceCacheKey
|
||||
domInstanceCacheKey = keyFunction(item)
|
||||
try {
|
||||
return callback(item, index)
|
||||
} finally {
|
||||
domInstanceCacheKey = originalCacheKey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return { map, html }
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
import { queueMicrotask } from '../../utils/queueMicrotask.js'
|
||||
|
||||
export function createState (abortSignal) {
|
||||
let destroyed = false
|
||||
let currentObserver
|
||||
|
||||
const propsToObservers = new Map()
|
||||
const dirtyObservers = new Set()
|
||||
|
||||
let queued
|
||||
|
||||
let recursionDepth = 0
|
||||
const MAX_RECURSION_DEPTH = 30
|
||||
|
||||
const flush = () => {
|
||||
if (destroyed) {
|
||||
return
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && recursionDepth === MAX_RECURSION_DEPTH) {
|
||||
throw new Error('max recursion depth, you probably didn\'t mean to do this')
|
||||
}
|
||||
const observersToRun = [...dirtyObservers]
|
||||
dirtyObservers.clear() // clear before running to force any new updates to run in another tick of the loop
|
||||
try {
|
||||
for (const observer of observersToRun) {
|
||||
observer()
|
||||
}
|
||||
} finally {
|
||||
queued = false
|
||||
if (dirtyObservers.size) { // new updates, queue another one
|
||||
recursionDepth++
|
||||
queued = true
|
||||
queueMicrotask(flush)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const state = new Proxy({}, {
|
||||
get (target, prop) {
|
||||
// console.log('reactivity: get', prop)
|
||||
if (currentObserver) {
|
||||
let observers = propsToObservers.get(prop)
|
||||
if (!observers) {
|
||||
observers = new Set()
|
||||
propsToObservers.set(prop, observers)
|
||||
}
|
||||
observers.add(currentObserver)
|
||||
}
|
||||
return target[prop]
|
||||
},
|
||||
set (target, prop, newValue) {
|
||||
// console.log('reactivity: set', prop, newValue)
|
||||
target[prop] = newValue
|
||||
const observers = propsToObservers.get(prop)
|
||||
if (observers) {
|
||||
for (const observer of observers) {
|
||||
dirtyObservers.add(observer)
|
||||
}
|
||||
if (!queued) {
|
||||
recursionDepth = 0
|
||||
queued = true
|
||||
queueMicrotask(flush)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const createEffect = (callback) => {
|
||||
const runnable = () => {
|
||||
const oldObserver = currentObserver
|
||||
currentObserver = runnable
|
||||
try {
|
||||
return callback()
|
||||
} finally {
|
||||
currentObserver = oldObserver
|
||||
}
|
||||
}
|
||||
return runnable()
|
||||
}
|
||||
|
||||
// for debugging
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.state = state
|
||||
}
|
||||
|
||||
// destroy logic
|
||||
abortSignal.addEventListener('abort', () => {
|
||||
destroyed = true
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
delete window.state
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
state,
|
||||
createEffect
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
export function getFromMap (cache, key, func) {
|
||||
let cached = cache.get(key)
|
||||
if (!cached) {
|
||||
cached = func()
|
||||
cache.set(key, cached)
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
export function toString (value) {
|
||||
return '' + value
|
||||
}
|
||||
|
||||
export function parseTemplate (htmlString) {
|
||||
const template = document.createElement('template')
|
||||
template.innerHTML = htmlString
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (template.content.children.length !== 1) {
|
||||
throw new Error('only 1 child allowed for now')
|
||||
}
|
||||
}
|
||||
return template
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// via https://unpkg.com/browse/emojibase-data@6.0.0/meta/groups.json
|
||||
const allGroups = [
|
||||
export const allGroups = [
|
||||
[-1, '✨', 'custom'],
|
||||
[0, '😀', 'smileys-emotion'],
|
||||
[1, '👋', 'people-body'],
|
||||
|
@ -13,4 +13,3 @@ const allGroups = [
|
|||
].map(([id, emoji, name]) => ({ id, emoji, name }))
|
||||
|
||||
export const groups = allGroups.slice(1)
|
||||
export const customGroup = allGroups[0]
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// Compare two arrays, with a function called on each item in the two arrays that returns true if the items are equal
|
||||
export function arraysAreEqualByFunction (left, right, areEqualFunc) {
|
||||
if (left.length !== right.length) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
if (!areEqualFunc(left[i], right[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
/* istanbul ignore next */
|
||||
const qM = typeof queueMicrotask === 'function' ? queueMicrotask : callback => Promise.resolve().then(callback)
|
||||
export { qM as queueMicrotask }
|
|
@ -4,7 +4,8 @@
|
|||
// https://github.com/sveltejs/svelte/issues/6521
|
||||
// Also note tabpanelElement can be null if the element is disconnected immediately after connected
|
||||
export function resetScrollTopIfPossible (element) {
|
||||
if (element) {
|
||||
/* istanbul ignore else */
|
||||
if (element) { // Makes me nervous not to have this `if` guard
|
||||
element.scrollTop = 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export const resetResizeObserverSupported = () => {
|
|||
resizeObserverSupported = typeof ResizeObserver === 'function'
|
||||
}
|
||||
|
||||
export function calculateWidth (node, onUpdate) {
|
||||
export function calculateWidth (node, abortSignal, onUpdate) {
|
||||
let resizeObserver
|
||||
if (resizeObserverSupported) {
|
||||
resizeObserver = new ResizeObserver(entries => (
|
||||
|
@ -25,11 +25,10 @@ export function calculateWidth (node, onUpdate) {
|
|||
}
|
||||
|
||||
// cleanup function (called on destroy)
|
||||
return {
|
||||
destroy () {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
abortSignal.addEventListener('abort', () => {
|
||||
if (resizeObserver) {
|
||||
console.log('ResizeObserver destroyed')
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
console.warn('Importing emoji-picker-element from "emoji-picker-element/svelte" is deprecated. ' +
|
||||
'Instead, import from "emoji-picker-element" or "emoji-picker-element/picker".')
|
||||
import picker from './picker.js'
|
||||
export default picker
|
|
@ -178,6 +178,16 @@ describe('Picker tests', () => {
|
|||
await waitFor(() => expect(queryAllByRole('listbox', { name: 'Skin tones' })).toHaveLength(0))
|
||||
})
|
||||
|
||||
test('Click skintone button while picker is open', async () => {
|
||||
// this should not be possible since the picker covers the button when it's open,
|
||||
// but this is for test coverage, and just to be safe
|
||||
await openSkintoneListbox(container)
|
||||
await fireEvent.click(getByRole('button', { name: /Choose a skin tone/ }))
|
||||
|
||||
// listbox closes
|
||||
await waitFor(() => expect(queryAllByRole('listbox', { name: 'Skin tones' })).toHaveLength(0))
|
||||
})
|
||||
|
||||
test('nav keyboard test', async () => {
|
||||
getByRole('tab', { name: 'Smileys and emoticons', selected: true }).focus()
|
||||
|
||||
|
@ -339,6 +349,19 @@ describe('Picker tests', () => {
|
|||
), { timeout: 5000 })
|
||||
}, 10000)
|
||||
|
||||
test('press enter on an empty search list', async () => {
|
||||
await tick(120)
|
||||
type(getByRole('combobox'), 'xxxyyyzzzhahaha')
|
||||
await waitFor(() => expect(queryAllByRole('option')).toHaveLength(0))
|
||||
expect(getByRole('combobox').getAttribute('aria-activedescendant')).toBeFalsy()
|
||||
await tick(120)
|
||||
fireEvent.keyDown(getByRole('combobox'), { key: 'Enter', code: 'Enter' })
|
||||
await tick(120)
|
||||
// should do nothing basically since there's nothing to search for
|
||||
expect(queryAllByRole('option')).toHaveLength(0)
|
||||
expect(getByRole('combobox').getAttribute('aria-activedescendant')).toBeFalsy()
|
||||
}, 10000)
|
||||
|
||||
test('press enter to make first search item active - custom emoji', async () => {
|
||||
picker.customEmoji = [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
import { createFramework } from '../../../src/picker/components/Picker/framework.js'
|
||||
|
||||
describe('framework', () => {
|
||||
test('patches a node', () => {
|
||||
const state = { name: 'foo' }
|
||||
|
||||
const { html } = createFramework(state)
|
||||
|
||||
let node
|
||||
const render = () => {
|
||||
node = html`<div>${html`<span>${state.name}</span>`}</div>`
|
||||
}
|
||||
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div><span>foo</span></div>')
|
||||
|
||||
state.name = 'bar'
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div><span>bar</span></div>')
|
||||
})
|
||||
|
||||
test('replaces one node with a totally different one', () => {
|
||||
const state = { name: 'foo' }
|
||||
|
||||
const { html } = createFramework(state)
|
||||
|
||||
let node
|
||||
const render = () => {
|
||||
node = html`<div>${
|
||||
state.name === 'foo' ? html`<span>${state.name}</span>` : html`<button>${state.name}</button>`
|
||||
}</div>`
|
||||
}
|
||||
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div><span>foo</span></div>')
|
||||
|
||||
state.name = 'bar'
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div><button>bar</button></div>')
|
||||
})
|
||||
|
||||
test('return the same exact node after a re-render', () => {
|
||||
const state = { name: 'foo' }
|
||||
|
||||
const { html } = createFramework(state)
|
||||
|
||||
let node
|
||||
let cached
|
||||
const render = () => {
|
||||
cached = cached ?? html`<span>${state.name}</span>`
|
||||
node = html`<div>${cached}</div>`
|
||||
}
|
||||
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div><span>foo</span></div>')
|
||||
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div><span>foo</span></div>')
|
||||
})
|
||||
|
||||
test('render two dynamic expressions inside the same element', () => {
|
||||
const state = { name1: 'foo', name2: 'bar' }
|
||||
|
||||
const { html } = createFramework(state)
|
||||
|
||||
let node
|
||||
const render = () => {
|
||||
node = html`<div>${state.name1}${state.name2}</div>`
|
||||
}
|
||||
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div>foobar</div>')
|
||||
|
||||
state.name1 = 'baz'
|
||||
state.name2 = 'quux'
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div>bazquux</div>')
|
||||
})
|
||||
|
||||
test('render a mix of dynamic and static text nodes in the same element', () => {
|
||||
const state = { name1: 'foo', name2: 'bar' }
|
||||
|
||||
const { html } = createFramework(state)
|
||||
|
||||
let node
|
||||
const render = () => {
|
||||
node = html`<div>1${state.name1}2${state.name2}3</div>`
|
||||
}
|
||||
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div>1foo2bar3</div>')
|
||||
|
||||
state.name1 = 'baz'
|
||||
state.name2 = 'quux'
|
||||
render()
|
||||
expect(node.outerHTML).toBe('<div>1baz2quux3</div>')
|
||||
})
|
||||
|
||||
test('attributes', () => {
|
||||
const state = {}
|
||||
|
||||
const { html } = createFramework(state)
|
||||
|
||||
const expectRender = (render, expected1, expected2) => {
|
||||
state.name = 'foo'
|
||||
expect(render().outerHTML).toBe(expected1)
|
||||
|
||||
state.name = 'bar'
|
||||
expect(render().outerHTML).toBe(expected2)
|
||||
}
|
||||
|
||||
expectRender(() => html`<div class="${state.name}"></div>`, '<div class="foo"></div>', '<div class="bar"></div>')
|
||||
expectRender(() => html`<div class=${state.name}></div>`, '<div class="foo"></div>', '<div class="bar"></div>')
|
||||
|
||||
// pre
|
||||
expectRender(() => html`<div class="a${state.name}"></div>`, '<div class="afoo"></div>', '<div class="abar"></div>')
|
||||
expectRender(() => html`<div class=a${state.name}></div>`, '<div class="afoo"></div>', '<div class="abar"></div>')
|
||||
|
||||
// post
|
||||
expectRender(() => html`<div class="${state.name}z"></div>`, '<div class="fooz"></div>', '<div class="barz"></div>')
|
||||
expectRender(() => html`<div class=${state.name}z></div>`, '<div class="fooz"></div>', '<div class="barz"></div>')
|
||||
|
||||
// pre+post
|
||||
expectRender(() => html`<div class="a${state.name}z"></div>`, '<div class="afooz"></div>', '<div class="abarz"></div>')
|
||||
expectRender(() => html`<div class=a${state.name}z></div>`, '<div class="afooz"></div>', '<div class="abarz"></div>')
|
||||
})
|
||||
})
|
299
yarn.lock
299
yarn.lock
|
@ -12,7 +12,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11"
|
||||
integrity sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==
|
||||
|
||||
"@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.2.1":
|
||||
"@ampproject/remapping@^2.2.0":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
|
||||
integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
|
||||
|
@ -625,7 +625,7 @@
|
|||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
@ -761,14 +761,6 @@
|
|||
estree-walker "^2.0.2"
|
||||
magic-string "^0.30.3"
|
||||
|
||||
"@rollup/pluginutils@^4.1.0":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||
integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
|
||||
dependencies:
|
||||
estree-walker "^2.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@rollup/pluginutils@^5.0.1":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.5.tgz#bbb4c175e19ebfeeb8c132c2eea0ecb89941a66c"
|
||||
|
@ -960,7 +952,15 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.20.7"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.1":
|
||||
"@types/clean-css@*":
|
||||
version "4.2.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.11.tgz#3f170dedd8d096fe7e7bd1c8dda0c8314217cbe6"
|
||||
integrity sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453"
|
||||
integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==
|
||||
|
@ -979,6 +979,15 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/html-minifier@^3.5.3":
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/html-minifier/-/html-minifier-3.5.3.tgz#5276845138db2cebc54c789e0aaf87621a21e84f"
|
||||
integrity sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==
|
||||
dependencies:
|
||||
"@types/clean-css" "*"
|
||||
"@types/relateurl" "*"
|
||||
"@types/uglify-js" "*"
|
||||
|
||||
"@types/http-cache-semantics@^4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz#abe102d06ccda1efdf0ed98c10ccf7f36a785a41"
|
||||
|
@ -1047,10 +1056,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
|
||||
integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==
|
||||
|
||||
"@types/pug@^2.0.6":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.7.tgz#ffb9239e4da7ea1af27070cad9343049e440993d"
|
||||
integrity sha512-I469DU0UXNC1aHepwirWhu9YKg5fkxohZD95Ey/5A7lovC+Siu+MCLffva87lnfThaOrw9Vb1DUN5t55oULAAw==
|
||||
"@types/relateurl@*":
|
||||
version "0.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.33.tgz#fa174c30100d91e88d7b0ba60cefd7e8c532516f"
|
||||
integrity sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==
|
||||
|
||||
"@types/resolve@1.20.2":
|
||||
version "1.20.2"
|
||||
|
@ -1072,6 +1081,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
|
||||
integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==
|
||||
|
||||
"@types/uglify-js@*":
|
||||
version "3.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.4.tgz#3c70021f08023e5a760ce133d22966f200e1d31c"
|
||||
integrity sha512-Hm/T0kV3ywpJyMGNbsItdivRhYNCQQf1IIsYsXnoVPES4t+FMLyDe0/K+Ea7ahWtMtSNb22ZdY7MIyoD9rqARg==
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.1.tgz#07773d7160494d56aa882d7531aac7319ea67c3b"
|
||||
|
@ -1130,7 +1146,7 @@ acorn-walk@^8.0.2:
|
|||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||
|
||||
acorn@^8.1.0, acorn@^8.10.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
|
||||
acorn@^8.1.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
|
||||
version "8.10.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
|
||||
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
|
||||
|
@ -1268,7 +1284,7 @@ aria-query@5.1.3:
|
|||
dependencies:
|
||||
deep-equal "^2.0.5"
|
||||
|
||||
aria-query@^5.0.0, aria-query@^5.3.0:
|
||||
aria-query@^5.0.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e"
|
||||
integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
|
||||
|
@ -1429,13 +1445,6 @@ available-typed-arrays@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
axobject-query@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
|
||||
integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==
|
||||
dependencies:
|
||||
dequal "^2.0.3"
|
||||
|
||||
b4a@^1.6.4:
|
||||
version "1.6.4"
|
||||
resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9"
|
||||
|
@ -1593,7 +1602,7 @@ bser@2.1.1:
|
|||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
buffer-crc32@^0.2.5, buffer-crc32@~0.2.3:
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
@ -1677,6 +1686,14 @@ callsites@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camel-case@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
|
||||
integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==
|
||||
dependencies:
|
||||
no-case "^2.2.0"
|
||||
upper-case "^1.1.1"
|
||||
|
||||
camelcase-keys@^6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0"
|
||||
|
@ -1779,6 +1796,13 @@ cjs-module-lexer@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107"
|
||||
integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
|
||||
|
||||
clean-css@^4.2.1:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
|
||||
integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==
|
||||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
cli-cursor@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea"
|
||||
|
@ -1827,17 +1851,6 @@ co@^4.6.0:
|
|||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
|
||||
|
||||
code-red@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/code-red/-/code-red-1.0.4.tgz#59ba5c9d1d320a4ef795bc10a28bd42bfebe3e35"
|
||||
integrity sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||
"@types/estree" "^1.0.1"
|
||||
acorn "^8.10.0"
|
||||
estree-walker "^3.0.3"
|
||||
periscopic "^3.1.0"
|
||||
|
||||
coffee-script@^1.12.4:
|
||||
version "1.12.7"
|
||||
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
|
||||
|
@ -1914,7 +1927,7 @@ commander@11.1.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906"
|
||||
integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
|
||||
|
||||
commander@^2.20.0:
|
||||
commander@^2.19.0, commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
@ -2509,11 +2522,6 @@ destroy@1.2.0, destroy@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
|
||||
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
|
||||
|
||||
detect-indent@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
||||
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
|
||||
|
||||
detect-newline@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||
|
@ -2775,11 +2783,6 @@ es-to-primitive@^1.2.1:
|
|||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
es6-promise@^3.1.2:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
|
||||
integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
|
@ -3027,18 +3030,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
|
|||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||
|
||||
estree-walker@^2.0.1, estree-walker@^2.0.2:
|
||||
estree-walker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
estree-walker@^3.0.0, estree-walker@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
|
||||
integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
|
@ -3734,7 +3730,7 @@ got@^12.1.0:
|
|||
p-cancelable "^3.0.0"
|
||||
responselike "^3.0.0"
|
||||
|
||||
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9:
|
||||
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
@ -3825,6 +3821,11 @@ has@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6"
|
||||
integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==
|
||||
|
||||
he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
|
@ -3849,6 +3850,19 @@ html-escaper@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||
|
||||
html-minifier@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56"
|
||||
integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==
|
||||
dependencies:
|
||||
camel-case "^3.0.0"
|
||||
clean-css "^4.2.1"
|
||||
commander "^2.19.0"
|
||||
he "^1.2.0"
|
||||
param-case "^2.1.1"
|
||||
relateurl "^0.2.7"
|
||||
uglify-js "^3.5.1"
|
||||
|
||||
html-tags@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
|
||||
|
@ -4307,13 +4321,6 @@ is-reference@1.2.1:
|
|||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
is-reference@^3.0.0, is-reference@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.2.tgz#154747a01f45cd962404ee89d43837af2cba247c"
|
||||
integrity sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
is-regex@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||
|
@ -5309,11 +5316,6 @@ load-json-file@^5.2.0:
|
|||
strip-bom "^3.0.0"
|
||||
type-fest "^0.3.0"
|
||||
|
||||
locate-character@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-3.0.0.tgz#0305c5b8744f61028ef5d01f444009e00779f974"
|
||||
integrity sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
|
@ -5464,6 +5466,11 @@ loose-envify@^1.4.0:
|
|||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lower-case@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
|
||||
integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==
|
||||
|
||||
lowercase-keys@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
|
||||
|
@ -5493,14 +5500,14 @@ lz-string@^1.5.0:
|
|||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||
|
||||
magic-string@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
|
||||
integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==
|
||||
magic-string@^0.25.0:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.13"
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.5:
|
||||
magic-string@^0.30.3:
|
||||
version "0.30.5"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
|
||||
integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==
|
||||
|
@ -5694,6 +5701,17 @@ min-indent@^1.0.0, min-indent@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||
|
||||
minify-html-literals@^1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/minify-html-literals/-/minify-html-literals-1.3.5.tgz#11c05e2b9699be7f41647186a9fe8249b7de6734"
|
||||
integrity sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==
|
||||
dependencies:
|
||||
"@types/html-minifier" "^3.5.3"
|
||||
clean-css "^4.2.1"
|
||||
html-minifier "^4.0.0"
|
||||
magic-string "^0.25.0"
|
||||
parse-literals "^1.2.1"
|
||||
|
||||
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
|
@ -5740,13 +5758,6 @@ mkdirp-classic@^0.5.2:
|
|||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
modify-values@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
|
||||
|
@ -5797,6 +5808,13 @@ nice-try@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
no-case@^2.2.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
|
||||
integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
|
||||
dependencies:
|
||||
lower-case "^1.1.1"
|
||||
|
||||
node-fetch@^2.6.12:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||
|
@ -6138,6 +6156,13 @@ pako@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
param-case@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
|
||||
integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==
|
||||
dependencies:
|
||||
no-case "^2.2.0"
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
|
@ -6163,6 +6188,13 @@ parse-json@^5.0.0, parse-json@^5.2.0:
|
|||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse-literals@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-literals/-/parse-literals-1.2.1.tgz#2311855a12a6e12434f44eb40fa434c48cc0560f"
|
||||
integrity sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==
|
||||
dependencies:
|
||||
typescript "^2.9.2 || ^3.0.0 || ^4.0.0"
|
||||
|
||||
parse5@^5.1.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
|
||||
|
@ -6247,21 +6279,12 @@ pend@~1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
periscopic@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a"
|
||||
integrity sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
estree-walker "^3.0.0"
|
||||
is-reference "^3.0.0"
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
@ -6776,6 +6799,11 @@ regexpp@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
|
||||
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
|
||||
|
||||
relateurl@^0.2.7:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
|
||||
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
|
||||
|
||||
remarkable@^1.7.1:
|
||||
version "1.7.4"
|
||||
resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00"
|
||||
|
@ -6887,13 +6915,6 @@ rfdc@^1.3.0:
|
|||
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
|
||||
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
|
||||
|
||||
rimraf@^2.5.2:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
|
@ -6906,14 +6927,6 @@ rollup-plugin-analyzer@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/rollup-plugin-analyzer/-/rollup-plugin-analyzer-4.0.0.tgz#96b757ed64a098b59d72f085319e68cdd86d5798"
|
||||
integrity sha512-LL9GEt3bkXp6Wa19SNR5MWcvHNMvuTFYg+eYBZN2OIFhSWN+pEJUQXEKu5BsOeABob3x9PDaLKW7w5iOJnsESQ==
|
||||
|
||||
rollup-plugin-svelte@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.6.tgz#44a4ea6c6e8ed976824d9fd40c78d048515e5838"
|
||||
integrity sha512-nVFRBpGWI2qUY1OcSiEEA/kjCY2+vAjO9BI8SzA7NRrh2GTunLd6w2EYmnMt/atgdg8GvcNjLsmZmbQs/u4SQA==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^4.1.0"
|
||||
resolve.exports "^2.0.0"
|
||||
|
||||
rollup-plugin-terser@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
|
||||
|
@ -6985,16 +6998,6 @@ safe-regex-test@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sander@^0.5.0:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/sander/-/sander-0.5.1.tgz#741e245e231f07cafb6fdf0f133adfa216a502ad"
|
||||
integrity sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==
|
||||
dependencies:
|
||||
es6-promise "^3.1.2"
|
||||
graceful-fs "^4.1.3"
|
||||
mkdirp "^0.5.1"
|
||||
rimraf "^2.5.2"
|
||||
|
||||
sanitize-filename@^1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378"
|
||||
|
@ -7233,16 +7236,6 @@ socks@^2.7.1:
|
|||
ip "^2.0.0"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
sorcery@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.11.0.tgz#310c80ee993433854bb55bb9aa4003acd147fca8"
|
||||
integrity sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
buffer-crc32 "^0.2.5"
|
||||
minimist "^1.2.0"
|
||||
sander "^0.5.0"
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
|
@ -7264,11 +7257,16 @@ source-map-support@^0.5.16, source-map-support@~0.5.20:
|
|||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
|
||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
|
||||
|
@ -7652,41 +7650,6 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
svelte-jester@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-jester/-/svelte-jester-3.0.0.tgz#7872beb559bce3c66f134d6f016f2626cf8f1d1c"
|
||||
integrity sha512-V279cL906++hn00hkL1xAr/y5OjjxPYWic1g0yTJFmqdbdWKthdcuP3XBvmmwP9AzFBT51DlPgXz56HItle1Ug==
|
||||
|
||||
svelte-preprocess@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-5.1.1.tgz#53d7107c2e8b307afd4418e06239177c4de12025"
|
||||
integrity sha512-p/Dp4hmrBW5mrCCq29lEMFpIJT2FZsRlouxEc5qpbOmXRbaFs7clLs8oKPwD3xCFyZfv1bIhvOzpQkhMEVQdMw==
|
||||
dependencies:
|
||||
"@types/pug" "^2.0.6"
|
||||
detect-indent "^6.1.0"
|
||||
magic-string "^0.27.0"
|
||||
sorcery "^0.11.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
svelte@^4.2.8:
|
||||
version "4.2.8"
|
||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-4.2.8.tgz#a279d8b6646131ffb11bc692840f8839b8ae4ed1"
|
||||
integrity sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "^2.2.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||
"@jridgewell/trace-mapping" "^0.3.18"
|
||||
acorn "^8.9.0"
|
||||
aria-query "^5.3.0"
|
||||
axobject-query "^3.2.1"
|
||||
code-red "^1.0.3"
|
||||
css-tree "^2.3.1"
|
||||
estree-walker "^3.0.3"
|
||||
is-reference "^3.0.1"
|
||||
locate-character "^3.0.0"
|
||||
magic-string "^0.30.4"
|
||||
periscopic "^3.1.0"
|
||||
|
||||
svg-tags@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
|
||||
|
@ -8070,6 +8033,11 @@ typedarray@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||
|
||||
"typescript@^2.9.2 || ^3.0.0 || ^4.0.0":
|
||||
version "4.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
|
||||
typical@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
|
||||
|
@ -8085,7 +8053,7 @@ ua-parser-js@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c"
|
||||
integrity sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
uglify-js@^3.1.4, uglify-js@^3.5.1:
|
||||
version "3.17.4"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
|
||||
integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==
|
||||
|
@ -8136,6 +8104,11 @@ update-browserslist-db@^1.0.13:
|
|||
escalade "^3.1.1"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
upper-case@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
|
||||
integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
|
|
Loading…
Reference in New Issue