Add admin page component

next
mashirozx 2021-07-15 18:22:43 +08:00
parent edeab203e0
commit 4f0c68a46b
30 changed files with 1391 additions and 134 deletions

View File

@ -79,6 +79,8 @@ class AdminPageHelper extends ViteHelper
public function enqueue_common_scripts()
{
wp_enqueue_media();
wp_enqueue_style('style.css', get_template_directory_uri() . '/style.css');
wp_enqueue_style('fontawesome-free', 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/css/all.min.css');

View File

@ -30,12 +30,16 @@
"@formatjs/intl": "^1.13.2",
"@material/button": "^12.0.0-canary.068fd5028.0",
"@material/card": "^12.0.0-canary.068fd5028.0",
"@material/checkbox": "^12.0.0-canary.e1703bed9.0",
"@material/chips": "^12.0.0-canary.068fd5028.0",
"@material/dialog": "^12.0.0-canary.068fd5028.0",
"@material/elevation": "^12.0.0-canary.068fd5028.0",
"@material/form-field": "^12.0.0-canary.e1703bed9.0",
"@material/list": "^12.0.0-canary.068fd5028.0",
"@material/menu": "^12.0.0-canary.068fd5028.0",
"@material/radio": "^12.0.0-canary.9f68a932e.0",
"@material/ripple": "^12.0.0-canary.068fd5028.0",
"@material/switch": "^12.0.0-canary.9f68a932e.0",
"@material/tab-bar": "^12.0.0-canary.22d29cbb4.0",
"@material/textfield": "^12.0.0-canary.068fd5028.0",
"@material/theme": "^12.0.0-canary.068fd5028.0",
@ -95,7 +99,8 @@
"vite": "^2.4.2",
"vite-plugin-svgicon": "^1.0.0-alpha.0",
"vue-jest": "^5.0.0-alpha.10",
"vue-tsc": "^0.2.0"
"vue-tsc": "^0.2.0",
"wp-types": "^2.10.0"
},
"engines": {
"npm": "please-use-yarn",

View File

@ -16,32 +16,49 @@
v-for="(tabKey, tabKeyIndex) in tabKeys"
:key="tabKeyIndex"
>
<div class="tab-page__content mdc-typography">
<h1 class="mdc-typography--headline5">{{ options[tabKey].title }}</h1>
<div class="tab-page__content">
<h1 class="row__wrapper--title">{{ options[tabKey].title }}</h1>
<p class="row__wrapper--desc" v-if="options[tabKey].desc"> {{ options[tabKey].desc }} </p>
<div
class="row__wrapper--options"
v-for="(option, optionIndex) in options[tabKey].options"
:key="optionIndex"
>
{{ option.namespace }}
<OptionItem :option="option"></OptionItem>
</div>
</div>
</SwiperSlide>
</Swiper>
<div class="buttons__wrapper">
<NormalButton icon="fas fa-save" context="Save" :contained="true"></NormalButton>
<NormalButton icon="fas fa-upload" context="Import" :contained="true"></NormalButton>
<NormalButton icon="fas fa-download" context="Export" :contained="true"></NormalButton>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, Ref, watch, nextTick } from 'vue'
import {
defineComponent,
ref,
Ref,
watch,
nextTick,
watchEffect,
onMounted,
onBeforeUnmount,
} from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Swiper as SwiperInterface } from 'swiper'
import { useInjector } from '@/hooks'
import store from './store'
import options from '@/admin/options'
import TabBar from '@/components/tabBar/TabBar.vue'
import OptionItem from './OptionItem.vue'
import NormalButton from '@/components/buttons/NormalButton.vue'
export default defineComponent({
components: { TabBar, Swiper, SwiperSlide },
components: { TabBar, Swiper, SwiperSlide, OptionItem, NormalButton },
setup() {
// UI controllers
const currentTabIndex: Ref<number> = ref(0)
@ -55,7 +72,16 @@ export default defineComponent({
}
watch(currentTabIndex, (current) => swiperRef.value?.slideTo(current))
nextTick(() => swiperRef.value?.updateAutoHeight(100))
const updateAutoHeight = () => swiperRef.value?.updateAutoHeight(0)
// nextTick(() => updateAutoHeight())
// watchEffect(() => updateAutoHeight())
onMounted(() => {
const timer = setInterval(() => updateAutoHeight(), 100)
onBeforeUnmount(() => clearInterval(timer))
})
// data controllers
const { config, setConfig } = useInjector(store)
@ -87,5 +113,14 @@ export default defineComponent({
}
}
}
> .buttons__wrapper {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: center;
gap: 12px;
padding: 12px;
width: calc(100% - 24px);
}
}
</style>

View File

@ -1,24 +1,168 @@
<template>
<div class="option__container">
<h1 class="mdc-typography--headline6">{{ $props.namespace }}</h1>
<h3 class="column__wrapper--label"> {{ title }} </h3>
<div class="column__wrapper--main">
<div class="row__wrapper--option">
<OutlinedInput
v-if="type === 'string'"
v-model:content="optionResultRef"
v-bind="binds"
></OutlinedInput>
<OutlinedTextarea
v-else-if="type === 'longString'"
v-model:content="optionResultRef"
v-bind="binds"
></OutlinedTextarea>
<Selection
v-else-if="type === 'selection'"
v-model:result="optionResultRef"
v-bind="binds"
></Selection>
<Choose
v-else-if="type === 'choose'"
v-model:result="optionResultRef"
v-bind="binds"
></Choose>
<MediaPicker
v-else-if="type === 'mediaPicker'"
v-model:selection="optionResultRef"
v-bind="binds"
></MediaPicker>
<Switcher
v-else-if="type === 'switcher'"
v-model:checked="optionResultRef"
v-bind="binds"
></Switcher>
</div>
<p class="row__wrapper--desc" v-if="desc">
<i class="fas fa-info-circle"></i> <span v-html="desc"></span>
</p>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent, ref, watch } from 'vue'
import { useInjector } from '@/hooks'
import store from './store'
const defaultOption = {}
import validator from './validator'
import OutlinedInput from '@/components/inputs/OutlinedInput.vue'
import OutlinedTextarea from '@/components/inputs/OutlinedTextarea.vue'
import Selection from '@/components/checkbox/Selection.vue'
import MediaPicker from '@/admin/components/MediaPicker.vue'
import Choose from '@/components/radio/Choose.vue'
import Switcher from '@/components/switcher/Switcher.vue'
export default defineComponent({
components: { OutlinedInput, OutlinedTextarea, Selection, MediaPicker, Choose, Switcher },
props: {
options: { type: Object, default: () => defaultOption },
option: { type: Object, required: true },
},
emits: [],
setup(props, { emit }) {
const { config, setConfig } = useInjector(store)
return {}
const { namespace, type, title, desc, binds } = props.option
const { config, updateOption } = useInjector(store)
const optionResultRef = ref(config.value[namespace] ?? props.option.default)
watch(
optionResultRef,
(result) => {
if (validator(result, type).pass) {
updateOption(config, namespace, result)
}
},
{ immediate: true, deep: true }
)
return { config, optionResultRef, type, title, desc, binds }
},
})
</script>
<style lang="scss" scoped>
.option__container {
display: flex;
flex-flow: row nowrap;
align-items: space-between;
justify-content: flex-start;
> .column__wrapper {
&--label {
flex: 0 0 auto;
width: 200px;
padding-top: 15px;
}
&--main {
width: 100%;
flex: 1 1 auto;
display: flex;
flex-flow: column nowrap;
align-items: space-between;
justify-content: flex-start;
padding-top: 12px;
> .row__wrapper {
&--option {
width: 100%;
}
&--desc {
font-size: 14px;
color: #646970;
}
}
}
}
}
// custom
// ::v-deep() {
// .mdc-checkbox {
// transform: translateX(-13px);
// }
// }
// Polyfill WP default styles
::v-deep() {
input.disabled,
input:disabled,
select.disabled,
select:disabled,
textarea.disabled,
textarea:disabled {
background: unset;
border-color: unset;
box-shadow: unset;
color: unset;
}
input[type='checkbox'],
input[type='radio'] {
border: unset;
}
input[type='checkbox']:focus,
input[type='color']:focus,
input[type='date']:focus,
input[type='datetime-local']:focus,
input[type='datetime']:focus,
input[type='email']:focus,
input[type='month']:focus,
input[type='number']:focus,
input[type='password']:focus,
input[type='radio']:focus,
input[type='search']:focus,
input[type='tel']:focus,
input[type='text']:focus,
input[type='time']:focus,
input[type='url']:focus,
input[type='week']:focus,
select:focus,
textarea:focus {
border-color: unset;
box-shadow: unset;
outline: unset;
}
.notice-error,
div.error {
border-left-color: unset;
}
}
</style>

View File

@ -1 +0,0 @@
# The admin's panel sub-project

View File

@ -0,0 +1,194 @@
<template>
<div class="picker__container">
<div class="row__wrapper--input">
<div class="input__wrapper">
<OutlinedInput v-model:content="userInput" label="Input remote URL here"></OutlinedInput>
</div>
<div class="button__wrapper" @click="add">
<NormalButton icon="fas fas fa-link" context="Add" :contained="true"></NormalButton>
</div>
<div class="button__wrapper" @click="open">
<NormalButton icon="fas fa-box-open" context="Pick" :contained="true"></NormalButton>
</div>
</div>
<div class="row__wrapper--preview">
<div class="image__box" v-for="(item, index) in selection" :key="index">
<div class="image__wrapper">
<Image :src="item.url" :avatar="false" :draggable="false"></Image>
</div>
<div class="delete__button" @click="del(index)">
<span><i class="fas fa-trash-alt"></i></span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, Ref } from 'vue'
import { cloneDeep, remove } from 'lodash'
import uniqueHash from '@/utils/uniqueHash'
import { isUrl } from '@/utils/urlHelper'
import NormalButton from '@/components/buttons/NormalButton.vue'
import OutlinedInput from '@/components/inputs/OutlinedInput.vue'
export default defineComponent({
components: { NormalButton, OutlinedInput },
props: {
title: { type: String, default: 'Select Media' },
button: { type: String, default: 'Use this media' },
type: { type: String, default: 'image' }, // video
multiple: { type: Boolean, default: true },
selection: { type: Array, default: () => [] }, // [{url,id}]
},
emits: ['update:selection'],
setup(props, { emit }) {
const selection: Ref<{ id: number; url: string }[]> = ref(
props.selection as { id: number; url: string }[]
)
const userInput = ref('')
const frame = (window as any).wp.media({
id: `media-frame-${uniqueHash()}`,
title: props.title,
multiple: props.multiple ? 'add' : false,
allowLocalEdits: true,
displaySettings: true,
displayUserSettings: true,
library: {
// author: uid, // specific user-posted attachment
type: props.type,
},
button: { text: props.button },
})
frame.on('select', () => {
const result = (frame.state().get('selection') as any[]).map((item) => {
const { id, url } = item.attributes
return { id: id as number, url: url as string }
})
const selected = cloneDeep(selection.value)
// Delete unchecked items
remove(selected, (item) => item.id > 0 && result.map((i) => i.id).indexOf(item.id) < 0)
// Delete existing items
remove(result, (item) => selected.map((i) => i.id).indexOf(item.id) >= 0)
selection.value = [...selected, ...result]
})
// frame.on('close', () => {
// console.log(frame.state().get('selection').toJSON())
// })
frame.on('open', () => {
const result = frame.state().get('selection')
const preSelectIds = selection.value
.map((item) => (item.id ? (window as any).wp.media.attachment(item.id) : NaN))
.filter((attachment) => attachment)
result.add(preSelectIds)
})
const open = () => {
frame.open()
}
const add = () => {
const url = userInput.value
if (isUrl(url).state) {
if (selection.value.map((item) => item.url).indexOf(url) < 0) {
selection.value.push({ id: 0, url })
userInput.value = ''
} else {
// TODO
console.warn('Duplicate URLs')
}
} else {
// TODO
console.warn('Invalid URL')
}
}
const del = (index: number) => {
remove(selection.value, (item, itemIndex) => index === itemIndex)
}
watch(selection, (value) => emit('update:selection', value), { deep: true })
return { open, add, del, userInput, selection }
},
})
</script>
<style lang="scss" scoped>
.picker__container {
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
align-items: center;
gap: 12px;
> * {
width: 100%;
}
> .row__wrapper {
&--input {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
gap: 12px;
> .input__wrapper {
flex: 1 1 auto;
width: 100%;
}
> .button__wrapper {
flex: 0 0 auto;
}
}
&--preview {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: center;
gap: 12px;
> .image__box {
position: relative;
overflow: hidden;
> .image__wrapper {
width: 200px;
height: 200px;
border: 1px solid #bdbdbd;
border-radius: 5px;
overflow: hidden;
}
> .delete__button {
position: absolute;
top: 0;
right: 0;
width: 30px;
height: 30px;
background: #757575;
font-size: 16px;
color: #ffffff;
border-radius: 0 5px 0 5px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
opacity: 0;
transition: all 0.3s ease-in-out;
}
&:hover {
.delete__button {
opacity: 1;
}
}
}
}
}
}
</style>

