@fastkit/vue-disabled-reason
🌐 English | 日本語
A library providing utility functions to define headless Vue.js components for monitoring disabled elements and displaying reasons.
Features
- Headless Design: Create components that handle only logic without providing UI
- Automatic Disabled State Detection: Automatically monitors
disabledandaria-disabledattributes - Flexible Display Control: Display reasons through slots or render functions
- Full TypeScript Support: Type safety with strict type definitions
- Advanced Customization: Support for custom VNode traversal handling
- Performance Optimization: Efficient element monitoring and rendering control
Installation
npm install @fastkit/vue-disabled-reason
# or
pnpm add @fastkit/vue-disabled-reasonRequired CSS Import
Before using the library, make sure to import the CSS file. This CSS sets position: relative on monitored elements, allowing the library to properly insert reason display elements.
// Import at the main entry point first
import '@fastkit/vue-disabled-reason/vue-disabled-reason.css';Or import in HTML:
<link rel="stylesheet" href="@fastkit/vue-disabled-reason/vue-disabled-reason.css">Basic Usage
Simple Disabled Reason Display
<template>
<div>
<!-- Wrap disabled button -->
<MyDisabledReason>
<button :disabled="hasNoPermission">
Execute Delete
</button>
<!-- Display disabled reason via slot -->
<template #reason>
Insufficient permissions
</template>
</MyDisabledReason>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { MyDisabledReason } from './components/MyDisabledReason.vue';
const hasNoPermission = ref(true);
</script>Reason Display with reason Property
You can also use the reason property instead of slots.
<template>
<div>
<!-- Specify reason with reason property -->
<MyDisabledReason reason="Administrator privileges required">
<button :disabled="!isAdmin">
System Settings
</button>
</MyDisabledReason>
<!-- Dynamic reason setting -->
<MyDisabledReason :reason="disableReason">
<button :disabled="isDisabled">
Delete Data
</button>
</MyDisabledReason>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const isAdmin = ref(false);
const hasDeletePermission = ref(false);
const disableReason = computed(() =>
hasDeletePermission.value ? null : 'Delete permission required'
);
</script>Custom Component Definition
import { defineDisabledReasonComponent } from '@fastkit/vue-disabled-reason';
import { VTooltip } from './components/VTooltip.vue';
export const MyDisabledReason = defineDisabledReasonComponent({
name: 'MyDisabledReason',
setup(api) {
return (reason) => (
<VTooltip
disabled={!reason || !api.disabled}
v-slots={{
activator: ({ attrs }) => <span {...attrs} />,
default: () => reason,
}}
/>
);
},
});Advanced Usage
Custom Container Specification
By default, reason display elements are inserted into the root element of the monitored target. You can customize the insertion position using DISABLED_REASON_CONTAINER_BIND.
<template>
<MyDisabledReason>
<div class="button-container">
<!-- Specify insertion position with DISABLED_REASON_CONTAINER_BIND -->
<div v-bind="DISABLED_REASON_CONTAINER_BIND">
<button :disabled="isDisabled">Change Settings</button>
</div>
<span>System Settings</span>
</div>
<template #reason>
No permission to change system settings
</template>
</MyDisabledReason>
</template>
<script setup lang="ts">
import { DISABLED_REASON_CONTAINER_BIND } from '@fastkit/vue-disabled-reason';
const isDisabled = ref(true);
</script>Individual Handling for Multiple Buttons
When there are multiple disableable elements, each must be wrapped with individual components.
<template>
<div>
<h3>Data Management</h3>
<div>
<!-- Edit button -->
<MyDisabledReason>
<div v-bind="DISABLED_REASON_CONTAINER_BIND">
<button :disabled="!canEdit">Edit</button>
</div>
<template #reason>
No edit permission
</template>
</MyDisabledReason>
<!-- Delete button -->
<MyDisabledReason>
<div v-bind="DISABLED_REASON_CONTAINER_BIND">
<button :disabled="!canDelete">Delete</button>
</div>
<template #reason>
No delete permission
</template>
</MyDisabledReason>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { DISABLED_REASON_CONTAINER_BIND } from '@fastkit/vue-disabled-reason';
const userRole = ref('viewer'); // 'admin', 'editor', 'viewer'
const canEdit = computed(() => ['admin', 'editor'].includes(userRole.value));
const canDelete = computed(() => userRole.value === 'admin');
</script>API Reference
defineDisabledReasonComponent
Defines a headless disabled reason component.
function defineDisabledReasonComponent<Props>(
options: DisabledReasonComponentOptions<Props>
): DefineComponentOptions:
name?: Component nameprops?: Additional property definitionsskipVNode?: VNode traversal skip handlersetup: Setup function that returns a render function
DisabledReasonAPI
API available within components.
interface DisabledReasonAPI<Props = {}> {
readonly props: ExtractPropTypes<Props>;
readonly disabled: boolean;
}DISABLED_REASON_CONTAINER_BIND
Bind object for customizing the insertion position of disabled reason display elements.
const DISABLED_REASON_CONTAINER_BIND: {
'data-disabled-reason-container': '';
}Usage:
<template>
<MyDisabledReason>
<!-- Disabled reason will be inserted within this element -->
<div v-bind="DISABLED_REASON_CONTAINER_BIND">
<button :disabled="isDisabled">Button</button>
</div>
<template #reason>
Reason text for being disabled
</template>
</MyDisabledReason>
</template>Notes:
- Only the first disableable element found within the specified container will be monitored
- When there are multiple disableable elements, wrap each with individual components
Monitored Elements
Standard Disableable Elements
<button><input>(all types)<textarea><select><fieldset><optgroup><option>
ARIA-Compatible Elements
Elements with the following roles that have aria-disabled attribute (including <a> tags):
buttonlinkmenuitemmenuitemcheckboxmenuitemradiooptionradiosliderspinbuttonswitchtabcheckboxgridcelltextbox
Performance Considerations
- Efficient Element Search: Minimal DOM traversal
- Rendering Optimization: Re-render only when disabled state changes
- Memory Management: Proper cleanup processing
- Event Optimization: Register event listeners only when necessary
Limitations
- Single Element Monitoring: One component instance monitors only the first disableable element found. For multiple elements, wrap each with individual components
- Dynamic Elements: Monitoring dynamically added elements requires remounting
- CSS Pseudo-classes: Cannot detect disabled states set by CSS pseudo-classes (
:disabled, etc.) - JavaScript Dynamic Changes: Dynamic disabled state changes via JavaScript are reflected after
nextTick
Related Packages
@fastkit/vue-utils- Vue.js utilities (dependency)vue- Vue.js framework (peer dependency)
License
MIT