+
+
+ Web Locks election
-
-
-
- Leader status:
- {{ isLeader ? 'Leader' : 'Follower' }}
+ {{ isSupported ? 'Supported' : 'Unsupported' }}
-
- Open this page in multiple tabs — only one will be the leader.
- Close the leader tab and another will take over automatically.
-
+
+
+
+
+ {{ isLeader ? 'Leader tab' : 'Follower tab' }}
+
+
+ {{ isLeader
+ ? 'This tab holds the lock and would run exclusive work.'
+ : 'Another tab is the leader, or leadership was released.' }}
+
+
-
+
Acquire
Release
+
+
+ The Web Locks API is not available in this browser.
+
+
+ Open this page in a second tab — only one tab is the leader at a time. Release
+ here and watch another tab take over.
+
diff --git a/vue/toolkit/src/composables/browser/useTextareaAutosize/demo.vue b/vue/toolkit/src/composables/browser/useTextareaAutosize/demo.vue
new file mode 100644
index 0000000..66b2940
--- /dev/null
+++ b/vue/toolkit/src/composables/browser/useTextareaAutosize/demo.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+ Auto-growing textarea
+
+
+
+
+
+
+
+ Max height
+
+ {{ maxHeight }}px
+
+
+
+ {{ input.length }} chars
+
+ {{ resizes }} resizes
+
+
+
+
+
+
+ Load sample
+
+
+ Trigger resize
+
+
+ Clear
+
+
+
+
diff --git a/vue/toolkit/src/composables/browser/useTitle/demo.vue b/vue/toolkit/src/composables/browser/useTitle/demo.vue
new file mode 100644
index 0000000..793fd98
--- /dev/null
+++ b/vue/toolkit/src/composables/browser/useTitle/demo.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+ Live document title
+
+
+
+
+
+
+
+ {{ title || 'Untitled' }} · {{ appName }}
+
+
+
+
+
+
+ Page title
+
+
+
+
+
+
+ App name (template suffix)
+
+
+
+
+
+
+ {{ preset }}
+
+
+
+
+ Check your browser tab — it updates in real time.
+
+
+
diff --git a/vue/toolkit/src/composables/browser/useUrlSearchParams/demo.vue b/vue/toolkit/src/composables/browser/useUrlSearchParams/demo.vue
new file mode 100644
index 0000000..e0dfca7
--- /dev/null
+++ b/vue/toolkit/src/composables/browser/useUrlSearchParams/demo.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+ Search query
+
+
+
+
+
+
Sort by
+
+
+ {{ s }}
+
+
+
+
+
+
+ Tags (repeated keys → array)
+
+
+
+ #{{ tag }}
+
+
+
+
+
+
+ Live URL query
+
+
+ {{ queryString }}
+
+
+ The browser address bar updates as you edit. Falsy values are dropped.
+
+
+
+
diff --git a/vue/toolkit/src/composables/browser/useVibrate/demo.vue b/vue/toolkit/src/composables/browser/useVibrate/demo.vue
new file mode 100644
index 0000000..bbea540
--- /dev/null
+++ b/vue/toolkit/src/composables/browser/useVibrate/demo.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+ The Vibration API is not supported in this browser. Try a mobile device.
+
+
+
+
+
+ Current pattern (ms)
+
+
+ {{ isSupported ? 'supported' : 'unsupported' }}
+
+
+
+ [{{ Array.isArray(pattern) ? pattern.join(', ') : pattern }}]
+
+
+
+
+
Presets
+
+
+ {{ name }}
+
+
+
+
+
+
+ Vibrate now
+
+
+ {{ looping ? 'Stop loop' : 'Loop every 1.5s' }}
+
+
+ Stop
+
+
+
+
diff --git a/vue/toolkit/src/composables/browser/useWakeLock/demo.vue b/vue/toolkit/src/composables/browser/useWakeLock/demo.vue
new file mode 100644
index 0000000..26f9c05
--- /dev/null
+++ b/vue/toolkit/src/composables/browser/useWakeLock/demo.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+ The Screen Wake Lock API is not supported in this browser.
+
+
+
+
+
+
+ {{ isActive ? 'AWAKE' : 'IDLE' }}
+
+
+ {{ isActive ? 'Screen will stay on' : 'Screen may sleep normally' }}
+
+
+
+
+
+ Lock held
+
+ {{ sentinel ? 'yes' : 'none' }}
+
+
+
+
+ {{ sentinel ? 'Release wake lock' : 'Request wake lock' }}
+
+
+
+ {{ error }}
+
+
+ The lock auto-releases when the tab is hidden and re-acquires when visible again.
+
+
+
diff --git a/vue/toolkit/src/composables/browser/useWebNotification/demo.vue b/vue/toolkit/src/composables/browser/useWebNotification/demo.vue
new file mode 100644
index 0000000..16103ad
--- /dev/null
+++ b/vue/toolkit/src/composables/browser/useWebNotification/demo.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+ Notifications are not supported in this browser.
+
+
+
+
+
+
+ Permission
+
+
+
+ {{ permissionGranted ? 'Granted' : 'Not granted' }}
+
+
+
+ {{ permissionGranted ? 'Allowed' : 'Request access' }}
+
+
+
+
+
+ Title
+
+
+
+ Body
+
+
+
+
+
+
+ Show notification
+
+
+ Close
+
+
+
+
+ Last event
+ {{ lastEvent || '—' }}
+
+
+
+ Grant access first, then trigger a notification. Switch back to this tab and it auto-closes.
+
+
+
+
diff --git a/vue/toolkit/src/composables/component/createReusableTemplate/demo.vue b/vue/toolkit/src/composables/component/createReusableTemplate/demo.vue
new file mode 100644
index 0000000..bdd3c6c
--- /dev/null
+++ b/vue/toolkit/src/composables/component/createReusableTemplate/demo.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+ {{ label }}
+
+
+ {{ value }}
+
+
+
+
+
+
+
+
+
{{ name }}
+
{{ role }}
+
+
+ {{ online ? 'Online' : 'Away' }}
+
+
+
+
+
+
+
+
+
+
+
+ Team — click a row to toggle status
+
+
+
+
+
+
+
+ Both card and row markup are declared once via DefineTemplate and
+ rendered from multiple ReuseTemplate call sites.
+
+
+
diff --git a/vue/toolkit/src/composables/component/unrefElement/demo.vue b/vue/toolkit/src/composables/component/unrefElement/demo.vue
new file mode 100644
index 0000000..5a75c8e
--- /dev/null
+++ b/vue/toolkit/src/composables/component/unrefElement/demo.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+ Target element
+
+
+
+
+ Width
+ {{ width }}px
+
+
+
+
+
+ Read element via unrefElement
+
+
+
+
+ tagName
+ {{ tag }}
+
+
+ boundingRect
+ {{ rect ? `${rect.w} × ${rect.h}` : '—' }}
+
+
+
+
+ Resize the box, then measure. unrefElement unwraps the template
+ ref to the real DOM node — it also resolves a component ref to its $el.
+
+
+
diff --git a/vue/toolkit/src/composables/component/useCurrentElement/demo.vue b/vue/toolkit/src/composables/component/useCurrentElement/demo.vue
new file mode 100644
index 0000000..1c59986
--- /dev/null
+++ b/vue/toolkit/src/composables/component/useCurrentElement/demo.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+ Live measurement of this component's root
+
+
+
+
+ {{ chip }}
+
+
+
+
+
+ Root padding
+ {{ padding }}px
+
+
+
+
+
+
+ Remove chip
+
+
+ Add chip
+
+
+
+
+
+ el.value
+ {{ info ? `<${info.tag}>` : 'undefined' }}
+
+
+ chips in DOM
+ {{ info?.children ?? '—' }}
+
+
+ root height
+ {{ info ? `${info.height}px` : '—' }}
+
+
+
+
+ The computed re-reads $el on every update, so the readout tracks
+ padding and chip changes automatically.
+
+
+
diff --git a/vue/toolkit/src/composables/component/useForwardExpose/demo.vue b/vue/toolkit/src/composables/component/useForwardExpose/demo.vue
new file mode 100644
index 0000000..0e84b4c
--- /dev/null
+++ b/vue/toolkit/src/composables/component/useForwardExpose/demo.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+ Wrapper component
+
+
+
+ <FieldWrapper> renders an inner input, but
+ useForwardExpose makes its $el
+ resolve to that input.
+
+
+
+
+
+ Focus forwarded $el
+
+
+ Fill sample
+
+
+
+
+
+ field.$el
+ {{ resolved ? `<${resolved.tag}>` : 'undefined' }}
+
+
+ value
+ {{ resolved?.value || '""' }}
+
+
+
+
+ The parent never touches the input directly — it holds a ref to the wrapper, whose
+ $el is forwarded to the inner element.
+
+
+
diff --git a/vue/toolkit/src/composables/component/useTemplateRefsList/demo.vue b/vue/toolkit/src/composables/component/useTemplateRefsList/demo.vue
new file mode 100644
index 0000000..82bb7db
--- /dev/null
+++ b/vue/toolkit/src/composables/component/useTemplateRefsList/demo.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+ Playlist
+
+ {{ collected }} refs collected
+
+
+
+
+
+ {{ index + 1 }}
+
+ {{ track.title }}
+ {{ track.artist }}
+
+
+ ✕
+
+
+
+ No tracks — add one to collect a ref.
+
+
+
+
+ refs[{{ lastMeasured.index }}].width = {{ lastMeasured.width }}px
+
+
+ Add a track to measure the newest collected element directly from the DOM.
+
+
+
+
+ Add track
+
+
+ Measure last
+
+
+
+
diff --git a/vue/toolkit/src/composables/component/useVirtualList/demo.vue b/vue/toolkit/src/composables/component/useVirtualList/demo.vue
new file mode 100644
index 0000000..efc31cb
--- /dev/null
+++ b/vue/toolkit/src/composables/component/useVirtualList/demo.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+ Virtual list
+
+ {{ total.toLocaleString() }} rows
+
+
+
+
+
+
+
+ {{ data.label }}
+ idx {{ index }}
+
+
+
+
+
+ rendered
+ {{ list.length }} nodes · idx {{ visibleRange }}
+
+
+
+
+ Scroll to index
+
+
+
+ Jump
+
+
+
+
diff --git a/vue/toolkit/src/composables/debug/useRenderCount/demo.vue b/vue/toolkit/src/composables/debug/useRenderCount/demo.vue
new file mode 100644
index 0000000..a34343b
--- /dev/null
+++ b/vue/toolkit/src/composables/debug/useRenderCount/demo.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Render count
+ {{ renderCount }}
+ renders since mount
+
+
+
+ Bound input
+
+
+
+
+ {{ message }}
+
+
+
+ Force re-render (shift color)
+
+
+
diff --git a/vue/toolkit/src/composables/debug/useRenderInfo/demo.vue b/vue/toolkit/src/composables/debug/useRenderInfo/demo.vue
new file mode 100644
index 0000000..ff3c78c
--- /dev/null
+++ b/vue/toolkit/src/composables/debug/useRenderInfo/demo.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Render info
+
+ {{ component ?? 'anonymous' }}
+
+
+
+
+
+
{{ count }}
+
renders
+
+
+
{{ duration.toFixed(2) }}
+
last render ms
+
+
+
+
+ mounted at
+ {{ mountedAt }}
+
+
+
+
+ Render workload
+ {{ rows }} cells
+
+
+
Drag to re-render a larger DOM subtree and watch the render duration climb.
+
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/onElementRemoval/demo.vue b/vue/toolkit/src/composables/elements/onElementRemoval/demo.vue
new file mode 100644
index 0000000..eaa0a87
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/onElementRemoval/demo.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+ DOM removal watcher
+
+
+ {{ mounted ? 'In DOM' : 'Removed' }}
+
+
+
+
+
+ Watched element
+ Remove me and the callback fires
+
+
Element detached from the document
+
+
+
+
+
{{ removals }}
+
removals fired
+
+
+
{{ lastEvent ?? '—' }}
+
last fired
+
+
+
+
+
+ {{ mounted ? 'Remove element' : 'Mount element' }}
+
+
+ Reset
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useActiveElement/demo.vue b/vue/toolkit/src/composables/elements/useActiveElement/demo.vue
new file mode 100644
index 0000000..8f136c2
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useActiveElement/demo.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ Focus a field
+
+
+ {{ field.label }}
+
+
+
+ A focusable button
+
+
+
+
+
+ activeElement
+
+ <{{ activeTag }}>
+
+ none
+
+
+ id
+ {{ activeId ?? '—' }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useDocumentReadyState/demo.vue b/vue/toolkit/src/composables/elements/useDocumentReadyState/demo.vue
new file mode 100644
index 0000000..ebd88f7
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useDocumentReadyState/demo.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ document.readyState
+
+
+
+ {{ readyState }}
+
+
+
+
+
+
+ {{ i + 1 }}
+
+
+
+ {{ stage.label }}
+
+
+ {{ stage.hint }}
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useDocumentVisibility/demo.vue b/vue/toolkit/src/composables/elements/useDocumentVisibility/demo.vue
new file mode 100644
index 0000000..79fda82
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useDocumentVisibility/demo.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ {{ isVisible ? '👁️' : '💤' }}
+
+
+
+ {{ visibility }}
+
+
+ {{ isVisible ? 'This tab is in the foreground' : 'This tab is hidden' }}
+
+
+
+
+
+ Switch to another tab or minimize the window to watch this update.
+
+
+
+
+
+ {{ switches }}
+
+
+ Times hidden
+
+
+
+
+ {{ lastHidden ?? '—' }}
+
+
+ Last hidden at
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useDraggable/demo.vue b/vue/toolkit/src/composables/elements/useDraggable/demo.vue
new file mode 100644
index 0000000..96c35fb
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useDraggable/demo.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+ Draggable, clamped to container
+
+
+
+ {{ isDragging ? 'dragging' : 'idle' }}
+
+
+
+
+
+
+ ⠿
+ Drag me
+
+
+
x: {{ Math.round(x) }}
+
y: {{ Math.round(y) }}
+
+
+
+
+
+ Drag from the header. Movement is clamped to the container bounds.
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useDropZone/demo.vue b/vue/toolkit/src/composables/elements/useDropZone/demo.vue
new file mode 100644
index 0000000..7961c1a
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useDropZone/demo.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+ Drag and Drop is not supported in this browser.
+
+
+
+
+
+ {{ isOverDropZone ? '📥' : '🖼️' }}
+
+
+ {{ isOverDropZone ? 'Release to drop' : 'Drop image files here' }}
+
+
+ Only image/* files are accepted
+
+
+
+
+
+
+ Dropped files
+
+
+ {{ fileList.length }}
+
+
+
+
+ Nothing dropped yet.
+
+
+
+
+ {{ file.name }}
+
+ {{ formatSize(file.size) }}
+
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useElementBounding/demo.vue b/vue/toolkit/src/composables/elements/useElementBounding/demo.vue
new file mode 100644
index 0000000..59b4110
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useElementBounding/demo.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+ getBoundingClientRect
+
+ {{ timing }}
+
+
+
+
+
+
+ {{ fmt(bounds.width.value) }}×{{ fmt(bounds.height.value) }}
+
+
+
+
+
+
{{ fmt(m.value) }}
+
{{ m.label }}
+
+
+
+
+
+
+ Size
+ {{ size }}px
+
+
+
+
+
+
+ {{ opt }}
+
+
+ Update
+
+
+
+
+ next-frame batches rapid scroll/resize reads into one measurement per animation frame.
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useElementSize/demo.vue b/vue/toolkit/src/composables/elements/useElementSize/demo.vue
new file mode 100644
index 0000000..474cb27
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useElementSize/demo.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+ ResizeObserver
+
+
+ {{ observing ? 'Observing' : 'Stopped' }}
+
+
+
+
+
+
+
+
+
{{ fmt(width) }}
+
width px
+
+
+
{{ fmt(height) }}
+
height px
+
+
+
+
+
+
+ Padding (border-box)
+ {{ padding }}px
+
+
+
+
+
+ {{ observing ? 'Stop observing' : 'Observer stopped' }}
+
+
+
+ With box: 'border-box' the reported size includes padding, so the slider changes the numbers without resizing the element.
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useElementVisibility/demo.vue b/vue/toolkit/src/composables/elements/useElementVisibility/demo.vue
new file mode 100644
index 0000000..f13ae2e
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useElementVisibility/demo.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+ IntersectionObserver
+
+
+ {{ isVisible ? 'In view' : 'Hidden' }}
+
+
+
+
+
+
Scroll down inside this box…
+
+
+
Target element
+
at least 50% visible to count
+
+
+
…and back up to hide it again.
+
+
+
+
+
{{ seenCount }}
+
times seen
+
+
+
+ {{ isActive ? 'Active' : 'Paused' }}
+
+
observer
+
+
+
+
+ {{ isActive ? 'Pause observer' : 'Resume observer' }}
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useFocusGuard/demo.vue b/vue/toolkit/src/composables/elements/useFocusGuard/demo.vue
new file mode 100644
index 0000000..c27546e
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useFocusGuard/demo.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+ Focus guards
+
+
+ {{ enabled ? 'Mounted' : 'Off' }}
+
+
+
+
+
+
+ {{ f.label }}
+
+
+
+
+ Submit
+
+
+
+
+ focused: {{ focused ?? 'nothing' }}
+
+
+
+ {{ enabled ? 'Remove focus guards' : 'Mount focus guards' }}
+
+
+
+ Guards are invisible tabindex="0" sentinels inserted at the page boundaries. Tab past the last field with them on to feel focus wrap around for overlays and modals.
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useIntersectionObserver/demo.vue b/vue/toolkit/src/composables/elements/useIntersectionObserver/demo.vue
new file mode 100644
index 0000000..87925ca
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useIntersectionObserver/demo.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+ Scroll spy
+
+ active: {{ activeId ?? '—' }}
+
+
+
+
+
+
+
+
+ {{ s.label }}
+
+
+
+
+
+
+
+
+
+ Active threshold
+ {{ Math.round(threshold * 100) }}%
+
+
+
A section becomes active once at least this much of it is visible inside the scroll root.
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useMutationObserver/demo.vue b/vue/toolkit/src/composables/elements/useMutationObserver/demo.vue
new file mode 100644
index 0000000..460d995
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useMutationObserver/demo.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+ MutationObserver
+
+
+ {{ isActive ? 'Observing' : 'Paused' }}
+
+
+
+
+ MutationObserver is not supported in this browser.
+
+
+
+
+
+
+ Observed element
+
+
+
+ {{ label }}
+
+ No labels
+
+
+
+
+
+
{{ mutationCount }}
+
mutations
+
+
+
{{ lastType }}
+
last record type
+
+
+
+
+
+ Add child
+
+
+ Remove child
+
+
+ Toggle class
+
+
+ Toggle style
+
+
+
+
+
Recent records
+
+
Mutate the element above to record changes.
+
+
+
+ {{ isActive ? 'Pause observer' : 'Resume observer' }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useParentElement/demo.vue b/vue/toolkit/src/composables/elements/useParentElement/demo.vue
new file mode 100644
index 0000000..05ca7e6
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useParentElement/demo.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
parentElement
+
+
+
+
+
+ #wrapper-a
+
+
+ Tracked child
+
+
empty
+
+
+
+
+ #wrapper-b
+
+
+ Tracked child
+
+
empty
+
+
+
+
+ Move child to #wrapper-{{ inSecond ? 'a' : 'b' }}
+
+
+
+
Resolved parent
+
+
+ tag
+ <{{ parentInfo.tag }}>
+
+
+ id
+ {{ parentInfo.id }}
+
+
+ size
+ {{ parentInfo.width }} × {{ parentInfo.height }}
+
+
+
Resolving parent on the client…
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useResizeObserver/demo.vue b/vue/toolkit/src/composables/elements/useResizeObserver/demo.vue
new file mode 100644
index 0000000..025dabd
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useResizeObserver/demo.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+ ResizeObserver
+
+
+ {{ isActive ? 'Observing' : 'Paused' }}
+
+
+
+
+ ResizeObserver is not supported in this browser.
+
+
+
+
+
+
+
+ {{ size.width }} × {{ size.height }}
+
+
drag the bottom-right corner
+
+
+
+
+
+
{{ size.width }}
+
width px
+
+
+
{{ size.height }}
+
height px
+
+
+
{{ callbacks }}
+
callbacks
+
+
+
+
+ {{ isActive ? 'Pause observer' : 'Resume observer' }}
+
+
+ While paused, resizing won't update the readout until you resume.
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useWindowFocus/demo.vue b/vue/toolkit/src/composables/elements/useWindowFocus/demo.vue
new file mode 100644
index 0000000..8424b7f
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useWindowFocus/demo.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
Window focus
+
+
+
+
+
+
+ {{ focused ? 'Window focused' : 'Window blurred' }}
+
+
+ {{ focused ? 'Click outside or switch apps to blur.' : 'Click back into this window to refocus.' }}
+
+
+
+
+
+
{{ focused ? 'on' : 'off' }}
+
focused
+
+
+
{{ blurCount }}
+
times blurred
+
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useWindowScroll/demo.vue b/vue/toolkit/src/composables/elements/useWindowScroll/demo.vue
new file mode 100644
index 0000000..7f022dd
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useWindowScroll/demo.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+ Window scroll
+
+
+ {{ isScrolling ? 'Scrolling' : 'Idle' }}
+
+
+
+
+
+
{{ Math.round(x) }}
+
scroll x
+
+
+
{{ Math.round(y) }}
+
scroll y
+
+
+
+
+
+ Direction
+
+ {{ verticalDirection }}
+
+
+
+
+
+ Arrived at top
+
+ {{ arrivedState.top ? 'yes' : 'no' }}
+
+
+
+ Arrived at bottom
+
+ {{ arrivedState.bottom ? 'yes' : 'no' }}
+
+
+
+
+
+
+
+ Scroll to top
+
+
+ Scroll to bottom
+
+
+
+
+ Re-measure
+
+
+ Scroll the documentation page to watch the position and arrived edges update live.
+
+
+
diff --git a/vue/toolkit/src/composables/elements/useWindowSize/demo.vue b/vue/toolkit/src/composables/elements/useWindowSize/demo.vue
new file mode 100644
index 0000000..47574ec
--- /dev/null
+++ b/vue/toolkit/src/composables/elements/useWindowSize/demo.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ Width
+
+
+ {{ width }}
+
+
+ px
+
+
+
+
+ Height
+
+
+ {{ height }}
+
+
+ px
+
+
+
+
+
+
+
+
+ {{ width }} × {{ height }}
+
+
+
+
+
+
+ {{ orientation }}
+
+
+ ratio {{ aspect }}
+
+
+
+
+ Resize your browser window to watch the values update live.
+
+
+
diff --git a/vue/toolkit/src/composables/forms/useField/demo.vue b/vue/toolkit/src/composables/forms/useField/demo.vue
new file mode 100644
index 0000000..7b8c44c
--- /dev/null
+++ b/vue/toolkit/src/composables/forms/useField/demo.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+ Email address
+
+
+
+ {{ errorMessage }}
+
+
+ Validates on every keystroke.
+
+
+
+
+
+ {{ meta.valid.value ? 'valid' : 'invalid' }}
+
+
+ dirty: {{ meta.dirty.value }}
+
+
+ touched: {{ meta.touched.value }}
+
+
+
+
+
+ Validate
+
+
+ Reset
+
+
+
+
+ value: "{{ value }}"
+
+
+
diff --git a/vue/toolkit/src/composables/forms/useFieldArray/demo.vue b/vue/toolkit/src/composables/forms/useFieldArray/demo.vue
new file mode 100644
index 0000000..2b84ecf
--- /dev/null
+++ b/vue/toolkit/src/composables/forms/useFieldArray/demo.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+ Tasks ({{ fields.length }})
+
+
+ + Add task
+
+
+
+
+
+
+
+
+
+ ↑
+
+
+ ↓
+
+
+ ×
+
+
+
+
+
+
+ No tasks yet. Add one to get started.
+
+
+
+
+ Stable keys survive reorders
+
+
+ Swap first ↔ last
+
+
+
+
diff --git a/vue/toolkit/src/composables/forms/useForm/demo.vue b/vue/toolkit/src/composables/forms/useForm/demo.vue
new file mode 100644
index 0000000..2628ae1
--- /dev/null
+++ b/vue/toolkit/src/composables/forms/useForm/demo.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/forms/useFormContext/demo.vue b/vue/toolkit/src/composables/forms/useFormContext/demo.vue
new file mode 100644
index 0000000..578a743
--- /dev/null
+++ b/vue/toolkit/src/composables/forms/useFormContext/demo.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+ Profile (fields read context, not props)
+
+
+
+
+
+
+
+ dirty: {{ isDirty }}
+
+
+ touched: {{ meta.touched }}
+
+
+
+
+
+ Shared form values:
+
+
{{ JSON.stringify(values, null, 2) }}
+
+
+
+ Both inputs are nested children that locate the form via useFormContext().
+
+
+
diff --git a/vue/toolkit/src/composables/lifecycle/tryOnBeforeMount/demo.vue b/vue/toolkit/src/composables/lifecycle/tryOnBeforeMount/demo.vue
new file mode 100644
index 0000000..a82fca7
--- /dev/null
+++ b/vue/toolkit/src/composables/lifecycle/tryOnBeforeMount/demo.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
tryOnBeforeMount
+
+ Registers a callback on onBeforeMount when inside a component,
+ otherwise calls it directly. Watch the execution order below.
+
+
+
+
+
Execution timeline
+
+
+
+
+ {{ index + 1 }}
+
+ {{ entry.label }}
+
+ {{ entry.phase }}
+
+
+
+
+
+
+ order: [{{ order.join(' → ') }}]
+
+
+
+ The sync callback fires during the component's before-mount phase; the async one is queued to the next tick,
+ so it always lands last.
+
+
+
diff --git a/vue/toolkit/src/composables/lifecycle/tryOnMounted/demo.vue b/vue/toolkit/src/composables/lifecycle/tryOnMounted/demo.vue
new file mode 100644
index 0000000..ba787d6
--- /dev/null
+++ b/vue/toolkit/src/composables/lifecycle/tryOnMounted/demo.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+ tryOnMounted
+
+
+ {{ mounted ? 'mounted' : 'pending' }}
+
+
+
+
+
Mount timeline
+
+
+
+
+ {{ index + 1 }}
+
+ {{ entry.label }}
+ +{{ entry.at }}ms
+
+
+
+
+
+ Both callbacks are safely deferred until the component is mounted. The async variant is queued one extra tick,
+ so it consistently runs after the synchronous one.
+
+
+
diff --git a/vue/toolkit/src/composables/lifecycle/tryOnScopeDispose/demo.vue b/vue/toolkit/src/composables/lifecycle/tryOnScopeDispose/demo.vue
new file mode 100644
index 0000000..8ca903c
--- /dev/null
+++ b/vue/toolkit/src/composables/lifecycle/tryOnScopeDispose/demo.vue
@@ -0,0 +1,137 @@
+
+
+
+
+
+ tryOnScopeDispose
+
+
+ {{ activeScopes > 0 ? 'scope active' : 'no scope' }}
+
+
+
+
+
+ Create scope
+
+
+ Dispose
+
+
+ Outside
+
+
+
+
+
Event log
+
+
+
+
+ {{ entry.label }}
+
+
+
+
+ Create a scope to register a cleanup hook, then dispose it to watch the callback fire.
+
+
+
+
+ Inside an active effect scope the cleanup is registered and runs on disposal. Called with no scope present, it
+ simply returns false instead of throwing.
+
+
+
diff --git a/vue/toolkit/src/composables/lifecycle/useMounted/demo.vue b/vue/toolkit/src/composables/lifecycle/useMounted/demo.vue
new file mode 100644
index 0000000..620f057
--- /dev/null
+++ b/vue/toolkit/src/composables/lifecycle/useMounted/demo.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
useMounted
+
+ Tracks whether the component has finished mounting — handy for SSR-safe rendering and entry animations.
+
+
+
+
+
+ isMounted
+ {{ isMounted }}
+
+
+
+ {{ isMounted ? 'mounted' : 'mounting…' }}
+
+
+
+
+
+
+ This panel fades and slides into view the moment isMounted becomes
+ true.
+
+
+ On the server it renders hidden, avoiding hydration flicker.
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/logicAnd/demo.vue b/vue/toolkit/src/composables/math/logicAnd/demo.vue
new file mode 100644
index 0000000..e446500
--- /dev/null
+++ b/vue/toolkit/src/composables/math/logicAnd/demo.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+
logicAnd
+
+ Reactive logical AND — the result is true only when every input
+ resolves truthy. Toggle the checklist below.
+
+
+
+
+
+
+ {{ cond.label }}
+
+ {{ cond.model.value }}
+
+
+
+
+
+
+ Word count ≥ 150
+
+ {{ meetsLength }}
+
+
+
+
{{ wordCount }} words
+
+
+
+
+
+ canPublish
+
+ {{ canPublish }}
+
+
+
+ Publish
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/logicNot/demo.vue b/vue/toolkit/src/composables/math/logicNot/demo.vue
new file mode 100644
index 0000000..6f8222a
--- /dev/null
+++ b/vue/toolkit/src/composables/math/logicNot/demo.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ Loading
+ source value
+
+
+
+
+
+
+
+
+ isLoading
+ {{ isLoading }}
+
+
+ isReady = !isLoading
+ {{ isReady }}
+
+
+
+
+ Ready to continue
+ Please wait, loading…
+
+
+
diff --git a/vue/toolkit/src/composables/math/logicOr/demo.vue b/vue/toolkit/src/composables/math/logicOr/demo.vue
new file mode 100644
index 0000000..0804144
--- /dev/null
+++ b/vue/toolkit/src/composables/math/logicOr/demo.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
Notification channels
+
+
+
+ {{ ch.label }}
+
+
+
+
+
+
+ logicOr(email, sms, push)
+ {{ hasChannel ? 'At least one channel is on' : 'Pick a channel to get notified' }}
+
+
{{ hasChannel }}
+
+
+
diff --git a/vue/toolkit/src/composables/math/useAbs/demo.vue b/vue/toolkit/src/composables/math/useAbs/demo.vue
new file mode 100644
index 0000000..7697e17
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useAbs/demo.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+ value
+ {{ value }}
+
+
+ |value|
+ {{ abs }}
+
+
+
+
+
+
+
+ -100
+ 0
+ 100
+
+
+
+
+ Math.abs({{ value }}) = {{ abs }}
+
+
+
diff --git a/vue/toolkit/src/composables/math/useAverage/demo.vue b/vue/toolkit/src/composables/math/useAverage/demo.vue
new file mode 100644
index 0000000..55be5af
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useAverage/demo.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+ Average score
+ {{ scores.length }} value{{ scores.length === 1 ? '' : 's' }}
+
+
{{ display }}
+
+
+
+
+ #{{ i + 1 }}
+
+
+ ✕
+
+
+
+
+
+ No values — mean is NaN (0 / 0)
+
+
+
+ + Add random score
+
+
+
diff --git a/vue/toolkit/src/composables/math/useCeil/demo.vue b/vue/toolkit/src/composables/math/useCeil/demo.vue
new file mode 100644
index 0000000..9424a22
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useCeil/demo.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ Math.ceil
+ {{ ceiled }}
+
+
+
+ {{ Math.floor(value) }}
+ {{ ceiled }}
+
+
+
+
+ Math.ceil({{ value }}) = {{ ceiled }}
+
+
+
diff --git a/vue/toolkit/src/composables/math/useClamp/demo.vue b/vue/toolkit/src/composables/math/useClamp/demo.vue
new file mode 100644
index 0000000..91313b3
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useClamp/demo.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+ Clamped
+ {{ clamped }}
+
+
+
+
+ {{ min }}
+ range
+ {{ max }}
+
+
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/useFloor/demo.vue b/vue/toolkit/src/composables/math/useFloor/demo.vue
new file mode 100644
index 0000000..2d4cac1
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useFloor/demo.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
Math.floor
+
+ {{ value.toFixed(2) }}
+ →
+ {{ floored }}
+
+
+
+
+
+ Value
+
+
+
+
+
+ −0.1
+
+
+ +0.1
+
+
+
+
+
+ useFloor({{ value }}) === {{ floored }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/useMath/demo.vue b/vue/toolkit/src/composables/math/useMath/demo.vue
new file mode 100644
index 0000000..927e89c
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useMath/demo.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+ a = {{ a }}
+
+
+
+ b = {{ b }}
+
+
+
+
+
+
+
+ {{ r.label }}
+ {{ r.expr }}
+
+
{{ fmt(r.value) }}
+
+
+
+
+ Every value above is a single useMath('<key>', ...) computed — any callable
+ Math method works, with refs, getters or plain values as arguments.
+
+
+
diff --git a/vue/toolkit/src/composables/math/useMax/demo.vue b/vue/toolkit/src/composables/math/useMax/demo.vue
new file mode 100644
index 0000000..a500cb4
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useMax/demo.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Maximum
+
+ {{ Number.isFinite(max) ? max : '−∞' }}
+
+ empty list → −Infinity
+
+
+
+
+
Values
+
+
+ + add
+
+
+ shuffle
+
+
+
+
+
+
+ No values — add some to compute a maximum.
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/useMin/demo.vue b/vue/toolkit/src/composables/math/useMin/demo.vue
new file mode 100644
index 0000000..a4616c3
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useMin/demo.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Minimum
+
+ {{ Number.isFinite(min) ? min : '∞' }}
+
+ empty list → Infinity
+
+
+
+
+
Values
+
+
+ + add
+
+
+ shuffle
+
+
+
+
+
+
+ No values — add some to compute a minimum.
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/usePrecision/demo.vue b/vue/toolkit/src/composables/math/usePrecision/demo.vue
new file mode 100644
index 0000000..f5b4868
--- /dev/null
+++ b/vue/toolkit/src/composables/math/usePrecision/demo.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+ precision result
+ {{ digits }} digit{{ digits === 1 ? '' : 's' }}
+
+
+ {{ result }}
+
+
+ from {{ value }}
+
+
+
+
+
+ Value
+ {{ value }}
+
+
+
+
+ Digits
+ {{ digits }}
+
+
+
+
+
+
Rounding method
+
+
+ {{ m.label }}
+
+
+
+
+
+
Samples
+
+
+ {{ s }}
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/useProjection/demo.vue b/vue/toolkit/src/composables/math/useProjection/demo.vue
new file mode 100644
index 0000000..d1aa575
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useProjection/demo.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+ source · °C
+ domain [0, 40]
+
+
+ {{ celsius }}°
+
+
+
+
+
+
+
+ → °F [32, 212]
+ {{ fahrenheit.toFixed(1) }}
+
+
+ → fraction [0, 1]
+ {{ fraction.toFixed(3) }}
+
+
+
+ → percent [0, 100]
+ {{ percent.toFixed(0) }}%
+
+
+
+
+
+
+
+ Clamp to domain
+ prevent extrapolation past bounds
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/useRound/demo.vue b/vue/toolkit/src/composables/math/useRound/demo.vue
new file mode 100644
index 0000000..365ffc6
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useRound/demo.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+ rounded
+ {{ hint }}
+
+
+ {{ rounded }}
+
+
+ input {{ value }}
+
+
+
+
+
Value
+
+
+
+ Digits
+ {{ digits > 0 ? '+' : '' }}{{ digits }}
+
+
+
+ tens
+ integer
+ decimals
+
+
+
+
diff --git a/vue/toolkit/src/composables/math/useSum/demo.vue b/vue/toolkit/src/composables/math/useSum/demo.vue
new file mode 100644
index 0000000..4921378
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useSum/demo.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+ monthly total
+ {{ items.length }} item{{ items.length === 1 ? '' : 's' }}
+
+
${{ total.toFixed(2) }}
+
+
+
+
+
+
+ No items — total is ${{ total.toFixed(2) }}
+
+
+
+
+ + Add item
+
+
+
diff --git a/vue/toolkit/src/composables/math/useTrunc/demo.vue b/vue/toolkit/src/composables/math/useTrunc/demo.vue
new file mode 100644
index 0000000..3585528
--- /dev/null
+++ b/vue/toolkit/src/composables/math/useTrunc/demo.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+ truncated
+ {{ truncated }}
+
+
+ dropped
+ {{ fractional.toFixed(3) }}
+
+
+
+
+
+ Value
+ {{ value }}
+
+
+
+
+
+
Samples
+
+
+ {{ s }}
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useBluetooth/demo.vue b/vue/toolkit/src/composables/media/useBluetooth/demo.vue
new file mode 100644
index 0000000..400a889
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useBluetooth/demo.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+ Web Bluetooth is not supported in this browser.
+
+
+
+
+
+ Web Bluetooth
+
+
+ {{ isConnected ? 'Connected' : 'Disconnected' }}
+
+
+
+
+
{{ deviceName }}
+
{{ deviceId }}
+
+
+ No device paired yet
+
+
+
+
+
+ Pair device
+
+
+ Reconnect
+
+
+ Disconnect
+
+
+
+
+ {{ errorMessage }}
+
+
+ Pairing prompts the browser's device chooser. A physical Bluetooth peripheral must be in range.
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useDisplayMedia/demo.vue b/vue/toolkit/src/composables/media/useDisplayMedia/demo.vue
new file mode 100644
index 0000000..775b594
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useDisplayMedia/demo.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+ Screen capture (getDisplayMedia) is not supported in this browser.
+
+
+
+
+
+ Screen share
+
+
+ {{ enabled ? 'Sharing' : 'Idle' }}
+
+
+
+
+
+
+ No active capture
+ Start sharing to preview your screen
+
+
+
+
+ {{ surfaceLabel }}
+
+
+
+
+
+ Start sharing
+
+
+ Stop sharing
+
+
+
+
+ enabled
+
+
+
+
+ Starting prompts the browser's screen-picker. Toggling enabled drives the same stream.
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useMediaControls/demo.vue b/vue/toolkit/src/composables/media/useMediaControls/demo.vue
new file mode 100644
index 0000000..fd464b4
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useMediaControls/demo.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatTime(currentTime) }}
+ {{ formatTime(duration) }}
+
+
+
+
+
+ {{ playing ? 'Pause' : ended ? 'Replay' : 'Play' }}
+
+
+ {{ muted ? 'Unmute' : 'Mute' }}
+
+
+ {{ isPictureInPicture ? 'Exit PiP' : 'Picture-in-Picture' }}
+
+
+
+
+
+ Volume
+
+
+
+
Speed
+
+
+ {{ r }}x
+
+
+
+
+
+
+
+ Reactive controls over a real <video>: seek, volume, mute, playback rate, buffered ranges, and Picture-in-Picture.
+
+
+
diff --git a/vue/toolkit/src/composables/media/useMemory/demo.vue b/vue/toolkit/src/composables/media/useMemory/demo.vue
new file mode 100644
index 0000000..30dd4a2
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useMemory/demo.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+ performance.memory is not available in this browser (Chromium only).
+
+
+
+
+
+ JS heap
+ {{ usedPercent.toFixed(1) }}%
+
+
+
+
+ {{ memory ? formatBytes(memory.usedJSHeapSize) : '—' }}
+
+ used
+
+
+
+
+
+
+
+
{{ memory ? formatBytes(memory.usedJSHeapSize) : '—' }}
+
used
+
+
+
{{ memory ? formatBytes(memory.totalJSHeapSize) : '—' }}
+
total
+
+
+
{{ memory ? formatBytes(memory.jsHeapSizeLimit) : '—' }}
+
limit
+
+
+
+
+
+
+ Allocate ~8 MB
+
+
+ Release
+
+
+
+
+ Sampled every second. Allocate buffers to watch usage climb; releasing lets the next GC reclaim it.
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/usePerformanceObserver/demo.vue b/vue/toolkit/src/composables/media/usePerformanceObserver/demo.vue
new file mode 100644
index 0000000..417098f
--- /dev/null
+++ b/vue/toolkit/src/composables/media/usePerformanceObserver/demo.vue
@@ -0,0 +1,134 @@
+
+
+
+
+
+ PerformanceObserver is not supported in this browser.
+
+
+
+
+
+ Performance entries
+
+
+ {{ isActive ? 'Observing' : 'Paused' }}
+
+
+
+
+
+
{{ total }}
+
entries seen
+
+
+
{{ lastDuration.toFixed(2) }}
+
last measure ms
+
+
+
+
+
+
+
+ {{ entry.type }}
+ {{ entry.name }}
+
+ {{ entry.duration.toFixed(1) }}ms
+
+
+
+ No entries yet — run a measured task
+
+
+
+
+
+
+ Run measured task
+
+
+ {{ isActive ? 'Pause' : 'Resume' }}
+
+
+
+
+ Each task emits User Timing mark/measure entries that the observer streams in live.
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useSpeechRecognition/demo.vue b/vue/toolkit/src/composables/media/useSpeechRecognition/demo.vue
new file mode 100644
index 0000000..d890ed0
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useSpeechRecognition/demo.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+ Speech Recognition is not supported in this browser.
+
+
+
+
+ Language
+
+ {{ l.label }}
+
+
+
+
+
+ {{ isListening ? 'Stop listening' : 'Start listening' }}
+
+
+
+
+ Transcript
+
+ {{ isFinal ? 'final' : 'interim' }}
+
+
+
+ {{ result }}
+
+
+ {{ isListening ? 'Listening… start speaking.' : 'Press start and say something.' }}
+
+
+
+
+
Confidence
+
+
+
{{ Math.round(confidence * 100) }}%
+
+
+
+
+ {{ 'error' in error ? error.error : error.message }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useSpeechSynthesis/demo.vue b/vue/toolkit/src/composables/media/useSpeechSynthesis/demo.vue
new file mode 100644
index 0000000..a82b7b0
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useSpeechSynthesis/demo.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Speech Synthesis is not supported in this browser.
+
+
+
+
+ Text to speak
+
+
+
+
+
+ Rate
+ {{ rate.toFixed(1) }}×
+
+
+
+
+ Pitch
+ {{ pitch.toFixed(1) }}
+
+
+
+
+ Volume
+ {{ Math.round(volume * 100) }}%
+
+
+
+
+
+
+ Speak
+
+
+ {{ isPlaying ? 'Pause' : 'Resume' }}
+
+
+ Stop
+
+
+
+
+ Status
+
+
+ {{ statusLabels[status] }}
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useUserMedia/demo.vue b/vue/toolkit/src/composables/media/useUserMedia/demo.vue
new file mode 100644
index 0000000..44e6bab
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useUserMedia/demo.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+ Camera capture (getUserMedia) is not supported in this browser.
+
+
+
+
+
+
+ Camera off
+ Press start to enable your webcam.
+
+
+
+ LIVE
+
+
+
+
+
+ Start camera
+
+
+ Stop camera
+
+
+ Flip
+
+
+
+
+ Facing
+
+ {{ facingMode === 'user' ? 'Front (user)' : 'Back (environment)' }}
+
+
+
+
+ {{ lastError }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useWebWorker/demo.vue b/vue/toolkit/src/composables/media/useWebWorker/demo.vue
new file mode 100644
index 0000000..ac75179
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useWebWorker/demo.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+ Web Workers are not supported in this browser.
+
+
+
+
+
Message to worker
+
+
+
+ Post
+
+
+
+
+
+
Latest reply
+
+ {{ data.transformed }}
+
+
+ The worker runs off the main thread — post a message to see its reply.
+
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/media/useWebWorkerFn/demo.vue b/vue/toolkit/src/composables/media/useWebWorkerFn/demo.vue
new file mode 100644
index 0000000..e446b5a
--- /dev/null
+++ b/vue/toolkit/src/composables/media/useWebWorkerFn/demo.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+ Web Workers are not supported in this browser.
+
+
+
+
+
+ Iterations
+ {{ iterations.toLocaleString() }}
+
+
+
+
+
+
+
+ {{ isRunning ? 'Computing…' : 'Run in worker' }}
+
+
+ Cancel
+
+
+
+
+
+ Result
+
+ {{ result?.toLocaleString() ?? '—' }}
+
+
+
+ Elapsed
+
+ {{ elapsed !== undefined ? `${elapsed}ms` : '—' }}
+
+
+
+
+
+ Status
+ {{ workerStatus }}
+
+
+
+ The heavy loop runs off the main thread, so this UI stays interactive while it computes.
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/computedAsync/demo.vue b/vue/toolkit/src/composables/reactivity/computedAsync/demo.vue
new file mode 100644
index 0000000..b40b510
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/computedAsync/demo.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
Fetch user by id
+
+
+ #{{ id }}
+
+
+
+
+
+
+
+
+ Resolving promise…
+
+
+
+ Evaluation failed
+ {{ error }}
+
+
+
+ {{ user.name }}
+ {{ user.role }}
+
+ {{ user.city }}
+
+
+
+
+ Awaiting first resolution…
+
+
+
+
+
+ evaluating
+
+
+ {{ evaluating }}
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/computedEager/demo.vue b/vue/toolkit/src/composables/reactivity/computedEager/demo.vue
new file mode 100644
index 0000000..0da20c2
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/computedEager/demo.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Password
+
+
+
+
+
+ Strength
+ {{ label }}
+
+
+
+
+
+
+
+ {{ rule.ok ? '✓' : '○' }}
+
+ {{ rule.text }}
+
+
+
+
+ length
+ {{ length }}
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/computedWithControl/demo.vue b/vue/toolkit/src/composables/reactivity/computedWithControl/demo.vue
new file mode 100644
index 0000000..85a6132
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/computedWithControl/demo.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+ Base price (tracked)
+ {{ source }}
+
+
+
+
+
+
+ Tax % (untracked)
+ {{ tax }}
+
+
+
+ Changing tax alone won't recompute — trigger to pull it in.
+
+
+
+
+
+ Total
+ {{ total }}
+
+
+
+
+ Trigger
+
+
+ Peek
+
+
+ Stop
+
+
+
+
+
+ Getter runs
+ {{ recomputes }}
+
+
+ Last peek
+ {{ peeked ?? '—' }}
+
+
+ Source watcher stopped — only Trigger updates the total now.
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/extendRef/demo.vue b/vue/toolkit/src/composables/reactivity/extendRef/demo.vue
new file mode 100644
index 0000000..5f14467
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/extendRef/demo.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ Volume
+ {{ volume.percent }}
+
+
+
+
+
+
+
+ {{ volume.isMuted ? 'Unmute' : 'Mute' }}
+
+
+ Max
+
+
+
+
+
+
+ volume.value
+ {{ volume.value }}
+
+
+ volume.percent
+ {{ volume.percent }}
+
+
+ volume.isMuted
+
+ {{ volume.isMuted }}
+
+
+
+
+
+ One ref carries its value, derived properties, and methods — no destructuring of an object needed.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/reactiveComputed/demo.vue b/vue/toolkit/src/composables/reactivity/reactiveComputed/demo.vue
new file mode 100644
index 0000000..7532ab3
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/reactiveComputed/demo.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+ Unit price
+
+
+
+ Quantity
+
+
+
+
+
+
Discount
+
+
+ {{ p }}%
+
+
+
+
+
+
+
+ Subtotal
+ ${{ cart.subtotal }}
+
+
+ Saved ({{ cart.discount }}%)
+ -${{ cart.saved }}
+
+
+
+ Total
+ ${{ cart.total }}
+
+
+
+
+ Each field reads from a single cached getter; writing cart.discount flows back to the source ref.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/reactiveOmit/demo.vue b/vue/toolkit/src/composables/reactivity/reactiveOmit/demo.vue
new file mode 100644
index 0000000..fb97877
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/reactiveOmit/demo.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+ Source object
+
+
+
+ name
+
+
+
+
+
role
+
+
+ {{ r }}
+
+
+
+
+
+ active
+
+
+
+
+
+
+
+
+ Omit token, email
+
+
{{ safeUser }}
+
+
+
+
+ Predicate: drop booleans
+
+
{{ noFlags }}
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/reactivePick/demo.vue b/vue/toolkit/src/composables/reactivity/reactivePick/demo.vue
new file mode 100644
index 0000000..72eaf78
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/reactivePick/demo.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+ Full source object
+
+
{{ settings }}
+
+ Editing the picked view above writes straight back to the source.
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/refAutoReset/demo.vue b/vue/toolkit/src/composables/reactivity/refAutoReset/demo.vue
new file mode 100644
index 0000000..09717af
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/refAutoReset/demo.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ Live value
+
+
+ {{ status }}
+
+ {{ status === 'Idle' ? 'reset' : 'active' }}
+
+
+
+
+
+ Save
+
+
+ Sync
+
+
+ Upload
+
+
+
+
+
+
+ reset delay
+ {{ delay }}ms
+
+
+
+
+
+ Copy-to-clipboard pattern
+
+ {{ copied ? 'Copied!' : 'Copy' }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/refDebounced/demo.vue b/vue/toolkit/src/composables/reactivity/refDebounced/demo.vue
new file mode 100644
index 0000000..91d886e
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/refDebounced/demo.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+ Type to search
+
+
+
+
+
+
+ debounce
+ {{ ms }}ms
+
+
+
+
+
+
+
+ Source
+
+
{{ search || '—' }}
+
+
+
+ Debounced
+
+
+
{{ debounced || '—' }}
+
+
+
+
+ {{ pending ? 'Waiting for input to settle…' : 'Synced — debounced value caught up' }}
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/refDefault/demo.vue b/vue/toolkit/src/composables/reactivity/refDefault/demo.vue
new file mode 100644
index 0000000..2b7750f
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/refDefault/demo.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+ Resolved value
+
+
+ {{ name }}
+
+ {{ raw == null ? 'using default' : 'from source' }}
+
+
+
+
+
+
+ Source ref (writes pass through)
+
+
+
+
+ Clear
+
+
+
+ raw.value = {{ raw === null ? 'null' : `"${raw}"` }}
+
+
+
+
+
+ Reactive fallback
+
+
+
+ Changes here update the resolved value while the source is empty.
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/refThrottled/demo.vue b/vue/toolkit/src/composables/reactivity/refThrottled/demo.vue
new file mode 100644
index 0000000..ac0fc31
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/refThrottled/demo.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+ Source
+ {{ source }}
+ {{ sourceUpdates }} updates
+
+
+ Throttled
+ {{ throttled }}
+ {{ throttledUpdates }} updates
+
+
+
+
+ Lag behind source
+ +{{ lag }}
+
+
+
+
+ Throttle window: {{ delay }}ms
+
+
+ Reset to apply a new window
+
+
+
+
+ Run (60ms)
+
+
+ Pause
+
+
+ Reset
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/refWithControl/demo.vue b/vue/toolkit/src/composables/reactivity/refWithControl/demo.vue
new file mode 100644
index 0000000..f829e24
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/refWithControl/demo.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+ Volume (tracked)
+ {{ volume }}
+
+
+
+ - 1
+
+
+ + 1
+
+
+ Set 99
+
+
+
+
+
+
+ peek() snapshot
+ untracked read; lay() writes silently
+
+
{{ peeked }}
+
+
+
+
+ peek()
+
+
+ lay() +1 silent
+
+
+
+
+
Hooks log
+
+
+ Try setting volume to 99 to see onBeforeChange veto.
+
+
+ {{ entry.vetoed ? '✗' : '✓' }} {{ entry.message }}
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/syncRef/demo.vue b/vue/toolkit/src/composables/reactivity/syncRef/demo.vue
new file mode 100644
index 0000000..539215c
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/syncRef/demo.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+ Two-way sync + transform
+
+
+ {{ synced ? 'live' : 'stopped' }}
+
+
+
+
+
+ Celsius
+
+
+
+ Fahrenheit
+
+
+
+
+
+ {{ celsius }}°C ⇄ {{ fahrenheit }}°F
+
+
+
+
+
+ Stop synchronization
+
+
+ Watchers torn down — the two refs now drift independently.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/toReactive/demo.vue b/vue/toolkit/src/composables/reactivity/toReactive/demo.vue
new file mode 100644
index 0000000..1838137
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/toReactive/demo.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+ Name
+
+
+
+ Role
+
+
+
+
+ Level: {{ profile.level }}
+
+
+
+
+
+
+
source.value (the backing ref)
+
{{ JSON.stringify(source, null, 2) }}
+
+
+
+
Reassign the whole ref
+
+
+ {{ preset.name.split(' ')[0] }}
+
+
+
+ The proxy survives reassignment — fields above update without re-binding.
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useCached/demo.vue b/vue/toolkit/src/composables/reactivity/useCached/demo.vue
new file mode 100644
index 0000000..8a22084
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useCached/demo.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+ Source ref
+
+
+
+
+
+ {{ sample }}
+
+
+
+
+
+
+ Default cache
+ a === b
+
+
"{{ cachedDefault }}"
+
+
+
+
+ Case-insensitive cache
+ toLowerCase() match
+
+
"{{ cachedInsensitive }}"
+
+
+
+
+ Toggle between Hello , hello and
+ HELLO : the default cache follows every change, while the
+ case-insensitive cache keeps its first stored casing.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useCloned/demo.vue b/vue/toolkit/src/composables/reactivity/useCloned/demo.vue
new file mode 100644
index 0000000..39ad7d0
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useCloned/demo.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+ useCloned
+
+
+ {{ isModified ? 'Modified' : 'In sync' }}
+
+
+
+
+
+
Source
+
+
+
name
+ {{ original.name }}
+
+
+
role
+ {{ original.role }}
+
+
+
tags
+ {{ original.tags.length }}
+
+
+
+
+
+
Cloned (editable)
+
+
+
name
+ {{ cloned.name }}
+
+
+
role
+ {{ cloned.role }}
+
+
+
+ {{ tag }}
+
+
+
+
+
+
+
+
+ Cycle source role
+
+
+ Edit clone
+
+
+ Re-sync from source
+
+
+
+
+ Editing the source auto-resyncs the clone. Editing the clone marks it modified without touching the source.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useDebounceFn/demo.vue b/vue/toolkit/src/composables/reactivity/useDebounceFn/demo.vue
new file mode 100644
index 0000000..ddc4b6d
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useDebounceFn/demo.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+ useDebounceFn
+
+
+ {{ runSearch.isPending.value ? 'Pending' : 'Settled' }}
+
+
+
+
+
+ Type to search
+
+
+
+
+
+
+ Delay
+
+
+ {{ delay }}ms
+
+
+
+
+
Invocations
+
{{ calls }}
+
+
+
Last result
+
{{ lastResult || '—' }}
+
+
+
+
+
+ Flush now
+
+
+ Cancel
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/usePrevious/demo.vue b/vue/toolkit/src/composables/reactivity/usePrevious/demo.vue
new file mode 100644
index 0000000..dbe19dc
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/usePrevious/demo.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
usePrevious
+
+
+
+
Current
+
{{ selected }}
+
+
+
Previous
+
{{ previous }}
+
+
+
+
+
Select a theme
+
+
+ {{ theme }}
+
+
+
+
+
+ Seeded with "None" as the initial previous value until the source first changes.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useSyncRefs/demo.vue b/vue/toolkit/src/composables/reactivity/useSyncRefs/demo.vue
new file mode 100644
index 0000000..2261c7f
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useSyncRefs/demo.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
useSyncRefs
+
+
+
+
Three independent target refs stay synced to the source:
+
+
+
+ swatch
+
+
+
+ label
+ {{ label }}
+
+
+ hex
+ {{ hex }}
+
+
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useThrottleFn/demo.vue b/vue/toolkit/src/composables/reactivity/useThrottleFn/demo.vue
new file mode 100644
index 0000000..392a0d0
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useThrottleFn/demo.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
useThrottleFn
+
+
+
+
+ Move your pointer over this area
+
+
+
+
+
+ Window
+
+
+ {{ delay }}ms
+
+
+
+
+
+
+ x: {{ position.x }} · y: {{ position.y }}
+
+
+ Flush trailing
+
+
+
+
+ Leading + trailing throttling caps the handler to once per window — drag faster and watch the fired count lag behind events.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useToNumber/demo.vue b/vue/toolkit/src/composables/reactivity/useToNumber/demo.vue
new file mode 100644
index 0000000..80591a2
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useToNumber/demo.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+ Source value
+
+
+
+
+
+ {{ v }}
+
+
+
+
+
+
+ {{ variant.label }}
+ {{ variant.desc }}
+
+
+ {{ Number.isNaN(variant.value) ? 'NaN' : variant.value }}
+
+
+
+
+
+ One reactive source, four useToNumber
+ instances. Try abc to see how
+ nanToZero and clamping tame invalid input.
+
+
+
diff --git a/vue/toolkit/src/composables/reactivity/useToString/demo.vue b/vue/toolkit/src/composables/reactivity/useToString/demo.vue
new file mode 100644
index 0000000..e1a3be3
--- /dev/null
+++ b/vue/toolkit/src/composables/reactivity/useToString/demo.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
Live source values
+
+
+
count
+
+ −
+ {{ count }}
+ +
+
+
+
+
+ ratio
+
+
+
+
+ enabled
+
+
+
+
+
+
+
+ {{ row.type }}
+
+ "{{ row.value }}"
+
+
+
+
+ useToString is
+ computed(() => String(toValue(v))) —
+ it stringifies refs, getters, and reactive objects alike.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/onKeyStroke/demo.vue b/vue/toolkit/src/composables/sensors/onKeyStroke/demo.vue
new file mode 100644
index 0000000..58a9d0f
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/onKeyStroke/demo.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+ Click here, then press keys / arrows
+
+
+
+
+
+
+ {{ lastKey === ' ' ? 'Space' : (lastKey || '—') }}
+
+ {{ lastCode || 'event.code' }}
+
+
+
+
+
+
+ dedupe held keys
+
+ {{ pressCount }} strokes
+
+
+
+
Recent
+
+ {{ k }}
+
+
No keys yet — focus the area above.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/onLongPress/demo.vue b/vue/toolkit/src/composables/sensors/onLongPress/demo.vue
new file mode 100644
index 0000000..6e3ef92
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/onLongPress/demo.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ {{ triggered ? 'Long press detected' : 'Press & hold me' }}
+
+
+ hold for {{ DELAY }}ms — a quick click counts as a tap
+
+
+
+
+
+ {{ longPresses }}
+ long presses
+
+
+ {{ taps }}
+ taps
+
+
+
+
+
+ Last hold
+ {{ lastDuration }}ms
+
+
+
+
+
+ Moving more than ~10px while holding cancels the press. The
+ onMouseUp callback tells taps from holds.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/onStartTyping/demo.vue b/vue/toolkit/src/composables/sensors/onStartTyping/demo.vue
new file mode 100644
index 0000000..a5257ef
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/onStartTyping/demo.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+ Start typing
+ anywhere on the page
+
+
+ the search box below focuses itself automatically
+
+
+ captured {{ lastChar === ' ' ? 'Space' : lastChar }}
+
+
+
+
+
+
+
+
+ No fruit matches "{{ query }}"
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useBattery/demo.vue b/vue/toolkit/src/composables/sensors/useBattery/demo.vue
new file mode 100644
index 0000000..d2242d5
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useBattery/demo.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+ Battery Status API is not supported in this browser.
+
+
+
+
+
+ Battery Status
+
+
+ {{ statusLabel }}
+
+
+
+
+
+
+
+
+
+
Time to full
+
{{ chargingTimeLabel }}
+
+
+
Time to empty
+
{{ dischargingTimeLabel }}
+
+
+
+
+ Plug in or unplug your charger to watch the charging state, level, and remaining time update live.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useBodyScrollLock/demo.vue b/vue/toolkit/src/composables/sensors/useBodyScrollLock/demo.vue
new file mode 100644
index 0000000..8859740
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useBodyScrollLock/demo.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
Body scroll lock
+
+ Open the dialog and try scrolling the page — the body is locked while it is open,
+ scrollbar width compensated so nothing shifts.
+
+
+
+ {{ open ? 'Scroll locked' : 'Scroll free' }}
+
+
+
+
+ Open dialog
+
+
+
+
+
+
+
+
Mailboxes
+
+ Close
+
+
+
+
The list scrolls, the page behind it does not.
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useClickOutside/demo.vue b/vue/toolkit/src/composables/sensors/useClickOutside/demo.vue
new file mode 100644
index 0000000..a475cca
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useClickOutside/demo.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
Click outside to dismiss
+
+ Open the menu, then click anywhere outside it to close. Clicks inside keep it open.
+
+
+
+
+
+ Document actions
+ ▾
+
+
+
+
+
+
+
+
+
+ outside dismissals
+ {{ dismissals }}
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useDeviceMotion/demo.vue b/vue/toolkit/src/composables/sensors/useDeviceMotion/demo.vue
new file mode 100644
index 0000000..1be6faa
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useDeviceMotion/demo.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+ DeviceMotionEvent is not supported in this browser.
+
+
+
+
+
+ Device motion
+
+
+ {{ permissionGranted ? 'Listening' : 'Idle' }}
+
+
+
+
+
+
+ |a| incl. gravity
+ {{ magnitude.toFixed(2) }} m/s²
+
+
+
+
+
+
+
Acceleration (m/s²)
+
+
+
{{ axis.label }}
+
{{ fmt(axis.value) }}
+
+
+
+
Rotation rate (deg/s)
+
+
+
{{ r.label }}
+
{{ fmt(r.value) }}
+
+
+
+
+ Sampling interval
+ {{ interval ? `${interval} ms` : '—' }}
+
+
+
+
+
+ Enable motion access
+
+
+ Move or tilt your device to see acceleration and rotation update in real time.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useDeviceOrientation/demo.vue b/vue/toolkit/src/composables/sensors/useDeviceOrientation/demo.vue
new file mode 100644
index 0000000..5fad36d
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useDeviceOrientation/demo.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+ DeviceOrientationEvent is not supported in this browser.
+
+
+
+
+
+ Device orientation
+
+
+ {{ isAbsolute ? 'Absolute' : 'Relative' }}
+
+
+
+
+
+
+
+
+
+
{{ fmt(angle.value) }}
+
{{ angle.label }}
+
+
+
+
+ Tilt and rotate your device to move the compass needle and tilt the dial. Best viewed on a phone.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useDevicePixelRatio/demo.vue b/vue/toolkit/src/composables/sensors/useDevicePixelRatio/demo.vue
new file mode 100644
index 0000000..eb2612d
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useDevicePixelRatio/demo.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ Device Pixel Ratio
+
+
+ {{ classification }}
+
+
+
+
+ {{ pixelRatio }}
+ {{ ratioLabel }}
+
+
+
+
+
+
+
+
+ 1 CSS pixel is rendered with about
+ {{ physicalPerCss }}
+ physical pixels.
+
+
+
+
+
+ Zoom the page (Cmd/Ctrl + and -) or drag this window to a monitor with a different scale to watch the ratio update live.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useDevicesList/demo.vue b/vue/toolkit/src/composables/sensors/useDevicesList/demo.vue
new file mode 100644
index 0000000..929495e
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useDevicesList/demo.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+ The MediaDevices API is not supported in this browser.
+
+
+
+
+
+ Media Devices
+
+
+ {{ permissionGranted ? 'Labels unlocked' : `${total} found` }}
+
+
+
+
+
+
+ {{ group.label }}
+ {{ group.devices.length }}
+
+
+
+ None detected
+
+
+
+
+
+ {{ group.icon }}
+
+ {{ deviceLabel(device, index, group.label) }}
+
+
+
+
+
+
+
+ {{ permissionGranted ? 'Permission granted' : requesting ? 'Requesting…' : 'Grant access to reveal names' }}
+
+
+
+ Without permission, device names are hidden. Plug in or remove a device to watch the list re-enumerate automatically.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useElementByPoint/demo.vue b/vue/toolkit/src/composables/sensors/useElementByPoint/demo.vue
new file mode 100644
index 0000000..f6caec0
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useElementByPoint/demo.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+ elementFromPoint is not supported in this browser.
+
+
+
+
+
+
+
+ {{ tracking ? 'Probing…' : 'Move your cursor here' }}
+
+
+
+
+
+ point
+ {{ coords }}
+
+
+ element
+ {{ elementInfo?.selector ?? '—' }}
+
+
+ text
+ {{ elementInfo.text }}
+
+
+
+
+
+
+ {{ active ? 'Pause sampling' : 'Resume sampling' }}
+
+ Sampled every animation frame
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useElementHover/demo.vue b/vue/toolkit/src/composables/sensors/useElementHover/demo.vue
new file mode 100644
index 0000000..0b0aa6a
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useElementHover/demo.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ Instant
+
+
+ {{ isInstantHovered ? 'hovered' : 'idle' }}
+
+
+
Hover this card to flip the state immediately.
+
+
+
+
+
+ Delayed
+
+
+ {{ isDelayedHovered ? 'hovered' : 'idle' }}
+
+
+
+ Waits {{ delayEnter }}ms to enter,
+ {{ delayLeave }}ms to leave.
+
+
+
+
+ Enter/leave delays debounce hover flicker — useful for tooltips and submenus that should not vanish the instant you cross a gap.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useEscapeKey/demo.vue b/vue/toolkit/src/composables/sensors/useEscapeKey/demo.vue
new file mode 100644
index 0000000..6fc1d04
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useEscapeKey/demo.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+ Escape Stack
+
+ {{ (panelOpen ? 1 : 0) + (dialogOpen ? 1 : 0) }} open
+
+
+
+
+ Open panel
+
+
+
+
+
+ Panel open. Press Esc to dismiss, or open a nested dialog on top.
+
+
+
+ Open nested dialog
+
+
+
+
+
+ Nested dialog — Esc closes me before the panel.
+
+
+ top
+
+
+
+
+
+
+ last dismissed via Esc
+ {{ lastDismissed ?? '—' }}
+
+
+
+ Only the topmost layer responds to a single Escape press, so nested dismissables close in the right order.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useFocus/demo.vue b/vue/toolkit/src/composables/sensors/useFocus/demo.vue
new file mode 100644
index 0000000..56ee3ce
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useFocus/demo.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+ Newsletter email
+
+
+
+
+
+ Focus state
+
+
+ {{ focused ? 'Focused' : 'Blurred' }}
+
+
+
+
+
+ Focus input
+
+
+ Blur input
+
+
+
+
+ focused is writable — click the buttons or use Tab to drive it.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useFocusWithin/demo.vue b/vue/toolkit/src/composables/sensors/useFocusWithin/demo.vue
new file mode 100644
index 0000000..5ecaefe
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useFocusWithin/demo.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ Shipping address
+
+
+
+ {{ focused ? 'Editing' : 'Idle' }}
+
+
+
+
+
+
+ The whole panel highlights while focus lives in any descendant field — tabbing between inputs stays "Editing".
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useFps/demo.vue b/vue/toolkit/src/composables/sensors/useFps/demo.vue
new file mode 100644
index 0000000..11aaa63
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useFps/demo.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+ Frames / second
+
+
+
+ {{ health.label }}
+
+
+
+
+ {{ fps }}
+ fps
+
+
+
+
+
+
+
+
Min
+
+ {{ Number.isFinite(min) ? min : '—' }}
+
+
+
+
+
+
+
+ Simulated load
+ {{ stress }} ms/frame
+
+
+
+
+
+
+ {{ isActive ? 'Pause' : 'Resume' }}
+
+
+ Reset min / max
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useGamepad/demo.vue b/vue/toolkit/src/composables/sensors/useGamepad/demo.vue
new file mode 100644
index 0000000..6b706ca
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useGamepad/demo.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
+ The Gamepad API is not supported in this browser.
+
+
+
+
+
+ Gamepad
+
+
+
+ {{ hasPad ? 'Connected' : 'Waiting' }}
+
+
+
+
+
No controller detected
+
+ Connect a gamepad and press any button to wake it.
+
+
+
+
+
+
+ {{ gamepad?.id }}
+
+
+
+
+
+ Buttons
+
+
+
+ {{ b.key }}
+
+
+
+
+
+
+
+
+
+
+ {{ side }} stick
+
+
+
+
+
+
+
+
+
+ State polls every animation frame and stays paused while no pad is connected.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useGeolocation/demo.vue b/vue/toolkit/src/composables/sensors/useGeolocation/demo.vue
new file mode 100644
index 0000000..4f7d46b
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useGeolocation/demo.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+ Geolocation is not supported in this browser.
+
+
+
+
+
+ Geolocation
+
+
+
+ {{ isActive ? 'Watching' : 'Idle' }}
+
+
+
+
+
+
Latitude
+
+ {{ ready ? fmt(coords.latitude) : '—' }}
+
+
+
+
Longitude
+
+ {{ ready ? fmt(coords.longitude) : '—' }}
+
+
+
+
+
+
+
Accuracy
+
+ {{ ready ? `± ${Math.round(coords.accuracy)} m` : '—' }}
+
+
+
+
Heading
+
+ {{ ready ? `${fmt(coords.heading, 0)}°` : '—' }}
+
+
+
+
Located at
+ {{ located }}
+
+
+
+
+ {{ error.message || 'Unable to retrieve your location.' }}
+
+
+
+
+ {{ ready ? 'Resume watching' : 'Find my location' }}
+
+
+ Stop watching
+
+
+
+
+ Requires permission — nothing is requested until you press the button.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useIdle/demo.vue b/vue/toolkit/src/composables/sensors/useIdle/demo.vue
new file mode 100644
index 0000000..f85d468
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useIdle/demo.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+ Status
+
+
+ {{ idle ? 'Idle' : 'Active' }}
+
+
+
+
+ Move your mouse, type, or scroll to stay active. After
+ {{ (timeout / 1000).toFixed(0) }}s
+ without activity you go idle.
+
+
+
+
+
+
Inactive for
+
+ {{ secondsSinceActive }}s
+
+
+
+
Last active
+
+ {{ lastActiveLabel }}
+
+
+
+
+
+
+ Reset timer
+
+
+ Stop
+
+
+ Start
+
+
+
+ tracking: {{ isPending ? 'on' : 'off' }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useInfiniteScroll/demo.vue b/vue/toolkit/src/composables/sensors/useInfiniteScroll/demo.vue
new file mode 100644
index 0000000..89b6a86
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useInfiniteScroll/demo.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Repositories
+
+ {{ items.length }} / {{ MAX_ITEMS }} loaded
+
+
+
+
+
+
+ {{ repo.name }}
+
+ ★ {{ repo.stars.toLocaleString() }}
+
+
+
+
+
+
+ Loading more…
+
+
+
+ You've reached the end.
+
+
+
+
+ Scroll to within 24px of the bottom to fetch the next page.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useKeyModifier/demo.vue b/vue/toolkit/src/composables/sensors/useKeyModifier/demo.vue
new file mode 100644
index 0000000..8c445b2
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useKeyModifier/demo.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+ Toggle Caps Lock / Num Lock ,
+ or hold a modifier key, then press any key (or click here) to refresh state.
+
+
+
+
Lock keys
+
+
+ {{ key.label }}
+
+ {{ key.state === null ? 'null' : key.state ? 'ON' : 'off' }}
+
+
+
+
+
+
+
Held modifiers
+
+
+ {{ key.label }}
+
+
+
+
+
+
+ Modifier state is read from getModifierState , so it stays
+ null until the first matching event arrives.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useMagicKeys/demo.vue b/vue/toolkit/src/composables/sensors/useMagicKeys/demo.vue
new file mode 100644
index 0000000..8ac46f1
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useMagicKeys/demo.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+ Press keys or try a combo — Ctrl + S ,
+ Cmd + K , or
+ Ctrl + A .
+
+
+
+
+ Currently pressed
+
+ Reset
+
+
+
+
+
+ {{ key === ' ' ? 'Space' : key }}
+
+ No keys held
+
+
+
+
+
+ {{ combo.label }}
+
+
+
+
+
Triggered actions
+
+
Waiting for a chord…
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useMouse/demo.vue b/vue/toolkit/src/composables/sensors/useMouse/demo.vue
new file mode 100644
index 0000000..a7d39a4
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useMouse/demo.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
page x
+
{{ Math.round(x) }}
+
+
+
page y
+
{{ Math.round(y) }}
+
+
+
source
+
{{ sourceType ?? '—' }}
+
+
+
+
+
Move inside this area
+
+
+
+ {{ Math.round(relX) }}, {{ Math.round(relY) }}
+
+
+ Hover to track relative position
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useMouseInElement/demo.vue b/vue/toolkit/src/composables/sensors/useMouseInElement/demo.vue
new file mode 100644
index 0000000..772823e
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useMouseInElement/demo.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Move your cursor here
+
+
+
+
+
+
elementX
+
{{ Math.round(clampedX) }}px
+
{{ percentX }}%
+
+
+
elementY
+
{{ Math.round(clampedY) }}px
+
{{ percentY }}%
+
+
+
+
+ Cursor
+
+
+ {{ isOutside ? 'Outside' : 'Inside' }}
+
+
+
+
+ Coordinates are relative to the box's top-left corner — observed via useElementBounding, so they stay correct as it moves or resizes.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useMousePressed/demo.vue b/vue/toolkit/src/composables/sensors/useMousePressed/demo.vue
new file mode 100644
index 0000000..2fd2ec5
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useMousePressed/demo.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+ {{ pressed ? 'Holding…' : 'Press and hold' }}
+
+
+
+
+
+
+
Pressed
+
+ {{ pressed ? 'true' : 'false' }}
+
+
+
+
Source
+
+ {{ sourceType ? sourceLabel[sourceType] : '—' }}
+
+
+
+
Presses
+
{{ pressCount }}
+
+
+
+
+ Tracking is scoped to the pad via target, but release is global — let go anywhere and pressed flips back.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useNetwork/demo.vue b/vue/toolkit/src/composables/sensors/useNetwork/demo.vue
new file mode 100644
index 0000000..39523da
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useNetwork/demo.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ isOnline ? 'Online' : 'Offline' }}
+
+
+ {{ isOnline ? 'since' : 'at' }} {{ lastTransition }}
+
+
+
+
+ {{ type }}
+
+
+
+
+
+ Effective type
+ {{ effectiveType ?? 'unknown' }}
+
+
+
+
+
+
+
+
Downlink
+ {{ fmt(downlink, ' Mbps') }}
+
+
+
RTT
+ {{ fmt(rtt, ' ms') }}
+
+
+
Max downlink
+ {{ fmt(downlinkMax, ' Mbps') }}
+
+
+
Save data
+ {{ saveData === undefined ? '—' : (saveData ? 'on' : 'off') }}
+
+
+
+
+
+
+ Network Information API not supported
+
+
+ Online status still works; connection details are unavailable in this browser.
+
+
+
+
+ Toggle your device's connection (or DevTools network conditions) to watch the state react live.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useOnline/demo.vue b/vue/toolkit/src/composables/sensors/useOnline/demo.vue
new file mode 100644
index 0000000..f700f6d
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useOnline/demo.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+ {{ online ? 'You are online' : 'You are offline' }}
+
+
+ Reflects navigator.onLine
+
+
+
+
+
+ online.value
+
+
+ {{ online ? 'true' : 'false' }}
+
+
+
+
+ Disable Wi-Fi or flip DevTools to Offline and the banner updates instantly.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/usePageLeave/demo.vue b/vue/toolkit/src/composables/sensors/usePageLeave/demo.vue
new file mode 100644
index 0000000..0859c7f
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/usePageLeave/demo.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ {{ hasLeft ? '👋' : '👀' }}
+
+
+ {{ hasLeft ? 'Come back!' : 'Pointer is on the page' }}
+
+
+
+
+
+ State
+
+
+ {{ hasLeft ? 'Left' : 'Inside' }}
+
+
+
+ Exits
+ {{ leaveCount }}
+
+
+
+
+ Move your cursor out of the browser viewport — the classic exit-intent signal for save-cart prompts and the like.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useParallax/demo.vue b/vue/toolkit/src/composables/sensors/useParallax/demo.vue
new file mode 100644
index 0000000..67d1264
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useParallax/demo.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+ Move your pointer over the panel — layers shift by depth.
+
+
+
+
+
+ Intensity
+
+ {{ intensity }}°
+
+
+
+
+
+
+
Roll
+
{{ fmt(roll) }}
+
+
+
Tilt
+
{{ fmt(tilt) }}
+
+
+
Source
+
{{ source }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/usePointer/demo.vue b/vue/toolkit/src/composables/sensors/usePointer/demo.vue
new file mode 100644
index 0000000..ac04c30
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/usePointer/demo.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+ {{ isInside ? 'Tracking' : 'Move your pointer here' }}
+
+
+
+
+
+
+
Position
+
{{ Math.round(x) }}, {{ Math.round(y) }}
+
+
+
Pressure
+
{{ pressure.toFixed(2) }}
+
+
+
Tilt X / Y
+
{{ tiltX }} / {{ tiltY }}
+
+
+
Type
+
{{ typeLabel }}
+
+
+
+
+ Pointer inside
+
+
+ {{ isInside ? 'Yes' : 'No' }}
+
+
+
+
+ Pressure scales the dot. Pen tilt & pressure show real values on supporting hardware.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/usePointerLock/demo.vue b/vue/toolkit/src/composables/sensors/usePointerLock/demo.vue
new file mode 100644
index 0000000..48970f9
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/usePointerLock/demo.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+ {{ isLocked ? 'Locked — move your mouse' : 'Click to lock pointer' }}
+
+
+
+
+
+ {{ isLocked ? 'Release pointer (Esc)' : 'Lock pointer' }}
+
+
+
+ Lock state
+
+
+ {{ isLocked ? 'Locked' : 'Unlocked' }}
+
+
+
+
+ While locked, the cursor is hidden and only relative
+ movementX/Y drives the dot. Press
+ Esc
+ to release.
+
+
+
+
+
+ Pointer Lock API not supported
+
+
+ This browser cannot lock the pointer to an element.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/usePointerSwipe/demo.vue b/vue/toolkit/src/composables/sensors/usePointerSwipe/demo.vue
new file mode 100644
index 0000000..cb13864
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/usePointerSwipe/demo.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+ {{ arrows[direction] }}
+
+
+ Drag in any direction
+
+
+
+
+
+
Direction
+
{{ direction }}
+
+
+
Swiping
+
+ {{ isSwiping ? 'active' : 'idle' }}
+
+
+
+
Distance X / Y
+
{{ Math.round(distanceX) }} / {{ Math.round(distanceY) }}
+
+
+
Last swipe
+
+ {{ lastDirection }}
+ ×{{ swipeCount }}
+
+
+
+
+
+ Threshold is 40px — shorter drags resolve to
+ none.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useScreenOrientation/demo.vue b/vue/toolkit/src/composables/sensors/useScreenOrientation/demo.vue
new file mode 100644
index 0000000..7245ebe
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useScreenOrientation/demo.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
Orientation
+
{{ orientation ?? '—' }}
+
+
+
+
+
+
Lock to
+
+
+ {{ type }}
+
+
+
+ Unlock
+
+
+
+
+ {{ error }}
+
+
+ Locking requires fullscreen on most devices and is typically a no-op on desktop.
+
+
+
+
+
+ Screen Orientation API not supported
+
+
+ This browser does not expose screen.orientation.
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useScroll/demo.vue b/vue/toolkit/src/composables/sensors/useScroll/demo.vue
new file mode 100644
index 0000000..07626c6
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useScroll/demo.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+ Scroll container
+
+
+ {{ isScrolling ? 'scrolling' : 'idle' }}
+
+
+
+
+
+
+ {{ item.label }}
+ #{{ item.id }}
+
+
+
+
+
+
+ offset
+ x {{ Math.round(x) }} · y {{ Math.round(y) }} {{ arrowFor() }}
+
+
+
+
+
+ {{ edge }}
+ {{ arrivedState[edge] ? 'arrived' : '—' }}
+
+
+
+
+
+ Scroll to top
+
+
+ Scroll to bottom
+
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useScrollLock/demo.vue b/vue/toolkit/src/composables/sensors/useScrollLock/demo.vue
new file mode 100644
index 0000000..b857a99
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useScrollLock/demo.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+ Scroll lock
+
+ {{ isLocked ? 'locked' : 'free' }}
+
+
+
+
+
+
+ {{ isLocked ? 'Overflow is hidden — the panel above will not scroll.' : 'Try scrolling the panel, then lock it.' }}
+
+
+
+
+
+
+ Lock scrolling
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useSwipe/demo.vue b/vue/toolkit/src/composables/sensors/useSwipe/demo.vue
new file mode 100644
index 0000000..c311607
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useSwipe/demo.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
Swipe area (touch / drag)
+
+
+
+ {{ arrows[direction] }}
+ {{ isSwiping ? 'swiping…' : 'swipe me' }}
+
+
+
+
+
+
+ Δx
+ {{ Math.round(lengthX) }}px
+
+
+ Δy
+ {{ Math.round(lengthY) }}px
+
+
+
+ Last swipe
+ {{ lastDirection }}
+
+
+
+
+ start ({{ Math.round(coordsStart.x) }}, {{ Math.round(coordsStart.y) }})
+ → end ({{ Math.round(coordsEnd.x) }}, {{ Math.round(coordsEnd.y) }})
+
+
+
+ On a desktop, click and drag with touch emulation; on touch devices, swipe in any direction.
+
+
+
diff --git a/vue/toolkit/src/composables/sensors/useTextSelection/demo.vue b/vue/toolkit/src/composables/sensors/useTextSelection/demo.vue
new file mode 100644
index 0000000..14eb2ec
--- /dev/null
+++ b/vue/toolkit/src/composables/sensors/useTextSelection/demo.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
Select some text
+
+
+ {{ sample }}
+
+
+
+
+ {{ charCount }}
+ chars
+
+
+ {{ wordCount }}
+ words
+
+
+ {{ ranges.length }}
+ ranges
+
+
+
+
+
Selected text
+
+ “{{ text }}”
+
+
+ Nothing selected yet.
+
+
+
+
+ first rect · {{ Math.round(rects[0]!.width) }} × {{ Math.round(rects[0]!.height) }} px
+
+
+
diff --git a/vue/toolkit/src/composables/state/createSharedComposable/demo.vue b/vue/toolkit/src/composables/state/createSharedComposable/demo.vue
new file mode 100644
index 0000000..c94f87d
--- /dev/null
+++ b/vue/toolkit/src/composables/state/createSharedComposable/demo.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+ Shared composable
+
+ {{ subscribers.length }} active
+
+
+
+
+
+
+
+
+ No consumers — the shared scope is disposed and its interval cleared.
+
+
+
+
+ Every consumer reads the same count and the same setup id (# ),
+ proving a single instance and one interval back all of them. Remove every
+ consumer to dispose the scope; adding one again creates a fresh instance.
+
+
+
+
+ Add consumer
+
+
+ Remove consumer
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useAppSharedState/demo.vue b/vue/toolkit/src/composables/state/useAppSharedState/demo.vue
new file mode 100644
index 0000000..f014e3d
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useAppSharedState/demo.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+ Shared likes
+ {{ total }}
+ last reaction by {{ alice.state.lastBy }}
+
+
+
+
+
+
+ A
+ Alice’s panel
+
+
+ reads {{ alice.state.likes }}
+
+
+ ♥ Like
+
+
+
+
+
+
+ B
+ Bob’s panel
+
+
+ reads {{ bob.state.likes }}
+
+
+ ♥ Like
+
+
+
+
+
+ Both panels call useStore() independently yet share one
+ reactive instance — like in either column updates the other instantly.
+
+
+
diff --git a/vue/toolkit/src/composables/state/useAsyncState/demo.vue b/vue/toolkit/src/composables/state/useAsyncState/demo.vue
new file mode 100644
index 0000000..074d657
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useAsyncState/demo.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+ Profile
+
+
+ {{ isLoading ? 'Loading' : isReady ? 'Ready' : error ? 'Error' : 'Idle' }}
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+
+
+ {{ state.name.charAt(0) }}
+
+
+ {{ state.name }}
+ {{ state.role }}
+ {{ state.commits.toLocaleString() }} commits
+
+
+
+
+
+ Press “Load” to fetch a profile.
+
+
+
+
+
+ Profile id
+ #{{ id }}
+
+
+
+
+
+
+ {{ isLoading ? 'Loading…' : 'Load' }}
+
+
+ Trigger error
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useContextFactory/demo.vue b/vue/toolkit/src/composables/state/useContextFactory/demo.vue
new file mode 100644
index 0000000..2f3ede4
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useContextFactory/demo.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
Provider (parent)
+
+
+
+
+
Density
+
+
+ Cozy
+
+
+ Compact
+
+
+
+
+
+
+
+
+
+ The card reads context with inject() — change the controls above
+ and it updates without a single prop.
+
+
+
diff --git a/vue/toolkit/src/composables/state/useCounter/demo.vue b/vue/toolkit/src/composables/state/useCounter/demo.vue
index fe65768..01d7de8 100644
--- a/vue/toolkit/src/composables/state/useCounter/demo.vue
+++ b/vue/toolkit/src/composables/state/useCounter/demo.vue
@@ -1,6 +1,94 @@
-
+
-
+
+
+
+ Tickets
+
+ {{ MIN }} – {{ MAX }}
+
+
+
+
+
+
+ −
+
+
+
+ {{ count }}
+ in cart
+
+
+
+ +
+
+
+
+
+
+
+
+
Step actions
+
+
+ +5
+
+
+ Max
+
+
+ Reset
+
+
+
+ Values are clamped to {{ MIN }}–{{ MAX }} — try +5 near the top.
+
+
+
diff --git a/vue/toolkit/src/composables/state/useCycleList/demo.vue b/vue/toolkit/src/composables/state/useCycleList/demo.vue
new file mode 100644
index 0000000..b3a77c3
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useCycleList/demo.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ Accent
+
+ {{ position }}
+
+
+
+
+
+
+
+ {{ state.name }}
+ {{ state.hex }}
+
+
+
+
+
+
+ ← Prev
+
+
+ Next →
+
+
+
+
+
+
+
Jump to
+
+
+
+
+ Tabs call go(i) ; arrows wrap around with
+ next/prev .
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useDebouncedRefHistory/demo.vue b/vue/toolkit/src/composables/state/useDebouncedRefHistory/demo.vue
new file mode 100644
index 0000000..24285c0
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useDebouncedRefHistory/demo.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+ Tracked draft
+
+
+
+
+
+ Debounce delay
+ {{ debounce }}ms
+
+
+
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Clear
+
+
+
+
+
+ History
+
+ {{ history.length }} records
+
+
+
+
+ "{{ record.snapshot }}"
+
+ {{ i === 0 ? 'now' : formatTime(record.timestamp) }}
+
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useId/demo.vue b/vue/toolkit/src/composables/state/useId/demo.vue
new file mode 100644
index 0000000..c5ff319
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useId/demo.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+ Email
+
+
+
+ The label , input and this hint are
+ linked by generated ids — no hard-coded strings, no collisions.
+
+
+
+
+
+ field
+ {{ fieldId }}
+
+
+ help
+ {{ helpId }}
+
+
+
+
+ Deterministic override
+
+
+
+
+ Resolved
+ {{ resolvedId }}
+
+
+
diff --git a/vue/toolkit/src/composables/state/useInjectionStore/demo.vue b/vue/toolkit/src/composables/state/useInjectionStore/demo.vue
new file mode 100644
index 0000000..a6f3bed
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useInjectionStore/demo.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
Cart (provided here)
+
+
+
+
+
+
+
+ Add
+
+
+
+
+
+
+ The parent calls useProvidingState() ; the nested summary calls
+ useInjectedState() and reads the very same reactive cart.
+
+
+
diff --git a/vue/toolkit/src/composables/state/useLastChanged/demo.vue b/vue/toolkit/src/composables/state/useLastChanged/demo.vue
new file mode 100644
index 0000000..bda5dc7
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useLastChanged/demo.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+ Document title
+
+
+
+
+
+ Last edited
+
+
+ {{ lastChanged === null ? 'untouched' : 'edited' }}
+
+
+
+
{{ relative }}
+
+
+
+
+ Timestamp
+ {{ exactTime }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useManualRefHistory/demo.vue b/vue/toolkit/src/composables/state/useManualRefHistory/demo.vue
new file mode 100644
index 0000000..e0807f7
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useManualRefHistory/demo.vue
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+ Working value
+ hue {{ hue }}°
+
+
+
+
+
+
+
+ Commit snapshot
+
+
+
+
+
+ Undo
+
+
+ Redo
+
+
+ Reset
+
+
+ Clear
+
+
+
+
+
+ Committed snapshots
+
+ {{ history.length }}
+
+
+
+
+
+
+ {{ record.snapshot }}°
+
+
+ {{ i === 0 ? 'current' : formatTime(record.timestamp) }}
+
+
+
+
+
+
+ Unlike automatic history, nothing is recorded until you press
+ Commit snapshot .
+
+
+
diff --git a/vue/toolkit/src/composables/state/useOffsetPagination/demo.vue b/vue/toolkit/src/composables/state/useOffsetPagination/demo.vue
new file mode 100644
index 0000000..1f41534
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useOffsetPagination/demo.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+ Per page
+
+
+
+ {{ size }}
+
+
+
+
+
+
+
+
+ {{ user.id }}
+
+ {{ user.name }}
+
+ {{ user.role }}
+
+
+
+
+
+
+
+ {{ rangeStart }}–{{ rangeEnd }} of {{ total }}
+
+
+
+
+ ‹ Prev
+
+
+
+ {{ page }}
+
+
+
+ Next ›
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useRefHistory/demo.vue b/vue/toolkit/src/composables/state/useRefHistory/demo.vue
new file mode 100644
index 0000000..2961035
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useRefHistory/demo.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+ Tracked value
+
+
+
+
+
+
+
+ ↶ Undo
+
+
+ ↷ Redo
+
+
+
+ {{ isTracking ? '❚❚ Pause' : '▶ Resume' }}
+
+
+
+ Clear
+
+
+
+
+
+
+
+ {{ isTracking ? 'Tracking' : 'Paused' }}
+
+
+ {{ history.length }} record{{ history.length === 1 ? '' : 's' }}
+
+
+
+
+
+
+
+
+ {{ formatTime(record.timestamp) }}
+
+
+ {{ record.snapshot || '(empty)' }}
+
+ current
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useStepper/demo.vue b/vue/toolkit/src/composables/state/useStepper/demo.vue
new file mode 100644
index 0000000..4dd9781
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useStepper/demo.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+ ✓
+ {{ i + 1 }}
+
+
+
+
+
+
+
+
+
+ Step {{ index + 1 }} of {{ stepNames.length }}
+
+
+ {{ current.title }}
+
+
+ {{ current.hint }}
+
+
+
+
+
+
+ ‹ Back
+
+
+
+ {{ isLast ? 'Finished' : 'Continue ›' }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useThrottledRefHistory/demo.vue b/vue/toolkit/src/composables/state/useThrottledRefHistory/demo.vue
new file mode 100644
index 0000000..fd380a2
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useThrottledRefHistory/demo.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+ Live value
+
+ {{ value }}
+
+
+
+
+ Drag the slider — only one snapshot is committed per {{ throttle }}ms.
+
+
+
+
+
+
+ Throttle
+
+
+
+ {{ ms }}ms
+
+
+
+
+
+
+
+ ↶ Undo
+
+
+ ↷ Redo
+
+
+ Clear
+
+
+
+
+
+
+
+
+ {{ formatTime(record.timestamp) }}
+
+
+ {{ record.snapshot }}
+
+ latest
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/state/useToggle/demo.vue b/vue/toolkit/src/composables/state/useToggle/demo.vue
new file mode 100644
index 0000000..0f8772b
--- /dev/null
+++ b/vue/toolkit/src/composables/state/useToggle/demo.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+ Boolean toggle
+
+
+
+ Notifications
+
+
+
+
+
+
+
+ value:
+
+ {{ isOn }}
+
+
+
+
+
+
+
+ Custom values
+
+
+
+
+ {{ theme === 'dark' ? '☽' : '☀' }} {{ theme }}
+
+
+
+ Toggle theme
+
+
+
+
+
+ Set light
+
+
+ Set dark
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/storage/useLocalStorage/demo.vue b/vue/toolkit/src/composables/storage/useLocalStorage/demo.vue
new file mode 100644
index 0000000..334fb43
--- /dev/null
+++ b/vue/toolkit/src/composables/storage/useLocalStorage/demo.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Persisted settings
+
+ localStorage
+
+
+
+
+
+
{{ persistedJson }}
+
+
+ Edit anything, then reload the page or open a second tab — values stay in sync.
+
+
+
+ Reset to defaults
+
+
+
diff --git a/vue/toolkit/src/composables/storage/useSessionStorage/demo.vue b/vue/toolkit/src/composables/storage/useSessionStorage/demo.vue
new file mode 100644
index 0000000..186c5f7
--- /dev/null
+++ b/vue/toolkit/src/composables/storage/useSessionStorage/demo.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+ Step {{ step }} of {{ totalSteps }}
+
+
+ sessionStorage
+
+
+
+
+
+
+
+
+ Full name
+
+
+
+
+
+
+ Email
+
+
+
+
+
+
+ Subscribe to the newsletter
+
+
+
+
Name: {{ draft.name || '—' }}
+
Email: {{ draft.email || '—' }}
+
+
+
+
+
+
+ Back
+
+
+ Continue
+
+
+
+
+ Reload the page — your step and draft are restored. Closing the tab clears them.
+
+
+
+ Discard draft
+
+
+
diff --git a/vue/toolkit/src/composables/storage/useStorage/demo.vue b/vue/toolkit/src/composables/storage/useStorage/demo.vue
new file mode 100644
index 0000000..135f4b2
--- /dev/null
+++ b/vue/toolkit/src/composables/storage/useStorage/demo.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+ Custom storage backend
+
+ in-memory
+
+
+
+
+
Tags (Set)
+
+
+
+ {{ tag }}
+
+ ×
+
+
+ No tags yet
+
+
+
+
+
+
+
+ Ticket #
+ zero-padded serializer
+
+
+
+ −
+
+ {{ ticket }}
+
+ +
+
+
+
+
+
+
Raw store contents
+
+
+ {{ key }}
+ {{ value }}
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/storage/useStorageAsync/demo.vue b/vue/toolkit/src/composables/storage/useStorageAsync/demo.vue
new file mode 100644
index 0000000..8423aac
--- /dev/null
+++ b/vue/toolkit/src/composables/storage/useStorageAsync/demo.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+ Async preferences
+
+
+ {{ isReady ? 'ready' : 'loading…' }}
+
+
+
+
+
+
+
+
Theme
+
+
+ {{ t }}
+
+
+
+
+
+
Density
+
+
+ {{ d }}
+
+
+
+
+
+
+ {{ JSON.stringify(prefs) }}
+
+ {{ saving ? 'saving…' : 'saved' }}
+
+
+
+
+ Every change is written through the async backend with simulated latency.
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/createEventHook/demo.vue b/vue/toolkit/src/composables/utilities/createEventHook/demo.vue
new file mode 100644
index 0000000..015756a
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/createEventHook/demo.vue
@@ -0,0 +1,149 @@
+
+
+
+
+
+ Event hook
+
+ {{ listenerActive ? '1 listener' : 'no listeners' }}
+
+
+
+
+
+
+ {{ lvl }}
+
+
+
+
+
+
+ {{ listenerActive ? 'Detach listener (off)' : 'Attach listener (on)' }}
+
+
+
+
+ Received events
+
+ Clear
+
+
+
+
+
+ {{ listenerActive ? 'Trigger an event to see it here.' : 'Attach a listener, then trigger.' }}
+
+
+
+ {{ entry.level }}
+
+ {{ entry.message }}
+
+
+
+
+ Detach the listener and trigger again — with no listeners the payload is dropped.
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/get/demo.vue b/vue/toolkit/src/composables/utilities/get/demo.vue
new file mode 100644
index 0000000..58a66db
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/get/demo.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
unwrap a ref
+
+
+ {{ count }}
+
+
+
+
+
+
get(count)
+
{{ countValue }}
+
+
+
get(getter)
+
{{ doubledValue }}
+
+
+
+
+
read a single key
+
+
+ {{ k }}
+
+
+
+ get(user, '{{ selectedKey }}')
+ →
+ {{ JSON.stringify(keyedValue) }}
+
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/isDefined/demo.vue b/vue/toolkit/src/composables/utilities/isDefined/demo.vue
new file mode 100644
index 0000000..b991093
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/isDefined/demo.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+ useIsDefined(profile)
+
+
+ {{ ready ? 'defined' : 'nullish' }}
+
+
+
+
+
narrowed read
+
{{ greeting }}
+
+ {{ profile.email }}
+
+
+ Waiting for data…
+
+
+
+
+
+ Load profile
+
+
+ Clear
+
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/set/demo.vue b/vue/toolkit/src/composables/utilities/set/demo.vue
new file mode 100644
index 0000000..5a8ee86
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/set/demo.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+ set(volume, n)
+ {{ volume }}
+
+
+
+
+ −10
+
+
+ +10
+
+
+
+
+
+
+ set(profile, key, value)
+
+
+ {{ profile.status }}
+
+
+
{{ profile.name }}
+
+
+ {{ s }}
+
+
+
+ Set next name
+
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/useEventBus/demo.vue b/vue/toolkit/src/composables/utilities/useEventBus/demo.vue
new file mode 100644
index 0000000..999823e
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/useEventBus/demo.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+ Emit
+
+
+
+
+
received via bus
+
+
+ once()
+
+
+ reset()
+
+
+
+
+
+
+ No events yet — emit one above.
+
+
+
+ {{ entry.from }}
+
+ {{ entry.text }}
+
+ once
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/useMemoize/demo.vue b/vue/toolkit/src/composables/utilities/useMemoize/demo.vue
new file mode 100644
index 0000000..d8e3a0f
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/useMemoize/demo.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
resolver runs
+
{{ runs }}
+
+
+
cached keys
+
{{ cacheSize }}
+
+
+
+
+
+ fib(n)
+ key: {{ fib.generateKey(input) }}
+
+
+
+ {{ input }}
+
+
+
+
+
+ Compute
+
+
+ load (force)
+
+
+ delete(n)
+
+
+ clear
+
+
+
+
+
+ Compute a value — repeat the same n to see a cache hit.
+
+
+ fib({{ entry.n }})
+ =
+ {{ entry.value }}
+ {{ entry.computedAt }}
+
+ {{ entry.hit ? 'hit' : 'miss' }}
+
+
+
+
+
diff --git a/vue/toolkit/src/composables/utilities/useSupported/demo.vue b/vue/toolkit/src/composables/utilities/useSupported/demo.vue
new file mode 100644
index 0000000..9e438e6
--- /dev/null
+++ b/vue/toolkit/src/composables/utilities/useSupported/demo.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ Browser features
+
+ {{ supportedCount }} / {{ checks.length }} available
+
+
+
+
+
+ {{ check.name }}
+
+
+ Supported
+
+
+
+ Unavailable
+
+
+
+
+
+
+ Each check is SSR-safe: the result is false during
+ server render and resolves on the client after mount.
+
+
+
diff --git a/vue/toolkit/src/composables/watch/until/demo.vue b/vue/toolkit/src/composables/watch/until/demo.vue
new file mode 100644
index 0000000..6dd41e8
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/until/demo.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+ Live source
+
+ target = {{ TARGET }}
+
+
+
+
+
+ −
+
+ {{ count }}
+
+ +
+
+
+
+
+
+ await toBe({{ TARGET }})
+
+
+ changedTimes(3)
+
+
+
+
+
+
+ Promise log
+ {{ status }}
+
+
+ Press a button, then change the value above.
+
+
+ {{ line }}
+
+
+
+
+ Reset
+
+
+
diff --git a/vue/toolkit/src/composables/watch/watchDebounced/demo.vue b/vue/toolkit/src/composables/watch/watchDebounced/demo.vue
new file mode 100644
index 0000000..37eaf4d
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/watchDebounced/demo.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ Search
+
+
+
+
+
+
+ Debounce
+ {{ debounce }}ms
+
+
+
+
+
+ Settled query
+ {{ settledQuery || '—' }}
+
+
+
+
+
{{ keystrokes }}
+
keystrokes
+
+
+
{{ fetches }}
+
fetches
+
+
+
+ {{ savings() }}%
+
+
saved
+
+
+
+
+ Callback fires {{ debounce }}ms after you stop typing,
+ capped by a maxWait: 1500 ceiling.
+
+
+
diff --git a/vue/toolkit/src/composables/watch/watchIgnorable/demo.vue b/vue/toolkit/src/composables/watch/watchIgnorable/demo.vue
new file mode 100644
index 0000000..d43b8b1
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/watchIgnorable/demo.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+ Draft
+
+
+
+ {{ dirty ? 'Unsaved' : 'Saved' }}
+
+
+
+
+
+
+
+ Normalize (ignored)
+
+
+ Save
+
+
+
+
+
+ Tracked edits
+ {{ trackedEdits }}
+
+
+ Last change
+ {{ lastChange }}
+
+
+
+
+ Typing counts as an edit; Normalize writes the same
+ ref inside ignoreUpdates() and the watcher stays silent.
+
+
+
diff --git a/vue/toolkit/src/composables/watch/watchOnce/demo.vue b/vue/toolkit/src/composables/watch/watchOnce/demo.vue
new file mode 100644
index 0000000..a814661
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/watchOnce/demo.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+ Volume
+ {{ volume }}
+
+
+
+
+
+
+ First captured value
+
+ {{ firstTouch === null ? '—' : firstTouch }}
+
+
+
+
+ {{ armed ? 'Armed' : 'Spent' }}
+
+
+
+
+ Total drags
+ {{ totalChanges }}
+
+
+
+ Re-arm watcher
+
+
+
+ Drag the slider — only the first change is captured,
+ then the watcher stops. Total drags keeps climbing to prove it.
+
+
+
diff --git a/vue/toolkit/src/composables/watch/watchPausable/demo.vue b/vue/toolkit/src/composables/watch/watchPausable/demo.vue
new file mode 100644
index 0000000..e9332e2
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/watchPausable/demo.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+ Watched source
+
+
+
+
+
+
+
+ {{ stopped ? 'Stopped' : isActive ? 'Watching' : 'Paused' }}
+
+
+
+
+ Pause
+
+
+ Resume
+
+
+ Stop
+
+
+
+
+
+
+ Sync log
+
+
+
+ {{ entry.at }}
+ {{ entry.value || '—' }}
+
+
+
+ Edit the field above to record a sync.
+
+
+
+
+ While paused, source changes are ignored — no entry is logged until you resume.
+
+
+
diff --git a/vue/toolkit/src/composables/watch/watchThrottled/demo.vue b/vue/toolkit/src/composables/watch/watchThrottled/demo.vue
new file mode 100644
index 0000000..e9aad8e
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/watchThrottled/demo.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+ Live value
+
+ {{ volume }}
+
+
+
+ Drag rapidly — updates fire continuously, but the throttled callback only commits at most once per interval.
+
+
+
+
+
+
+ Saved (throttled)
+
+
+ {{ saved }}
+
+
+
+
+ Commits
+
+
+ {{ saveCount }}
+
+
+
+
+
+
+
+ Throttle interval
+
+ {{ throttle }} ms
+
+
+
+ The interval is reactive — change it and the next throttle window adapts immediately.
+
+
+
+
diff --git a/vue/toolkit/src/composables/watch/whenever/demo.vue b/vue/toolkit/src/composables/watch/whenever/demo.vue
new file mode 100644
index 0000000..f0640ff
--- /dev/null
+++ b/vue/toolkit/src/composables/watch/whenever/demo.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+ Access code
+
+
+
+ {{ code.length }} / 6 chars
+
+ {{ isValid ? 'valid' : 'too short' }}
+
+
+
+
+
+ Reached 12+ characters — this fired exactly once.
+
+
+
+
+ Became valid at
+
+
+
+ The callback only runs when the source turns truthy — type a valid code to log an event, then clear it and try again.
+
+
+
+
+ Unlike a plain watch, the callback skips every falsy transition.
+
+
+