View File

@ -2,13 +2,18 @@
@use '@material/elevation/mdc-elevation';
@use '@material/button/mdc-button';
@use "@material/textfield/mdc-text-field";
@use '@material/chips/deprecated/mdc-chips';
@use '@material/list/mdc-list';
// @use '@material/chips/deprecated/mdc-chips';
// @use '@material/list/mdc-list';
@use '@material/card/mdc-card';
// local only
@use "@material/tab-bar/mdc-tab-bar";
@use "@material/tab-scroller/mdc-tab-scroller";
@use "@material/tab-indicator/mdc-tab-indicator";
@use "@material/tab/mdc-tab";
@use '@material/typography/mdc-typography';
// @use '@material/typography/mdc-typography';
@use "@material/checkbox/mdc-checkbox";
// @use "@material/form-field/mdc-form-field";
@use "@material/radio/mdc-radio";
// @use "@material/switch/deprecated/mdc-switch";
@use '@material/switch/styles';

View File

@ -1,11 +1,15 @@
export interface Options {
[tag: string]: {
title: string
desc?: string
icon: string
options: Array<{
namespace: string
title: string
desc?: string
type: string
default: any
binds?: { [key: string]: any }
}>
}
}
@ -13,17 +17,87 @@ export interface Options {
const options: Options = {
basic: {
title: 'Basic',
desc: 'The basic options',
icon: 'fas fa-address-card',
options: [
{
namespace: 'basic.siteTitle',
title: 'Site title',
desc: 'The site title',
type: 'string',
default: 'Opps',
},
{
namespace: 'basic.userName',
type: 'string',
default: 'Mashiro',
namespace: 'basic.switcher',
title: 'Switcher',
type: 'switcher',
default: true,
binds: {
positiveLabel: 'current on',
negativeLabel: 'current off',
disabled: false,
},
},
{
namespace: 'basic.chooseTest',
title: 'Choose Test',
desc: 'wooooo',
type: 'choose',
default: NaN,
binds: {
options: [
{ label: 'op 1', disabled: false },
{ label: 'op 2', disabled: false },
{ label: 'op 3', disabled: false },
{ label: 'op 4', disabled: true },
],
max: 2,
},
},
{
namespace: 'basic.optionsTest',
title: 'Option Test',
desc: 'wooooo',
type: 'selection',
default: [true, false, true],
binds: {
options: [
{ label: 'op 1', disabled: false },
{ label: 'op 2', disabled: false },
{ label: 'op 3', disabled: false },
{ label: 'op 4', disabled: true },
],
max: 2,
},
},
{
namespace: 'basic.longString',
title: 'Long string',
desc: 'A long string',
type: 'longString',
default: 'Opps',
},
{
namespace: 'basic.mediaPicker',
title: 'Image picker',
desc: 'Media picker',
type: 'mediaPicker',
default: [
{
id: 0,
url: 'https://view.moezx.cc/images/2021/07/02/d5ab73174d18652d890e2f4d1b9bef8f.gif',
},
{
id: 0,
url: 'https://view.moezx.cc/images/2021/07/02/a90553bf5b67770e87a89b2ce204eaa7.gif',
},
],
binds: {
title: 'Select Media',
button: 'Use this media',
type: 'image',
multiple: true,
},
},
],
},
@ -31,8 +105,14 @@ const options: Options = {
title: 'Social',
icon: 'fas fa-users',
options: [
{ namespace: 'social.github', type: 'string', default: 'mashirozx' },
{ namespace: 'social.weibo', type: 'string', default: 'mashirozx' },
{
namespace: 'social.github',
title: 'Github username',
desc: 'Your <a href="https://github.com" target="_blank">Github</a> username',
type: 'string',
default: 'mashirozx',
},
{ namespace: 'social.weibo', title: 'Weibo username', type: 'string', default: 'mashirozx' },
],
},
other: {
@ -41,6 +121,7 @@ const options: Options = {
options: [
{
namespace: 'other.hello',
title: 'Hello world',
type: 'string',
default: 'world',
},

View File

@ -35,5 +35,5 @@ export default function auth(): object {
// return data
// }
return { config, setConfig }
return { config, updateOption }
}

View File

@ -0,0 +1,20 @@
import logger from '@/utils/logger'
export default function validator<T>(value: T, type: string): { pass: boolean; msg?: string } {
switch (type) {
case 'string':
case 'longString':
return { pass: typeof value === 'string' }
case 'choose':
return { pass: typeof value === 'number' }
case 'switcher':
return { pass: typeof value === 'boolean' }
case 'selection':
return { pass: value instanceof Array }
case 'mediaPicker':
return { pass: value instanceof Array }
default:
const msg = `No such type ${type}`
logger('error', msg)
return { pass: false, msg }
}
}

View File

@ -0,0 +1,93 @@
<template>
<div class="checkbox__container">
<div class="mdc-checkbox mdc-checkbox--touch" :ref="setElRef" @change="handleChange">
<input type="checkbox" class="mdc-checkbox__native-control" :id="`checkbox-${id}`" />
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path
class="mdc-checkbox__checkmark-path"
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59"
/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple"></div>
</div>
<label class="label" :for="`checkbox-${id}`" :id="`checkbox-label-${id}`">
{{ $props.label }}
</label>
</div>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue'
import { useElementRef } from '@/hooks'
import useMDCCheckbox from '@/hooks/mdc/useMDCCheckbox'
import uniqueHash from '@/utils/uniqueHash'
export default defineComponent({
props: {
label: { type: String, default: 'This is the label' },
checked: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
},
emits: ['update:checked'],
setup(props, { emit }) {
const id = uniqueHash()
const [elRef, setElRef] = useElementRef()
const MDCCheckboxRef = useMDCCheckbox(elRef)
const checked = ref(props.checked)
const handleChange = () => {
if (MDCCheckboxRef.value) {
checked.value = MDCCheckboxRef.value.checked
}
}
watch(
() => props.checked,
(value) => {
checked.value = value
if (MDCCheckboxRef.value) MDCCheckboxRef.value.checked = value
}
)
watch(
() => props.disabled,
(value) => {
if (MDCCheckboxRef.value) {
MDCCheckboxRef.value.disabled = value
// MDCCheckboxRef.value.indeterminate = value
}
}
)
watch(MDCCheckboxRef, (MDCCheckbox) => {
if (MDCCheckbox) {
MDCCheckbox.checked = checked.value
MDCCheckbox.disabled = props.disabled
// MDCCheckbox.indeterminate = props.disabled
}
})
watch(checked, (value) => emit('update:checked', value))
return { checked, id, setElRef, handleChange }
},
})
</script>
<style lang="scss" scoped>
.checkbox__container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.label {
user-select: none;
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<section class="selection__container">
<div class="checkbox__wrapper" v-for="(option, index) in $props.options" :key="index">
<Checkbox
v-model:checked="resultRef[index]"
:label="option.label"
:disabled="(isMax && !resultRef[index]) || option.disabled"
></Checkbox>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed } from 'vue'
import Checkbox from './Checkbox.vue'
export default defineComponent({
components: { Checkbox },
props: {
max: { type: Number, default: -1 },
result: { type: Array, default: () => [true, false, true] },
options: {
type: Array,
default: () => [
{ label: 'op 1', disabled: false },
{ label: 'op 2', disabled: false },
{ label: 'op 3', disabled: false },
],
},
},
emits: ['update:result'],
setup(props, { emit }) {
const resultRef = ref(props.result)
const isMax = computed(() => {
const { max } = props
if (max < 0) {
return false
} else {
const count = resultRef.value.filter((x) => x).length
return count >= max
}
})
// watch(resultRef,result=>{
// if()
// })
return { resultRef, isMax }
},
})
</script>

View File

@ -3,6 +3,7 @@
:class="[
'mdc-text-field',
'mdc-text-field--outlined',
{ 'mdc-text-field--no-label': !$props.label },
{ 'mdc-text-field--with-leading-icon': $props.leadingIcon },
{ 'mdc-text-field--with-trailing-icon': $props.trailingIcon },
]"
@ -10,7 +11,7 @@
>
<span class="mdc-notched-outline">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__notch">
<span class="mdc-notched-outline__notch" v-if="$props.label">
<span class="mdc-floating-label" :id="id">{{ $props.label }}</span>
</span>
<span class="mdc-notched-outline__trailing"></span>
@ -37,8 +38,9 @@
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
import { MD5 } from 'crypto-js'
import { useElementRef, useMDCTextField } from '@/hooks'
import uniqueHash from '@/utils/uniqueHash'
import { useElementRef } from '@/hooks'
import useMDCTextField from '@/hooks/mdc/useMDCTextField'
export default defineComponent({
props: {
@ -51,7 +53,7 @@ export default defineComponent({
},
emits: ['update:content', 'blur'],
setup(props, { emit }) {
const id = MD5(Math.random().toString()).toString().slice(0, 8)
const id = uniqueHash()
const [textareaRef, setTextareaRef] = useElementRef()
useMDCTextField(textareaRef)

View File

@ -4,13 +4,14 @@
'mdc-text-field',
'mdc-text-field--outlined',
'mdc-text-field--textarea',
{ 'mdc-text-field--no-label': !$props.label },
{ 'mdc-text-field--with-internal-counter': showCounter },
]"
:ref="setTextareaRef"
>
<span class="mdc-notched-outline">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__notch">
<span class="mdc-notched-outline__notch" v-if="$props.label">
<span class="mdc-floating-label" :id="id">{{ label }}</span>
</span>
<span class="mdc-notched-outline__trailing"></span>
@ -31,8 +32,9 @@
<script lang="ts">
import { defineComponent, computed, ref, watch } from 'vue'
import { MD5 } from 'crypto-js'
import { useElementRef, useMDCTextField } from '@/hooks'
import uniqueHash from '@/utils/uniqueHash'
import { useElementRef } from '@/hooks'
import useMDCTextField from '@/hooks/mdc/useMDCTextField'
export default defineComponent({
props: {
@ -47,7 +49,7 @@ export default defineComponent({
},
emits: ['update:content'],
setup(props, { emit }) {
const id = MD5(Math.random().toString()).toString().slice(0, 8)
const id = uniqueHash()
const [textareaRef, setTextareaRef] = useElementRef()
useMDCTextField(textareaRef)

View File

@ -0,0 +1,82 @@
<template>
<section class="selection__container">
<div class="checkbox__wrapper" v-for="(option, index) in $props.options" :key="index">
<RadioButton
v-model:checked="arrayRef[index]"
:label="option.label"
:disabled="option.disabled"
></RadioButton>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed, Ref } from 'vue'
import { cloneDeep } from 'lodash'
import RadioButton from './RadioButton.vue'
export default defineComponent({
components: { RadioButton },
props: {
result: { type: Number, default: () => NaN },
options: {
type: Array,
default: () => [
{ label: 'op 1', disabled: false },
{ label: 'op 2', disabled: false },
{ label: 'op 3', disabled: false },
],
},
},
emits: ['update:result'],
setup(props, { emit }) {
const arrayRef: Ref<boolean[]> = ref(
Array(props.options.length)
.fill(false)
.map((item, index) => index === props.result)
)
// TODO: watcher's bug on deep mode: https://github.com/vuejs/vue/issues/2164
const cacheArrayRef = computed(() => cloneDeep(arrayRef.value))
watch(
cacheArrayRef,
(n, o) => {
if (n.indexOf(true) < 0) return
n.forEach((_n, index) => {
if (_n && _n !== o[index]) {
const a = cloneDeep(arrayRef.value)
a.fill(false)
a[index] = true
arrayRef.value = a
}
})
},
{ deep: true }
)
watch(
arrayRef,
(arr) => {
const value = arr.indexOf(true)
if (value > -1) {
emit('update:result', value)
} else {
emit('update:result', NaN)
}
},
{ deep: true }
)
watch(
() => props.options,
(options) =>
(arrayRef.value = Array(options.length)
.fill(false)
.map((item, index) => index === props.result))
)
return { arrayRef }
},
})
</script>

View File

@ -0,0 +1,91 @@
<template>
<div class="radio__container">
<div class="mdc-radio" :ref="setElRef">
<input
class="mdc-radio__native-control"
type="checkbox"
:id="`radio-${id}`"
:name="`radio-${id}`"
@change="handleChange"
/>
<div class="mdc-radio__background">
<div class="mdc-radio__outer-circle"></div>
<div class="mdc-radio__inner-circle"></div>
</div>
<div class="mdc-radio__ripple"></div>
</div>
<label class="label" :for="`radio-${id}`">{{ $props.label }}</label>
</div>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue'
import { useElementRef } from '@/hooks'
import uniqueHash from '@/utils/uniqueHash'
import useMDCRadio from '@/hooks/mdc/useMDCRadio'
export default defineComponent({
props: {
label: { type: String, default: 'This is the label' },
checked: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
},
emits: ['update:checked'],
setup(props, { emit }) {
const id = uniqueHash()
const [elRef, setElRef] = useElementRef()
const MDCRadioRef = useMDCRadio(elRef)
const checked = ref(props.checked)
const handleChange = (event: Event) => {
if (!props.disabled) checked.value = !checked.value
}
watch(
() => props.checked,
(value) => {
checked.value = value
if (MDCRadioRef.value) MDCRadioRef.value.checked = value
}
)
watch(
() => props.disabled,
(value) => {
if (MDCRadioRef.value) {
MDCRadioRef.value.disabled = value
}
}
)
watch(MDCRadioRef, (MDCCheckbox) => {
if (MDCCheckbox) {
MDCCheckbox.checked = checked.value
MDCCheckbox.disabled = props.disabled
}
})
watch(checked, (value) => {
if (MDCRadioRef.value) MDCRadioRef.value.checked = value
emit('update:checked', value)
})
return { checked, id, setElRef, handleChange }
},
})
</script>
<style lang="scss" scoped>
.radio__container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.label {
user-select: none;
}
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<div class="switcher__container">
<button
:id="`switch-${id}`"
class="mdc-switch mdc-switch--unselected"
role="switch"
:aria-checked="checked"
:ref="setElRef"
@click="handleChange"
>
<div class="mdc-switch__track"></div>
<div class="mdc-switch__handle-track">
<div class="mdc-switch__handle">
<div class="mdc-switch__shadow">
<div class="mdc-elevation-overlay"></div>
</div>
<div class="mdc-switch__ripple"></div>
<div class="mdc-switch__icons">
<svg class="mdc-switch__icon mdc-switch__icon--on" viewBox="0 0 24 24">
<path d="M19.69,5.23L8.96,15.96l-4.23-4.23L2.96,13.5l6,6L21.46,7L19.69,5.23z" />
</svg>
<svg class="mdc-switch__icon mdc-switch__icon--off" viewBox="0 0 24 24">
<path d="M20 13H4v-2h16v2z" />
</svg>
</div>
</div>
</div>
</button>
<label class="label" :for="`switch-${id}`">
{{ checked ? $props.positiveLabel : $props.negativeLabel }}
</label>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
import uniqueHash from '@/utils/uniqueHash'
import { useElementRef } from '@/hooks'
import useMDCSwitch from '@/hooks/mdc/useMDCSwitch'
export default defineComponent({
props: {
positiveLabel: { type: String, default: 'current on' },
negativeLabel: { type: String, default: 'current off' },
checked: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
},
emits: ['update:checked'],
setup(props, { emit }) {
const id = uniqueHash()
const [elRef, setElRef] = useElementRef()
const MDCSwitchRef = useMDCSwitch(elRef)
const checked = ref(props.checked)
const handleChange = () => {
if (MDCSwitchRef.value) {
checked.value = !MDCSwitchRef.value.selected
}
}
watch(
() => props.checked,
(value) => {
checked.value = value
if (MDCSwitchRef.value) MDCSwitchRef.value.selected = !value
},
{ immediate: true }
)
watch(
() => props.disabled,
(value) => {
if (MDCSwitchRef.value) {
MDCSwitchRef.value.disabled = value
}
}
)
watch(MDCSwitchRef, (MDCCheckbox) => {
if (MDCCheckbox) {
MDCCheckbox.selected = checked.value
MDCCheckbox.disabled = props.disabled
}
})
watch(checked, (value) => emit('update:checked', value))
return { id, setElRef, handleChange, checked }
},
})
</script>
<style lang="scss" scoped>
.switcher__container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.label {
user-select: none;
padding-left: 10px;
}
}
</style>

View File

@ -33,7 +33,8 @@
import { defineComponent, onMounted, watch } from 'vue'
import { useState } from '@/hooks'
import { MD5 } from 'crypto-js'
import { useElementRef, useMDCTabBar } from '@/hooks'
import { useElementRef } from '@/hooks'
import useMDCTabBar from '@/hooks/mdc/useMDCTabBar'
export default defineComponent({
props: {

View File

@ -7,7 +7,7 @@ import useResizeObserver from './useResizeObserver'
import useReachElementSide from './useReachElementSide'
import { useElementRef, useElementRefs } from './useElementRef'
import useOffsetDistance from './useOffsetDistance'
import { useMDCRipple, useMDCDialog, useMDCTextField, useMDCTabBar } from './mdc'
import useMDCRipple from './mdc/useMDCRipple'
export {
useState,
@ -22,9 +22,6 @@ export {
useWindowResize,
useResizeObserver,
useMDCRipple,
useMDCDialog,
useMDCTextField,
useMDCTabBar,
useReachElementSide,
useElementRef,
useElementRefs,

View File

@ -1,95 +0,0 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCRipple } from '@material/ripple'
import { MDCDialog } from '@material/dialog'
import { MDCTextField } from '@material/textfield'
import { MDCTabBar } from '@material/tab-bar'
export const useMDCRipple = <El>(
elementRef: El extends Element ? Element : Ref<Element | null>,
unbounded = false
) => {
const rippleRef: Ref<MDCRipple | null> = ref(null)
if (elementRef instanceof Element) {
rippleRef.value = new MDCRipple(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
rippleRef.value = new MDCRipple(element)
if (unbounded) rippleRef.value.unbounded = true
}
})
}
onBeforeUnmount(() => {
rippleRef.value?.destroy()
})
return rippleRef
}
export const useMDCDialog = <El>(
elementRef: El extends Element ? Element : Ref<Element | null>
) => {
const dialogRef: Ref<MDCDialog | null> = ref(null)
if (elementRef instanceof Element) {
dialogRef.value = new MDCDialog(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
dialogRef.value = new MDCDialog(element)
}
})
}
onBeforeUnmount(() => {
dialogRef.value?.destroy()
})
return dialogRef
}
export const useMDCTextField = <El>(
elementRef: El extends Element ? Element : Ref<Element | null>
) => {
const textFieldRef: Ref<MDCTextField | null> = ref(null)
if (elementRef instanceof Element) {
textFieldRef.value = new MDCTextField(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
textFieldRef.value = new MDCTextField(element)
}
})
}
onBeforeUnmount(() => {
textFieldRef.value?.destroy()
})
return textFieldRef
}
export const useMDCTabBar = <El>(
elementRef: El extends Element ? Element : Ref<Element | null>
) => {
const tabBarRef: Ref<MDCTabBar | null> = ref(null)
if (elementRef instanceof Element) {
tabBarRef.value = new MDCTabBar(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
tabBarRef.value = new MDCTabBar(element)
}
})
}
onBeforeUnmount(() => {
tabBarRef.value?.destroy()
})
return tabBarRef
}

View File

@ -0,0 +1,24 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCCheckbox } from '@material/checkbox'
const useMDCCheckbox = <El>(elementRef: El extends Element ? Element : Ref<Element | null>) => {
const mdcRef: Ref<MDCCheckbox | null> = ref(null)
if (elementRef instanceof Element) {
mdcRef.value = new MDCCheckbox(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
mdcRef.value = new MDCCheckbox(element)
}
})
}
onBeforeUnmount(() => {
mdcRef.value?.destroy()
})
return mdcRef
}
export default useMDCCheckbox

View File

@ -0,0 +1,24 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCDialog } from '@material/dialog'
const useMDCDialog = <El>(elementRef: El extends Element ? Element : Ref<Element | null>) => {
const mdcRef: Ref<MDCDialog | null> = ref(null)
if (elementRef instanceof Element) {
mdcRef.value = new MDCDialog(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
mdcRef.value = new MDCDialog(element)
}
})
}
onBeforeUnmount(() => {
mdcRef.value?.destroy()
})
return mdcRef
}
export default useMDCDialog

View File

@ -0,0 +1,24 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCRadio } from '@material/radio'
const useMDCRadio = <El>(elementRef: El extends Element ? Element : Ref<Element | null>) => {
const mdcRef: Ref<MDCRadio | null> = ref(null)
if (elementRef instanceof Element) {
mdcRef.value = new MDCRadio(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
mdcRef.value = new MDCRadio(element)
}
})
}
onBeforeUnmount(() => {
mdcRef.value?.destroy()
})
return mdcRef
}
export default useMDCRadio

