A lightweight utility to manage readonly/disabled states for form controls based on CSS classes. Supports nested containers, exemptions, and Shadow DOM.
npm install fl-readonly-managerbash
npm install fl-readonly-manager
or
yarn add fl-readonly-manager
or
pnpm add fl-readonly-manager
`
$3
`html
`
$3
`html
`
$3
`javascript
import { ReadonlyManager } from 'fl-readonly-manager';
`
$3
`javascript
const { ReadonlyManager } = require('fl-readonly-manager');
`
$3
`html
`
$3
For direct browser usage without a bundler:
`html
`
---
Quick Start
$3
`html
`
$3
`html
`
$3
`javascript
// Toggle using CSS classes (observed automatically)
document.getElementById('myForm').classList.toggle('readonly');
// Or use the API
ReadonlyManager.setReadonly(document.getElementById('myForm'), true);
`
---
CSS Classes Reference
| Class | Target | Description |
|-------|--------|-------------|
| .canBeReadOnly | Any element | Marks element as controllable by this system |
| .readonly | Any element | Activates readonly/disabled state |
| .input-readonly | Any element | Alternative to .readonly (same behavior) |
| .ommitReadOnly | Any element | Exempts element and children from readonly |
| .fl-readonly-applied | Auto-added | Visual indicator (added automatically when readonly) |
---
Usage Examples
$3
All inputs inside become readonly/disabled:
`html
`
$3
Use .ommitReadOnly to keep sections editable:
`html
`
$3
Apply to a single element:
`html
`
$3
Exempt specific controls within a readonly container:
`html
`
$3
Handle complex nested structures:
`html
`
$3
`html
`
$3
`html
`
$3
Apply visual themes to readonly elements:
`javascript
// Enable themes
ReadonlyManager.configure({ THEME: { enabled: true } });
// Set theme (default, subtle, greyed, striped, disabled, bordered)
ReadonlyManager.setTheme('greyed');
`
$3
Control field editability based on user roles:
`html
`
`javascript
ReadonlyManager.configure({ RBAC: { enabled: true } });
ReadonlyManager.setRole('admin');
`
$3
Set readonly on multiple elements at once:
`javascript
// Using CSS selector
await ReadonlyManager.setReadonlyBulk('.form-fields', true);
// Using NodeList
await ReadonlyManager.setReadonlyBulk(document.querySelectorAll('input'), false);
// With progress callback
await ReadonlyManager.setReadonlyBulk('.fields', true, {
onProgress: (current, total) => console.log(${current}/${total})
});
`
$3
Time-based readonly scheduling:
`javascript
const element = document.getElementById('scheduledField');
// Schedule readonly during specific hours
ReadonlyManager.scheduleReadonly(element, {
startTime: new Date('2026-01-15T09:00:00'), // Go readonly
endTime: new Date('2026-01-15T17:00:00') // Go editable
});
// Cancel scheduled task
ReadonlyManager.cancelSchedule(element);
`
$3
Lifecycle hooks for readonly changes:
`javascript
// Listen for readonly state changes
ReadonlyManager.events.on('afterReadonly', (data) => {
console.log(${data.element.id} is now ${data.readonly ? 'readonly' : 'editable'});
});
// Prevent readonly change
ReadonlyManager.events.on('beforeReadonly', (data) => {
if (data.element.id === 'protectedField') {
return false; // Cancel the change
}
});
`
$3
Show reasons why elements are readonly:
`javascript
// Enable tooltips
ReadonlyManager.configure({ TOOLTIP: { enabled: true } });
// Set reason for an element
const field = document.getElementById('lockedField');
field.setAttribute('data-readonly-reason', 'Locked by admin');
ReadonlyManager.tooltip.attach(field);
`
$3
Animate state transitions:
`javascript
ReadonlyManager.configure({
ANIMATION: {
enabled: true,
duration: 300,
easing: 'ease-in-out'
}
});
// Transitions will now be animated
ReadonlyManager.setReadonly(element, true);
`
$3
Save and restore readonly states:
`javascript
// Save state to localStorage
ReadonlyManager.persistence.save(element);
// Load saved state
ReadonlyManager.persistence.load(element);
// Clear saved state
ReadonlyManager.persistence.clear(element);
`
$3
Form validation that respects readonly fields:
`javascript
const form = document.getElementById('myForm');
// Get only editable fields for validation
const editableFields = ReadonlyManager.validation.getEditableFields(form);
// Skip readonly fields in validation
editableFields.forEach(field => {
if (ReadonlyManager.validation.shouldValidate(field)) {
// Perform validation
}
});
`
---
JavaScript API
$3
Initialize the readonly manager. Called automatically on page load.
`javascript
ReadonlyManager.init();
`
$3
Manually re-process all readonly controls. Useful after dynamic DOM changes.
`javascript
// Refresh entire document
ReadonlyManager.refresh();
// Refresh specific container
ReadonlyManager.refresh(document.getElementById('myForm'));
`
$3
Programmatically set readonly state on an element.
`javascript
const form = document.getElementById('myForm');
// Make readonly
ReadonlyManager.setReadonly(form, true);
// Make editable
ReadonlyManager.setReadonly(form, false);
`
$3
Check if an element is currently readonly.
`javascript
const form = document.getElementById('myForm');
if (ReadonlyManager.isReadonly(form)) {
console.log('Form is readonly');
}
`
$3
Disconnect the observer and stop watching for changes.
`javascript
// Clean up when done (e.g., SPA navigation)
ReadonlyManager.destroy();
`
$3
Update configuration options at runtime.
`javascript
ReadonlyManager.configure({
DEBOUNCE_MS: 50, // Faster response
DEBUG: true, // Enable logging
TRAVERSE_SHADOW_DOM: true // Enable Shadow DOM support
});
`
$3
Get current configuration.
`javascript
const config = ReadonlyManager.getConfig();
console.log(config.DEBOUNCE_MS); // 100
`
$3
Access the logger instance for debugging.
`javascript
// Enable debug logging
ReadonlyManager.logger.enable();
ReadonlyManager.logger.setLevel(0); // DEBUG level
// Disable logging
ReadonlyManager.logger.disable();
`
$3
Check browser feature support.
`javascript
const features = ReadonlyManager.features;
if (features.shadowDOM) {
console.log('Shadow DOM is supported');
}
if (features.mutationObserver) {
console.log('Auto-updates are available');
}
`
---
Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| EDITABLE_SELECTOR | string | 'input, select, textarea, button, [contenteditable="true"]' | CSS selector for editable controls |
| CONTAINER_SELECTOR | string | 'div, section, span, ...' | CSS selector for container elements |
| CLASSES.CAN_BE_READONLY | string | 'canBeReadOnly' | Class marking controllable elements |
| CLASSES.READONLY | string | 'readonly' | Class activating readonly state |
| CLASSES.INPUT_READONLY | string | 'input-readonly' | Alternative readonly class |
| CLASSES.OMMIT_READONLY | string | 'ommitReadOnly' | Class for exempted elements |
| CLASSES.READONLY_APPLIED | string | 'fl-readonly-applied' | Visual indicator class |
| DEBOUNCE_MS | number | 100 | Debounce delay for observer (ms) |
| DEBUG | boolean | false | Enable debug logging |
| TRAVERSE_SHADOW_DOM | boolean | true | Enable Shadow DOM traversal |
$3
`javascript
ReadonlyManager.configure({
DEBOUNCE_MS: 50,
DEBUG: true,
CLASSES: {
CAN_BE_READONLY: 'my-readonly-container',
READONLY: 'is-locked',
OMMIT_READONLY: 'keep-editable'
}
});
`
---
Control Behavior Reference
| Element Type | Readonly Behavior |
|--------------|-------------------|
| input[type="text"] | readonly="readonly" |
| input[type="password"] | readonly="readonly" |
| input[type="email"] | readonly="readonly" |
| input[type="number"] | readonly="readonly" |
| input[type="tel"] | readonly="readonly" |
| input[type="url"] | readonly="readonly" |
| input[type="search"] | readonly="readonly" |
| input[type="date"] | readonly="readonly" |
| input[type="time"] | readonly="readonly" |
| input[type="datetime-local"] | readonly="readonly" |
| input[type="month"] | readonly="readonly" |
| input[type="week"] | readonly="readonly" |
| textarea | readonly="readonly" |
| input[type="checkbox"] | disabled |
| input[type="radio"] | disabled |
| input[type="file"] | disabled |
| input[type="color"] | disabled |
| input[type="range"] | disabled |
| input[type="submit"] | disabled |
| input[type="reset"] | disabled |
| input[type="button"] | disabled |
| select | disabled |
| button | disabled |
| [contenteditable] | contenteditable="false" |
---
Shadow DOM Support
The manager can traverse into Shadow DOM to process elements inside web components.
$3
`javascript
ReadonlyManager.configure({ TRAVERSE_SHADOW_DOM: true });
`
$3
`html
`
Both light-input and shadow-input will be processed.
---
Logger Utility
Built-in logger for debugging with enable/disable control.
`javascript
// Access logger
const logger = ReadonlyManager.logger;
// Enable logging
logger.enable();
// Set log level (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)
logger.setLevel(0);
// Log messages (when enabled)
logger.debug('Detailed info');
logger.info('General info');
logger.warn('Warning');
logger.error('Error');
// Disable for production
logger.disable();
`
$3
`javascript
import { LogLevel } from 'fl-readonly-manager';
ReadonlyManager.logger.setLevel(LogLevel.DEBUG); // 0
ReadonlyManager.logger.setLevel(LogLevel.INFO); // 1
ReadonlyManager.logger.setLevel(LogLevel.WARN); // 2
ReadonlyManager.logger.setLevel(LogLevel.ERROR); // 3
`
---
Browser Compatibility
| Browser | Version | Notes |
|---------|---------|-------|
| Chrome | 54+ | Full support |
| Firefox | 63+ | Full support |
| Safari | 10.1+ | Full support |
| Edge | 79+ | Full support (Chromium) |
| Edge Legacy | 15+ | No Shadow DOM |
| IE 11 | â | Not supported |
$3
`javascript
const features = ReadonlyManager.features;
if (!features.mutationObserver) {
console.warn('Auto-updates not available. Call refresh() manually.');
}
if (!features.shadowDOM) {
console.warn('Shadow DOM not supported. Web components will not be traversed.');
}
`
---
TypeScript Usage
Full TypeScript support with type definitions included.
$3
`typescript
import {
ReadonlyManager,
ReadonlyManagerConfig,
ILogger,
LogLevel,
FeatureSupport
} from 'fl-readonly-manager';
`
$3
`typescript
import { ReadonlyManagerConfig } from 'fl-readonly-manager';
const config: Partial = {
DEBOUNCE_MS: 50,
DEBUG: true
};
ReadonlyManager.configure(config);
`
$3
`typescript
import { ReadonlyManagerCore } from 'fl-readonly-manager';
const manager = new ReadonlyManagerCore({
DEBOUNCE_MS: 50,
TRAVERSE_SHADOW_DOM: true
});
manager.init();
manager.handleReadonlyControls(document.body);
`
---
Migration from v1
$3
1. Function renamed: FL_HandleReadonlyControls() is now ReadonlyManager.refresh()
2. Configuration: Settings now use ReadonlyManager.configure() instead of global variables
3. Logger: Console logging now requires enabling ReadonlyManager.logger.enable()
$3
`javascript
// v1 (old)
FL_HandleReadonlyControls();
// v2 (new)
ReadonlyManager.refresh();
// v1 (old) - no configuration API
// v2 (new)
ReadonlyManager.configure({ DEBOUNCE_MS: 50 });
`
$3
The legacy function is still available for backward compatibility:
`javascript
// Still works in v2
FL_HandleReadonlyControls();
`
---
Contributing
1. Fork the repository
2. Create a feature branch: git checkout -b feature/my-feature
3. Make your changes
4. Run tests: npm test
5. Submit a pull request
$3
`bash
Install dependencies
npm install
Run in watch mode
npm run dev
Run tests
npm test
Run tests with coverage
npm run test:coverage
Build for production
npm run build
Lint code
npm run lint
``