Index: src/UploadTest.vue
===================================================================
--- src/UploadTest.vue	(revision 4b96189c351c92e8383b4411f7a4a73de521da76)
+++ src/UploadTest.vue	(revision 64249d67d46154f425d75b148c215f53466b90e0)
@@ -12,4 +12,9 @@
 const max_jobs = ref(3)
 
+const can_reset = ref(false)
+const can_start = ref(false)
+const can_pick = ref(false)
+const can_abort = ref(false)
+
 const reset = (): void => {
   (inst as any).refs.uploader.reset()
@@ -20,103 +25,130 @@
 }
 
+const pick = (): void => {
+  (inst as any).refs.uploader.pick()
+}
+
+const abort = (): void => {
+  (inst as any).refs.uploader.abort()
+}
+
 </script>
 
 <template>
-  <component class='uploader' :is='UploadZone' target='https://vite.js29a.usermd.net/upload.php'
-             :max-jobs='max_jobs' :auto-start='auto_start' :auto-reset='auto_reset'
-             :keep-going='keep_going' ref='uploader'>
-    <template #idle='{ pick }'>
-      <div class='idle'>
-        idle
-        <br />
-        <button @click='pick()'>pick</button>
-      </div>
-    </template>
-
-    <template #hover>
-      <div class='hover'>
-        hover
-      </div>
-    </template>
-
-    <template #wait='{ start, queue }'>
-      <div class='wait'>
-        wait
-        <br />
-        <button @click='pick()'>pick</button>
-        <br />
-        <button @click='start()'>start</button>
-        <br/>
-        <div v-for='file in queue'>{{ file.name }}</div>
-      </div>
-    </template>
-
-    <template #uploading='{ progress, current, queue, abort, errors }'>
-      <div class='uploading'>
-        uploading
-        <br />
-        <button @click='abort()'>abort</button>
-        <br />
-        {{ progress.length }} / {{ current.length }} / {{ queue.length }}
-        <br />
-        <button @click='abort()'>abort</button>
-        <b>errors:</b> <span v-for='error in errors'>{{ error.file.name }}</span>
-        <b>progress:</b> <div v-for='item in progress'>{{ item }}</div>
-        <b>current:</b>
-        <div v-for='item in current'>
-          {{ item.file.name }}: {{ item.current }} of {{ item.total }}
-        </div>
-        <b>remaining:</b>
-        <div v-for='file in queue'>{{ file.name }}</div>
-      </div>
-    </template>
-
-    <template #done='{ result, reset }'>
-      <div class='done'>
-        done
-        <br />
-        <button @click='reset()'>reset</button>
-        <br />
-        <div v-for='item in result'>{{ item }}</div>
-      </div>
-    </template>
-
-    <template #error='{ error, reset }'>
-      <div class='error'>
-        error
-        <br />
-        <button @click='reset()'>reset</button>
-        <div>{{ error }}</div>
-      </div>
-    </template>
-
-    <template #aborted='{ result, reset }'>
-      <div class='aborted'>
-        aborted
-        <br />
-        <button @click='reset()'>reset</button>
-        <div v-for='item in result'>{{ item }}</div>
-      </div>
-    </template>
-
-    <template #with-errors='{ result, errors }'>
-      <div class='with-errors'>
-        with errors
-        <br />
-        <b>errors(s):</b>
-        <div v-for='item in errors'>{{ item.file.name }} / {{ item.error }}</div>
-        <b>ok:</b>
-        <div v-for='item in result'>{{ item }}</div>
-      </div>
-    </template>
-  </component>
+  <div class='over'>
+    <button @click='pick' :disabled='!can_pick'>pick</button>
+    <span>&nbsp;</span>
+    <button @click='start' :disabled='!can_start'>start</button>
+    <span>&nbsp;</span>
+    <button @click='abort' :disabled='!can_abort'>abort</button>
+    <span>&nbsp;</span>
+    <button @click='reset' :disabled='!can_reset'>reset</button>
+    <p />
+    <input type='checkbox' v-model='auto_start' id='auto-start' />
+    <label for='auto-start'>&nbsp;auto-start</label><br />
+    <input type='checkbox' v-model='auto_reset' id='auto-reset' />
+    <label for='auto-reset'>&nbsp;auto-reset</label><br />
+    <input type='checkbox' v-model='keep_going' id='keep-going' />
+    <label for='keep-going'>&nbsp;keep-going</label><br />
+    <p />
+
+    <component class='uploader' :is='UploadZone' target='https://vite.js29a.usermd.net/upload.php'
+               :max-jobs='max_jobs' :auto-start='auto_start' :auto-reset='auto_reset'
+               :keep-going='keep_going' ref='uploader'
+               @update:canPick='(flag: boolean) => can_pick = flag'
+               @update:canStart='(flag: boolean) => can_start = flag'
+               @update:canAbort='(flag: boolean) => can_abort = flag'
+               @update:canReset='(flag: boolean) => can_reset = flag'>
+      <template #idle='{ pick }'>
+        <div class='idle'>
+          idle
+          <br />
+          <button @click='pick()'>pick</button>
+        </div>
+      </template>
+
+      <template #hover>
+        <div class='hover'>
+          hover
+        </div>
+      </template>
+
+      <template #wait='{ start, queue }'>
+        <div class='wait'>
+          wait
+          <br />
+          <button @click='pick()'>pick</button>
+          <br />
+          <button @click='start()'>start</button>
+          <br/>
+          <div v-for='file in queue'>{{ file.name }}</div>
+        </div>
+      </template>
+
+      <template #uploading='{ progress, current, queue, abort, errors }'>
+        <div class='uploading'>
+          uploading
+          <br />
+          <button @click='abort()'>abort</button>
+          <br />
+          {{ progress.length }} / {{ current.length }} / {{ queue.length }}
+          <br />
+          <button @click='abort()'>abort</button>
+          <b>errors:</b> <span v-for='error in errors'>{{ error.file.name }}</span>
+          <b>progress:</b> <div v-for='item in progress'>{{ item }}</div>
+          <b>current:</b>
+          <div v-for='item in current'>
+            {{ item.file.name }}: {{ item.current }} of {{ item.total }}
+          </div>
+          <b>remaining:</b>
+          <div v-for='file in queue'>{{ file.name }}</div>
+        </div>
+      </template>
+
+      <template #done='{ result, reset }'>
+        <div class='done'>
+          done
+          <br />
+          <button @click='reset()'>reset</button>
+          <br />
+          <div v-for='item in result'>{{ item }}</div>
+        </div>
+      </template>
+
+      <template #error='{ error, reset }'>
+        <div class='error'>
+          error
+          <br />
+          <button @click='reset()'>reset</button>
+          <div>{{ error }}</div>
+        </div>
+      </template>
+
+      <template #aborted='{ result, reset }'>
+        <div class='aborted'>
+          aborted
+          <br />
+          <button @click='reset()'>reset</button>
+          <div v-for='item in result'>{{ item }}</div>
+        </div>
+      </template>
+
+      <template #with-errors='{ result, errors }'>
+        <div class='with-errors'>
+          with errors
+          <br />
+          <b>errors(s):</b>
+          <div v-for='item in errors'>{{ item.file.name }} / {{ item.error }}</div>
+          <b>ok:</b>
+          <div v-for='item in result'>{{ item }}</div>
+        </div>
+      </template>
+    </component>
+  </div>
 </template>
 
 <style scoped>
 