View File

@ -0,0 +1,28 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCRipple } from '@material/ripple'
const useMDCRipple = <El>(
elementRef: El extends Element ? Element : Ref<Element | null>,
unbounded = false
) => {
const rippleRef: Ref<MDCRipple | null> = ref(null)
if (elementRef instanceof Element) {
rippleRef.value = new MDCRipple(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
rippleRef.value = new MDCRipple(element)
if (unbounded) rippleRef.value.unbounded = true
}
})
}
onBeforeUnmount(() => {
rippleRef.value?.destroy()
})
return rippleRef
}
export default useMDCRipple

View File

@ -0,0 +1,24 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCSwitch } from '@material/switch'
const useMDCSwitch = <El>(elementRef: El extends Element ? Element : Ref<Element | null>) => {
const mdcRef: Ref<MDCSwitch | null> = ref(null)
if (elementRef instanceof Element) {
mdcRef.value = new MDCSwitch(elementRef as HTMLButtonElement)
} else {
watch(elementRef, (element) => {
if (element) {
mdcRef.value = new MDCSwitch(element as HTMLButtonElement)
}
})
}
onBeforeUnmount(() => {
mdcRef.value?.destroy()
})
return mdcRef
}
export default useMDCSwitch

View File

@ -0,0 +1,24 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCTabBar } from '@material/tab-bar'
const useMDCTabBar = <El>(elementRef: El extends Element ? Element : Ref<Element | null>) => {
const tabBarRef: Ref<MDCTabBar | null> = ref(null)
if (elementRef instanceof Element) {
tabBarRef.value = new MDCTabBar(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
tabBarRef.value = new MDCTabBar(element)
}
})
}
onBeforeUnmount(() => {
tabBarRef.value?.destroy()
})
return tabBarRef
}
export default useMDCTabBar

View File

@ -0,0 +1,24 @@
import { ref, Ref, watch, onBeforeUnmount } from 'vue'
import { MDCTextField } from '@material/textfield'
const useMDCTextField = <El>(elementRef: El extends Element ? Element : Ref<Element | null>) => {
const textFieldRef: Ref<MDCTextField | null> = ref(null)
if (elementRef instanceof Element) {
textFieldRef.value = new MDCTextField(elementRef)
} else {
watch(elementRef, (element) => {
if (element) {
textFieldRef.value = new MDCTextField(element)
}
})
}
onBeforeUnmount(() => {
textFieldRef.value?.destroy()
})
return textFieldRef
}
export default useMDCTextField

View File

@ -0,0 +1,5 @@
import { MD5 } from 'crypto-js'
export default function () {
return MD5(Math.random().toString()).toString().slice(0, 8)
}

View File

@ -0,0 +1,13 @@
export const isUrl = (url: string): { state: boolean; msg?: any } => {
try {
new URL(url)
} catch (error) {
return {
state: false,
msg: error,
}
}
return {
state: true,
}
}

253
yarn.lock
View File

@ -685,6 +685,20 @@
dependencies:
tslib "^2.1.0"
"@material/animation@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/animation/download/@material/animation-12.0.0-canary.9f68a932e.0.tgz#8deafa2e91db38d4f4ec4303c1eeba8a7e6b1586"
integrity sha1-jer6LpHbONT07EMDwe66in5rFYY=
dependencies:
tslib "^2.1.0"
"@material/animation@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/animation/download/@material/animation-12.0.0-canary.e1703bed9.0.tgz#d3f758c0ae65223b9459cfc3c35cd1313bb377bc"
integrity sha1-0/dYwK5lIjuUWc/Dw1zRMTuzd7w=
dependencies:
tslib "^2.1.0"
"@material/base@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/base/download/@material/base-12.0.0-canary.068fd5028.0.tgz#ba4403e153550bfd44c6f4d74af27c43be94b23b"
@ -699,6 +713,20 @@
dependencies:
tslib "^2.1.0"
"@material/base@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/base/download/@material/base-12.0.0-canary.9f68a932e.0.tgz#416b1ea1646de2585b9f6334b721eb32f27e1b62"
integrity sha1-QWseoWRt4lhbn2M0tyHrMvJ+G2I=
dependencies:
tslib "^2.1.0"
"@material/base@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/base/download/@material/base-12.0.0-canary.e1703bed9.0.tgz#b516620d7217a18955e694589c230d536ec04acd"
integrity sha1-tRZiDXIXoYlV5pRYnCMNU27ASs0=
dependencies:
tslib "^2.1.0"
"@material/button@12.0.0-canary.068fd5028.0", "@material/button@^12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/button/download/@material/button-12.0.0-canary.068fd5028.0.tgz#dea53b2914e4bd48c94fd8d6c3915c377ff6a326"
@ -745,6 +773,21 @@
"@material/touch-target" "12.0.0-canary.068fd5028.0"
tslib "^2.1.0"
"@material/checkbox@^12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/checkbox/download/@material/checkbox-12.0.0-canary.e1703bed9.0.tgz#97956408491070c2ffdf1bb718af23a35b5370c0"
integrity sha1-l5VkCEkQcML/3xu3GK8jo1tTcMA=
dependencies:
"@material/animation" "12.0.0-canary.e1703bed9.0"
"@material/base" "12.0.0-canary.e1703bed9.0"
"@material/density" "12.0.0-canary.e1703bed9.0"
"@material/dom" "12.0.0-canary.e1703bed9.0"
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
"@material/ripple" "12.0.0-canary.e1703bed9.0"
"@material/theme" "12.0.0-canary.e1703bed9.0"
"@material/touch-target" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/chips@^12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/chips/download/@material/chips-12.0.0-canary.068fd5028.0.tgz#92f1f6fa3aef8906a34b3fe9e3f351861d0ab6df"
@ -779,6 +822,20 @@
dependencies:
tslib "^2.1.0"
"@material/density@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/density/download/@material/density-12.0.0-canary.9f68a932e.0.tgz#e1aba9fb3242c3e94223700da4cbf6178bfe3170"
integrity sha1-4aup+zJCw+lCI3ANpMv2F4v+MXA=
dependencies:
tslib "^2.1.0"
"@material/density@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/density/download/@material/density-12.0.0-canary.e1703bed9.0.tgz#6f73f9a73dbc607c20f06a4dfa69bc041fac9a31"
integrity sha1-b3P5pz28YHwg8GpN+mm8BB+smjE=
dependencies:
tslib "^2.1.0"
"@material/dialog@^12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/dialog/download/@material/dialog-12.0.0-canary.068fd5028.0.tgz#ad0990c32b4c3538ce469297ca2b7f1f71abc84a"
@ -815,6 +872,22 @@
"@material/feature-targeting" "12.0.0-canary.22d29cbb4.0"
tslib "^2.1.0"
"@material/dom@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/dom/download/@material/dom-12.0.0-canary.9f68a932e.0.tgz#e9baf1e7622cad75689245a703165e7d3c3eb16b"
integrity sha1-6brx52IsrXVokkWnAxZefTw+sWs=
dependencies:
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/dom@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/dom/download/@material/dom-12.0.0-canary.e1703bed9.0.tgz#46fddbf9a0171409f144d5df618a4fc3887cc432"
integrity sha1-Rv3b+aAXFAnxRNXfYYpPw4h8xDI=
dependencies:
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/elevation@12.0.0-canary.068fd5028.0", "@material/elevation@^12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/elevation/download/@material/elevation-12.0.0-canary.068fd5028.0.tgz#0c670ae52c8f83491b06e31c95917c7ea37ca9ea"
@ -837,6 +910,17 @@
"@material/theme" "12.0.0-canary.22d29cbb4.0"
tslib "^2.1.0"
"@material/elevation@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/elevation/download/@material/elevation-12.0.0-canary.9f68a932e.0.tgz#3bd2d5b30b27e43f5f4d822abe0693e52c6ce297"
integrity sha1-O9LVswsn5D9fTYIqvgaT5Sxs4pc=
dependencies:
"@material/animation" "12.0.0-canary.9f68a932e.0"
"@material/base" "12.0.0-canary.9f68a932e.0"
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
"@material/theme" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/feature-targeting@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/feature-targeting/download/@material/feature-targeting-12.0.0-canary.068fd5028.0.tgz#e043d1f549bfd1a6e647486a354dcb7b04c7afb3"
@ -851,6 +935,20 @@
dependencies:
tslib "^2.1.0"
"@material/feature-targeting@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/feature-targeting/download/@material/feature-targeting-12.0.0-canary.9f68a932e.0.tgz#8ec6b30b59558f8a84f63aa00dd2ce870ff6844c"
integrity sha1-jsazC1lVj4qE9jqgDdLOhw/2hEw=
dependencies:
tslib "^2.1.0"
"@material/feature-targeting@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/feature-targeting/download/@material/feature-targeting-12.0.0-canary.e1703bed9.0.tgz#ce6d8a3111c1b20ecaab13c73c975fa8bd9d59b6"
integrity sha1-zm2KMRHBsg7KqxPHPJdfqL2dWbY=
dependencies:
tslib "^2.1.0"
"@material/floating-label@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/floating-label/download/@material/floating-label-12.0.0-canary.068fd5028.0.tgz#db76cda2d1de4482ddca5df854e1c1e5efe73ac5"
@ -865,6 +963,19 @@
"@material/typography" "12.0.0-canary.068fd5028.0"
tslib "^2.1.0"
"@material/form-field@^12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/form-field/download/@material/form-field-12.0.0-canary.e1703bed9.0.tgz#82788ff08eeed0494598bde00b73992f14c7a585"
integrity sha1-gniP8I7u0ElFmL3gC3OZLxTHpYU=
dependencies:
"@material/base" "12.0.0-canary.e1703bed9.0"
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
"@material/ripple" "12.0.0-canary.e1703bed9.0"
"@material/rtl" "12.0.0-canary.e1703bed9.0"
"@material/theme" "12.0.0-canary.e1703bed9.0"
"@material/typography" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/icon-button@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/icon-button/download/@material/icon-button-12.0.0-canary.068fd5028.0.tgz#eccd2bc14fd984817ae090e9c09433b1e94e1f46"
@ -949,6 +1060,21 @@
"@material/theme" "12.0.0-canary.068fd5028.0"
tslib "^2.1.0"
"@material/radio@^12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/radio/download/@material/radio-12.0.0-canary.9f68a932e.0.tgz#8c329ccb1057aafff079cf28a2efeb01d341d156"
integrity sha1-jDKcyxBXqv/wec8oou/rAdNB0VY=
dependencies:
"@material/animation" "12.0.0-canary.9f68a932e.0"
"@material/base" "12.0.0-canary.9f68a932e.0"
"@material/density" "12.0.0-canary.9f68a932e.0"
"@material/dom" "12.0.0-canary.9f68a932e.0"
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
"@material/ripple" "12.0.0-canary.9f68a932e.0"
"@material/theme" "12.0.0-canary.9f68a932e.0"
"@material/touch-target" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/ripple@12.0.0-canary.068fd5028.0", "@material/ripple@^12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/ripple/download/@material/ripple-12.0.0-canary.068fd5028.0.tgz#e1570b1e45b47bdb6166198abb47236bce3be689"
@ -973,6 +1099,30 @@
"@material/theme" "12.0.0-canary.22d29cbb4.0"
tslib "^2.1.0"
"@material/ripple@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/ripple/download/@material/ripple-12.0.0-canary.9f68a932e.0.tgz#cd923b2560250698215775cd67bd446b6a5e5e9b"
integrity sha1-zZI7JWAlBpghV3XNZ71Ea2peXps=
dependencies:
"@material/animation" "12.0.0-canary.9f68a932e.0"
"@material/base" "12.0.0-canary.9f68a932e.0"
"@material/dom" "12.0.0-canary.9f68a932e.0"
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
"@material/theme" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/ripple@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/ripple/download/@material/ripple-12.0.0-canary.e1703bed9.0.tgz#2fc99d599b4d4fc67834e62e3abc06719a561652"
integrity sha1-L8mdWZtNT8Z4NOYuOrwGcZpWFlI=
dependencies:
"@material/animation" "12.0.0-canary.e1703bed9.0"
"@material/base" "12.0.0-canary.e1703bed9.0"
"@material/dom" "12.0.0-canary.e1703bed9.0"
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
"@material/theme" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/rtl@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/rtl/download/@material/rtl-12.0.0-canary.068fd5028.0.tgz#e7a24ffdc7ef2419433a29c9fed4a0bd79e9086b"
@ -989,6 +1139,22 @@
"@material/theme" "12.0.0-canary.22d29cbb4.0"
tslib "^2.1.0"
"@material/rtl@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/rtl/download/@material/rtl-12.0.0-canary.9f68a932e.0.tgz#268bff8f0d9d6eb43803269ecd401b5a7e69c909"
integrity sha1-Jov/jw2dbrQ4AyaezUAbWn5pyQk=
dependencies:
"@material/theme" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/rtl@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/rtl/download/@material/rtl-12.0.0-canary.e1703bed9.0.tgz#52c0a7b09ce2dc52cac5df126e56be36ef8557fa"
integrity sha1-UsCnsJzi3FLKxd8Sbla+Nu+FV/o=
dependencies:
"@material/theme" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/shape@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/shape/download/@material/shape-12.0.0-canary.068fd5028.0.tgz#e68f58979314a23a3486105a8f91482b8c8e9130"
@ -999,6 +1165,34 @@
"@material/theme" "12.0.0-canary.068fd5028.0"
tslib "^2.1.0"
"@material/shape@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/shape/download/@material/shape-12.0.0-canary.9f68a932e.0.tgz#cb0466b1e8409c3c9e6143fe6d28d6d97d9ee11a"
integrity sha1-ywRmsehAnDyeYUP+bSjW2X2e4Ro=
dependencies:
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
"@material/rtl" "12.0.0-canary.9f68a932e.0"
"@material/theme" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/switch@^12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/switch/download/@material/switch-12.0.0-canary.9f68a932e.0.tgz#2089bcad64039c7d93917faa321392f5dd7dc5cd"
integrity sha1-IIm8rWQDnH2TkX+qMhOS9d19xc0=
dependencies:
"@material/animation" "12.0.0-canary.9f68a932e.0"
"@material/base" "12.0.0-canary.9f68a932e.0"
"@material/density" "12.0.0-canary.9f68a932e.0"
"@material/dom" "12.0.0-canary.9f68a932e.0"
"@material/elevation" "12.0.0-canary.9f68a932e.0"
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
"@material/ripple" "12.0.0-canary.9f68a932e.0"
"@material/rtl" "12.0.0-canary.9f68a932e.0"
"@material/shape" "12.0.0-canary.9f68a932e.0"
"@material/theme" "12.0.0-canary.9f68a932e.0"
"@material/tokens" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/tab-bar@^12.0.0-canary.22d29cbb4.0":
version "12.0.0-canary.22d29cbb4.0"
resolved "https://registry.nlark.com/@material/tab-bar/download/@material/tab-bar-12.0.0-canary.22d29cbb4.0.tgz#294a67f387e80cccade1700ab15ae79d9faee394"
@ -1090,6 +1284,29 @@
"@material/feature-targeting" "12.0.0-canary.22d29cbb4.0"
tslib "^2.1.0"
"@material/theme@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/theme/download/@material/theme-12.0.0-canary.9f68a932e.0.tgz#58cc520689b3c3c3a2852c42117376b0c363cf75"
integrity sha1-WMxSBomzw8OihSxCEXN2sMNjz3U=
dependencies:
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/theme@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/theme/download/@material/theme-12.0.0-canary.e1703bed9.0.tgz#f898b0ba756c183219750776b3e13b91e5ed1de8"
integrity sha1-+JiwunVsGDIZdQd2s+E7keXtHeg=
dependencies:
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/tokens@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/tokens/download/@material/tokens-12.0.0-canary.9f68a932e.0.tgz#3e3b906ad0145c80fdca5b8e362939f177866b29"
integrity sha1-PjuQatAUXID9yluONik58XeGayk=
dependencies:
"@material/elevation" "12.0.0-canary.9f68a932e.0"
"@material/touch-target@12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/touch-target/download/@material/touch-target-12.0.0-canary.068fd5028.0.tgz#542c3fa5e729fe542c21a8668a5fdd820b05e415"
@ -1099,6 +1316,24 @@
"@material/feature-targeting" "12.0.0-canary.068fd5028.0"
tslib "^2.1.0"
"@material/touch-target@12.0.0-canary.9f68a932e.0":
version "12.0.0-canary.9f68a932e.0"
resolved "https://registry.nlark.com/@material/touch-target/download/@material/touch-target-12.0.0-canary.9f68a932e.0.tgz#c8d4a5c292fbf0d6442fd31e4b6eb77915f94087"
integrity sha1-yNSlwpL78NZEL9MeS263eRX5QIc=
dependencies:
"@material/base" "12.0.0-canary.9f68a932e.0"
"@material/feature-targeting" "12.0.0-canary.9f68a932e.0"
tslib "^2.1.0"
"@material/touch-target@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/touch-target/download/@material/touch-target-12.0.0-canary.e1703bed9.0.tgz#2df8ab02cfe7cd5ddf951338c77ae5a890c78c8b"
integrity sha1-LfirAs/nzV3flRM4x3rlqJDHjIs=
dependencies:
"@material/base" "12.0.0-canary.e1703bed9.0"
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@material/typography@12.0.0-canary.068fd5028.0", "@material/typography@^12.0.0-canary.068fd5028.0":
version "12.0.0-canary.068fd5028.0"
resolved "https://registry.nlark.com/@material/typography/download/@material/typography-12.0.0-canary.068fd5028.0.tgz#3a3e536239cd1e55eec9921229877ef9cde4f288"
@ -1117,6 +1352,15 @@
"@material/theme" "12.0.0-canary.22d29cbb4.0"
tslib "^2.1.0"
"@material/typography@12.0.0-canary.e1703bed9.0":
version "12.0.0-canary.e1703bed9.0"
resolved "https://registry.nlark.com/@material/typography/download/@material/typography-12.0.0-canary.e1703bed9.0.tgz#f82960566f8afc7eafce0c60eda6fc60ef5e4839"
integrity sha1-+ClgVm+K/H6vzgxg7ab8YO9eSDk=
dependencies:
"@material/feature-targeting" "12.0.0-canary.e1703bed9.0"
"@material/theme" "12.0.0-canary.e1703bed9.0"
tslib "^2.1.0"
"@nodelib/fs.scandir@2.1.4":
version "2.1.4"
resolved "https://registry.npm.taobao.org/@nodelib/fs.scandir/download/@nodelib/fs.scandir-2.1.4.tgz?cache=0&sync_timestamp=1609074618762&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40nodelib%2Ffs.scandir%2Fdownload%2F%40nodelib%2Ffs.scandir-2.1.4.tgz"
@ -6699,7 +6943,7 @@ typescript-vscode-sh-plugin@^0.6.14:
resolved "https://registry.nlark.com/typescript-vscode-sh-plugin/download/typescript-vscode-sh-plugin-0.6.14.tgz#a81031b502f6346a26ea49ce082438c3e353bb38"
integrity sha1-qBAxtQL2NGom6knOCCQ4w+NTuzg=
typescript@^4.0, typescript@^4.3.5:
typescript@>=3, typescript@^4.0, typescript@^4.3.5:
version "4.3.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
@ -7392,6 +7636,13 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.npm.taobao.org/word-wrap/download/word-wrap-1.2.3.tgz?cache=0&sync_timestamp=1589683603678&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fword-wrap%2Fdownload%2Fword-wrap-1.2.3.tgz"
integrity sha1-YQY29rH3A4kb00dxzLF/uTtHB5w=
wp-types@^2.10.0:
version "2.10.0"
resolved "https://registry.nlark.com/wp-types/download/wp-types-2.10.0.tgz#f816346d37026563fe97bf502d4a5da3397c60f9"
integrity sha1-+BY0bTcCZWP+l79QLUpdozl8YPk=
dependencies:
typescript ">=3"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"