-.uploader {
-  border: 3px dashed #fff;
-  width: 512px;
-  height: 384px;
+.over {
   position: absolute;
   border-radius: 20px;
@@ -124,4 +156,11 @@
   left: 50%;
   transform: translate(-50%, -50%);
+}
+
+.uploader {
+  border: 3px dashed #fff;
+  width: 512px;
+  height: 384px;
+  display: inline-block;
 }
 
Index: src/UploadZone.vue
===================================================================
--- src/UploadZone.vue	(revision 4b96189c351c92e8383b4411f7a4a73de521da76)
+++ src/UploadZone.vue	(revision 64249d67d46154f425d75b148c215f53466b90e0)
@@ -1,5 +1,5 @@
 <script setup lang="ts">
 
-import { ref, defineProps, withDefaults, defineEmits, getCurrentInstance, defineExpose } from 'vue'
+import { ref, computed, defineProps, withDefaults, defineEmits, getCurrentInstance, defineExpose, watch, nextTick } from 'vue'
 
 import $ from 'jQuery'
@@ -28,4 +28,8 @@
   (e: 'upload:progress', vec: any[]): void
   (e: 'upload:done', vec: any[]): void
+  (e: 'update:canReset', flag: boolean): void
+  (e: 'update:canPick', flag: boolean): void
+  (e: 'update:canStart', flag: boolean): void
+  (e: 'update:canAbort', flag: boolean): void
 }>()
 
@@ -131,4 +135,7 @@
 
 const reset_cb = (): void => {
+  if(mode.value == Mode.m_idle)
+    return
+
   mode.value = Mode.m_idle
   files = []
@@ -136,5 +143,5 @@
 
 const pick_cb = (): void => {
-  if(mode.value != Mode.m_idle && Mode.value != Mode.m_wait)
+  if(mode.value != Mode.m_idle && mode.value != Mode.m_wait)
     return
 
@@ -168,5 +175,5 @@
   })
 
-  queue.value = queue.value.filter(queue.value, (file) => {
+  queue.value = queue.value.filter((file) => {
     return file !== cur
   })
@@ -184,5 +191,5 @@
 
       xhr.upload.addEventListener('progress', (evt): void => {
-        queue.value.forEach(current.value, (item) => {
+        current.value.forEach((item) => {
           if(item.file === cur) {
             item.current = evt.loaded
@@ -196,5 +203,5 @@
   })
     .then((res: any) => {
-      current.value = queue.value.filter(current.value, (item) => {
+      current.value = current.value.filter((item) => {
         return item.file !== cur
       })
@@ -223,10 +230,9 @@
         files = [] // XXX can drop w/o reset
 
-        if(errors.value.length) {
+        if(errors.value.length)
           if(props.keepGoing || props.keepGoing === '')
             mode.value = Mode.m_with_errors
           else
             mode.value = Mode.m_error
-        }
         else
           if(props.autoReset || props.autoReset === '')
@@ -243,5 +249,5 @@
     .catch((err: any) => {
       if(props.keepGoing || props.keepGoing === '') {
-        current.value = current.value.filter(current.value, (item) => {
+        current.value = current.value.filter((item) => {
           return item.file !== cur
         })
@@ -269,9 +275,59 @@
 }
 
+const can_pick = computed({
+  get: () => {
+    return mode.value == Mode.m_idle || mode.value == Mode.m_wait
+  },
+  set: () => {
+  }
+})
+
+const can_start = computed({
+  get: () => {
+    return mode.value == Mode.m_wait
+  },
+  set: () => {
+  }
+})
+
+const can_abort = computed({
+  get: () => {
+    return mode.value == Mode.m_uploading
+  },
+  set: () => {
+  }
+})
+
+const can_reset = computed({
+  get: () => {
+    return mode.value != Mode.m_idle
+  },
+  set: () => {
+  }
+})
+
+const update_can = (state: Mode): void => {
+  emit('update:canPick', can_pick.value)
+  emit('update:canStart', can_start.value)
+  emit('update:canAbort', can_abort.value)
+  emit('update:canReset', can_reset.value)
+}
+
+watch(() => { return mode.value }, (t: Mode, f: Mode) => { update_can(t) } )
+
+nextTick(() => {
+  update_can(mode.value)
+})
+
 defineExpose({
   reset: reset_cb,
   start: start_cb,
   pick: pick_cb,
-  abort: abort_cb
+  abort: abort_cb,
+
+  can_pick,
+  can_start,
+  can_abort,
+  can_reset
 })
 
