šŸš€ Big News:Socket Has Acquired Secure Annex.Learn More →
Socket
Book a DemoSign in
Socket

fl-readonly-manager

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fl-readonly-manager

A lightweight utility to manage readonly/disabled states for form controls based on CSS classes. Supports nested containers, exemptions, and Shadow DOM.

latest
Source
npmnpm
Version
3.1.0
Version published
Maintainers
1
Created
Source

FL Readonly Manager

A lightweight JavaScript/TypeScript utility to manage readonly/disabled states for form controls based on CSS classes. Supports nested containers with granular control, Shadow DOM traversal, and automatic DOM observation.

npm version License TypeScript Bundle Size

Table of Contents

Features

Core Features

  • šŸŽÆ CSS Class-Based Control - Toggle readonly/disabled states using simple CSS classes
  • šŸ“¦ Nested Container Support - Handle complex nested structures with proper inheritance
  • 🚫 Granular Exemptions - Exempt specific elements or sections from readonly behavior
  • šŸ‘ļø Automatic Observation - Uses MutationObserver to detect class changes automatically
  • šŸŒ‘ Shadow DOM Support - Traverse into web components and shadow roots
  • šŸ“ TypeScript Ready - Full type definitions included
  • šŸ”§ Configurable - Customize selectors, debounce timing, and behavior
  • 🪶 Modular & dependency-free - No runtime dependencies; ~42KB minified with all managers enabled (feature managers are opt-in)
  • ♿ Accessibility - Properly sets readonly/disabled attributes for screen readers

New in v3.0.0

  • šŸŽØ Theme Manager - Visual themes for readonly elements (subtle, greyed, striped, disabled, bordered)
  • šŸ” RBAC Manager - Role-based access control with data attributes
  • 🧭 CRUD Page Guardrail - Reusable CRUD page mode integration for shared views
  • ⚔ Bulk Operations - Set readonly on multiple elements via selector or array
  • ā° Scheduler Manager - Time-based readonly scheduling
  • šŸ’¬ Tooltip Manager - Show reasons why elements are readonly
  • ✨ Animation Manager - Animated state transitions
  • šŸŽÆ Event Manager - Lifecycle hooks (beforeReadonly, afterReadonly)
  • šŸ’¾ Persistence Manager - Save/restore states to localStorage
  • āœ… Validation Manager - Form validation that skips readonly fields

New in v3.1.0

  • šŸ’¾ Auto-Save Detection - Track form changes and prevent data loss with beforeunload warnings
  • ā®ļø Undo/Redo Stack - Full history management with keyboard shortcuts (Ctrl+Z/Ctrl+Y)
  • šŸ“Š Form Diff Viewer - Visual diff tracking to highlight changes with color-coded indicators
  • āŒØļø Keyboard Shortcuts - Power-user shortcuts for toggling readonly states and navigation
  • šŸ“‹ Smart Field Copying - One-click copy buttons for readonly fields
  • šŸŽÆ Conditional Readonly - Dynamic readonly rules based on expressions or functions
  • šŸŽØ Readonly Presets - Pre-configured profiles for common scenarios (view/edit modes)
  • šŸ”— Smart Dependencies - Automatic field dependencies with cascading readonly states
  • ✨ Auto-Format Values - 10+ formatters (date, currency, phone, SSN, credit card, etc.)
  • šŸ“¦ Field Groups - Manage related fields as cohesive units

Installation

NPM / Yarn

npm install fl-readonly-manager
# or
yarn add fl-readonly-manager
# or
pnpm add fl-readonly-manager

Package: www.npmjs.com/package/fl-readonly-manager

CDN (unpkg)

<!-- Minified (recommended for production) -->
<script src="https://unpkg.com/fl-readonly-manager@3.1.0/dist/readonly.min.js"></script>

<!-- Full version (for debugging) -->
<script src="https://unpkg.com/fl-readonly-manager@3.1.0/dist/readonly.js"></script>

<!-- Latest version (auto-updates with new releases) -->
<script src="https://unpkg.com/fl-readonly-manager/dist/readonly.min.js"></script>

CDN (jsDelivr)

<!-- Minified -->
<script src="https://cdn.jsdelivr.net/npm/fl-readonly-manager@3.1.0/dist/readonly.min.js"></script>

<!-- Full version -->
<script src="https://cdn.jsdelivr.net/npm/fl-readonly-manager@3.1.0/dist/readonly.js"></script>

<!-- Latest -->
<script src="https://cdn.jsdelivr.net/npm/fl-readonly-manager/dist/readonly.min.js"></script>

ES Module

import { ReadonlyManager } from 'fl-readonly-manager';

CommonJS

const { ReadonlyManager } = require('fl-readonly-manager');

ES Module via CDN

<script type="module">
  import { ReadonlyManager } from 'https://unpkg.com/fl-readonly-manager@3.1.0/dist/index.mjs';
  
  ReadonlyManager.init();
</script>

Browser (IIFE)

For direct browser usage without a bundler:

<!-- Minified (17 KB) -->
<script src="https://unpkg.com/fl-readonly-manager/dist/readonly.min.js"></script>
<!-- Or full version (40 KB) -->
<!-- <script src="https://unpkg.com/fl-readonly-manager/dist/readonly.js"></script> -->
<script>
  // Library exposes ReadonlyManagerLib global
  const { ReadonlyManager, FL_Logger } = ReadonlyManagerLib;
  
  document.addEventListener('DOMContentLoaded', () => {
    ReadonlyManager.init();

    const controller = ReadonlyManager.createCrudController(document, {
      pageSelector: '.crud-page',
      modeAttribute: 'data-crud-mode',
      autoRefresh: true
    });

    controller.setMode('edit');
  });
</script>

Quick Start

1. Add CSS Classes to Your HTML

<!-- Container with all children readonly -->
<div class="canBeReadOnly readonly">
  <input type="text" name="field1">    <!-- Will be readonly -->
  <select name="field2">               <!-- Will be disabled -->
    <option>Option 1</option>
  </select>
  <button type="button">Click</button> <!-- Will be disabled -->
</div>

2. Include the Script

<!-- Include from CDN -->
<script src="https://unpkg.com/fl-readonly-manager/dist/readonly.min.js"></script>
<script>
  // Extract ReadonlyManager from global and initialize
  const { ReadonlyManager } = ReadonlyManagerLib;
  document.addEventListener('DOMContentLoaded', () => {
    ReadonlyManager.init();
  });
</script>

3. Toggle Readonly Dynamically

// 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

ClassTargetDescription
.canBeReadOnlyAny elementMarks element as controllable by this system
.readonlyAny elementActivates readonly/disabled state
.input-readonlyAny elementAlternative to .readonly (same behavior)
.ommitReadOnlyAny elementExempts element and children from readonly
.fl-readonly-appliedAuto-addedVisual indicator (added automatically when readonly)
.crud-pagePage containerGuardrail class for shared CRUD views
.crud-viewPage containerPage is in readonly/view mode
.crud-editPage containerPage is in editable/edit mode
.crud-createPage containerPage is in create mode
.crud-deletePage containerPage is in delete mode

CRUD Page Guardrails

Use createCrudPageReadonlyController, useCrudReadonly, and applyCrudReadonlyGuardrails to keep shared CRUD page views consistent across view/edit/create/delete states.

  • data-crud-mode controls the current page state.
  • crud-page marks the page container for guardrail policy.
  • readonly is applied automatically in view/delete/readonly modes.
  • canBeReadOnly ensures the readonly manager manages the page container.

This makes the readonly manager reusable across all CRUD operation pages that share the same page view.

Usage Examples

Example 1: Basic Container

All inputs inside become readonly/disabled:

<div class="canBeReadOnly readonly">
  <input type="text" name="name">        <!-- readonly="readonly" -->
  <input type="email" name="email">      <!-- readonly="readonly" -->
  <select name="country">                <!-- disabled -->
    <option>USA</option>
  </select>
  <button type="submit">Submit</button>  <!-- disabled -->
</div>

Example 2: Container with Exempted Section

Use .ommitReadOnly to keep sections editable:

<div class="canBeReadOnly readonly">
  <input type="text" name="locked">      <!-- readonly -->
  
  <div class="ommitReadOnly">
    <input type="text" name="editable">  <!-- stays editable! -->
    <button type="button">Action</button><!-- stays enabled! -->
  </div>
  
  <input type="text" name="also-locked"> <!-- readonly -->
</div>

Example 3: Individual Control

Apply to a single element:

<input type="text" class="canBeReadOnly readonly" name="single">
<!-- This single input will be readonly -->

Example 4: Exempted Individual Control

Exempt specific controls within a readonly container:

<div class="canBeReadOnly readonly">
  <input type="text" name="locked">                        <!-- readonly -->
  <input type="text" name="exempt" class="ommitReadOnly">  <!-- editable! -->
  <input type="text" name="also-locked">                   <!-- readonly -->
</div>

Example 5: Nested Containers

Handle complex nested structures:

<div class="canBeReadOnly readonly">
  <input type="text" name="outer">         <!-- readonly -->
  
  <div class="canBeReadOnly ommitReadOnly">
    <input type="text" name="inner1">      <!-- editable (parent has ommitReadOnly) -->
    
    <div class="canBeReadOnly readonly">
      <input type="text" name="inner2">    <!-- readonly (own container is readonly) -->
    </div>
  </div>
</div>

Example 6: Dynamic Toggle with JavaScript

<div id="myForm" class="canBeReadOnly">
  <input type="text" name="field1">
  <input type="text" name="field2">
</div>

<button onclick="toggleReadonly()">Toggle Readonly</button>

<script>
  // Method 1: Toggle CSS class (MutationObserver handles the rest)
  function toggleReadonly() {
    const form = document.getElementById('myForm');
    form.classList.toggle('readonly');
  }
  
  // Method 2: Use the API
  function toggleReadonlyAPI() {
    const form = document.getElementById('myForm');
    const isReadonly = ReadonlyManager.isReadonly(form);
    ReadonlyManager.setReadonly(form, !isReadonly);
  }
</script>

Example 7: Complete Form with Mixed Controls

<form class="canBeReadOnly readonly">
  <!-- Text inputs: readonly="readonly" -->
  <input type="text" name="name">
  <input type="email" name="email">
  <input type="tel" name="phone">
  <input type="url" name="website">
  <input type="password" name="password">
  <input type="number" name="age">
  <input type="date" name="birthdate">
  
  <!-- Textarea: readonly="readonly" -->
  <textarea name="bio"></textarea>
  
  <!-- Special inputs: disabled -->
  <input type="checkbox" name="agree">
  <input type="radio" name="choice" value="1">
  <input type="radio" name="choice" value="2">
  <input type="file" name="avatar">
  <input type="color" name="theme">
  <input type="range" name="volume">
  
  <!-- Select and buttons: disabled -->
  <select name="country">
    <option>USA</option>
    <option>Canada</option>
  </select>
  <button type="submit">Submit</button>
  <button type="reset">Reset</button>
  
  <!-- Contenteditable: contenteditable="false" -->
  <div contenteditable="true" name="richtext">Edit me</div>
</form>

Example 8: Theme Manager (v3.0.0)

Apply visual themes to readonly elements:

// Enable themes
ReadonlyManager.configure({ THEME: { enabled: true } });

// Set theme (default, subtle, greyed, striped, disabled, bordered)
ReadonlyManager.setTheme('greyed');

Example 9: RBAC - Role-Based Access Control (v3.0.0)

Control field editability based on user roles:

<input data-editable-roles="admin,editor" value="Admin/Editor only">
<input data-readonly-for-roles="viewer" value="Readonly for viewers">
ReadonlyManager.configure({ RBAC: { enabled: true } });
ReadonlyManager.setRole('admin');

Example 10: CRUD Page Guardrail + Shared View Hook

Use the same page view for shared CRUD operations and let the manager enforce page mode state.

<div class="crud-page canBeReadOnly" data-crud-mode="view">
  <form>
    <input type="text" name="name">
    <button type="submit">Save</button>
  </form>
</div>
import { ReadonlyManager, createCrudPageReadonlyController } from 'fl-readonly-manager';

const controller = createCrudPageReadonlyController(document, {
  pageSelector: '.crud-page',
  modeAttribute: 'data-crud-mode',
  autoRefresh: true
});

// Switch page mode in shared CRUD views
controller.setMode('edit');
controller.setMode('view');
const hook = ReadonlyManager.useCrudReadonly(document, {
  pageSelector: '.crud-page',
  autoRefresh: true
});

hook.setMode('create');

Example 12: Blazor Page Integration (v3.0.0)

Integrate fl-readonly-manager in a Blazor page using JS interop.

<!-- _Host.cshtml or wwwroot/index.html -->
<script src="https://unpkg.com/fl-readonly-manager/dist/readonly.min.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    ReadonlyManager.init();

    const controller = ReadonlyManager.createCrudController(document, {
      pageSelector: '.crud-page',
      modeAttribute: 'data-crud-mode',
      autoRefresh: true
    });

    window.FlReadonlyCrud = {
      setMode: (mode) => controller.setMode(mode),
      applyGuardrails: () => ReadonlyManager.applyCrudReadonlyGuardrails(document)
    };
  });
</script>
@page "/crud"
@inject IJSRuntime JS

<div class="crud-page canBeReadOnly" data-crud-mode="view">
  <form>
    <input type="text" name="name" placeholder="Name" />
    <input type="email" name="email" placeholder="Email" />
    <button type="submit">Save</button>
  </form>

  <button @onclick="() => SetModeAsync('view')">View</button>
  <button @onclick="() => SetModeAsync('edit')">Edit</button>
  <button @onclick="() => SetModeAsync('create')">Create</button>
  <button @onclick="() => SetModeAsync('delete')">Delete</button>
</div>

@code {
  private async Task SetModeAsync(string mode)
  {
    await JS.InvokeVoidAsync("FlReadonlyCrud.setMode", mode);
  }
}

Example 12: Bulk Operations (v3.0.0)

Set readonly on multiple elements at once:

// 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}`)
});

Example 13: Scheduler Manager (v3.0.0)

Time-based readonly scheduling:

const element = document.getElementById('scheduledField');

// Schedule readonly during specific hours
const taskId = 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(taskId);

Example 14: Event Manager (v3.0.0)

Lifecycle hooks for readonly changes:

// Listen for readonly state changes
ReadonlyManager.events.on('afterReadonly', (data) => {
  console.log(`${data.element.id} is now ${data.isReadonly ? 'readonly' : 'editable'}`);
});

// Prevent readonly change
ReadonlyManager.events.on('beforeReadonly', (data) => {
  if (data.element.id === 'protectedField') {
    return false; // Cancel the change
  }
});

Example 15: Tooltip Manager (v3.0.0)

Show reasons why elements are readonly:

// 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);

Example 16: Animation Manager (v3.0.0)

Animate state transitions:

ReadonlyManager.configure({ 
  ANIMATION: { 
    enabled: true,
    duration: 300,
    easing: 'ease-in-out'
  } 
});

// Transitions will now be animated
ReadonlyManager.setReadonly(element, true);

Example 17: Persistence Manager (v3.0.0)

Save and restore readonly states:

// Save state to localStorage
ReadonlyManager.persistence.save(element);

// Load saved state
ReadonlyManager.persistence.load(element);

// Clear saved state
ReadonlyManager.persistence.clear(element);

Example 18: Validation Manager (v3.0.0)

Form validation that respects readonly fields:

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
  }
});

Example 17: Auto-Save Detection (v3.1.0)

Track form changes and prevent data loss:

// Get auto-save manager
const autoSave = ReadonlyManager.autoSave;

// Configure and initialize form
autoSave.configure({
  enabled: true,
  detectChanges: true,
  warnBeforeUnload: true,
  onDirtyChange: (form, isDirty) => {
    console.log(`Form is ${isDirty ? 'dirty' : 'clean'}`);
  }
});
autoSave.initializeForm(form);

// Check if form is dirty
if (autoSave.isDirty(form)) {
  console.log('Form has unsaved changes');
}

// Get changed fields (returns array of Elements)
const changedFields = autoSave.getChangedFields(form);
const originalValues = autoSave.getOriginalValues(form);

changedFields.forEach(field => {
  const name = field.name || field.id;
  console.log(`${name}: "${originalValues[name]}" → "${field.value}"`);
});

// Reset form to original values
autoSave.resetForm(form);

// Mark as clean after save
autoSave.markClean(form);

Example 18: Undo/Redo Stack (v3.1.0)

History management for readonly state changes (not field values):

// Get history manager
const history = ReadonlyManager.history;

// Configure with keyboard shortcuts
history.configure({
  enabled: true,
  maxSize: 50,
  shortcuts: { undo: 'Ctrl+Z', redo: 'Ctrl+Y' },
  onUndo: (entry) => console.log('Undid:', entry.action),
  onRedo: (entry) => console.log('Redid:', entry.action)
});

// Record a readonly state change
history.record({
  action: 'makeReadonly',
  elements: [{ selector: '#myField', id: 'myField' }],
  previousState: [false],
  newState: [true],
  source: 'user'
});

// Undo (or press Ctrl+Z) - restores previous readonly state
history.undo();

// Redo (or press Ctrl+Y) - reapplies readonly state
history.redo();

// Check if can undo/redo
if (history.canUndo()) {
  console.log(`Can undo ${history.getHistory().length} actions`);
}
if (history.canRedo()) {
  console.log(`Can redo ${history.getRedoStack().length} actions`);
}

// Clear history
history.clear();

Example 19: Form Diff Viewer (v3.1.0)

Visual diff tracking to highlight changes:

// Get diff tracker manager
const diffTracker = ReadonlyManager.diffTracker;

// Configure
diffTracker.configure({
  enabled: true,
  highlightChanges: true,
  showDiffIndicator: true
});

// Take snapshot before changes
diffTracker.takeSnapshot(form);

// Make changes to form...

// Get diff (returns object with fields property)
const diff = diffTracker.getDiff(form);

let totalChanges = 0;
Object.entries(diff.fields).forEach(([name, fieldDiff]) => {
  if (fieldDiff.changed) {
    totalChanges++;
    console.log(`${name}: "${fieldDiff.before}" → "${fieldDiff.after}"`);
  }
});

console.log(`${totalChanges} fields changed`);

// Show visual diff with highlighting
diffTracker.showDiff(form);

// Export diff as JSON
const diffJson = diffTracker.exportDiff(form);

// Reset diff and clear highlights
diffTracker.resetDiff(form);

Example 20: Keyboard Shortcuts (v3.1.0)

Power-user shortcuts for productivity:

// Enable shortcuts
ReadonlyManager.configure({ SHORTCUTS: { enabled: true } });

// Register custom shortcut
ReadonlyManager.shortcuts.register('Ctrl+E', () => {
  // Toggle edit mode
  document.querySelector('.form-container').classList.toggle('readonly');
}, {
  description: 'Toggle edit mode'
});

// Built-in shortcuts work automatically:
// Ctrl+Shift+L - Toggle readonly on focused field
// Ctrl+Shift+A - Toggle all fields

Example 21: Smart Forms - Conditional Readonly (v3.1.0)

Dynamic readonly rules based on conditions:

// Enable smart forms
ReadonlyManager.configure({ SMART_FORMS: { enabled: true } });

// Add conditional rule
ReadonlyManager.smartForms.addConditionalRule({
  selector: '#billingAddress',
  condition: (context) => context.sameAsShipping === true,
  readonly: true
});

// Update context to trigger rules
ReadonlyManager.smartForms.updateContext({ sameAsShipping: true });

Example 22: Auto-Format Values (v3.1.0)

Formatters for common data types (requires value storage for round-trip):

// Enable smart forms with auto-format
const smartForms = ReadonlyManager.smartForms;
smartForms.enable({ autoFormat: true });

// Store original values for round-trip formatting
const originalValues = new Map();

// Format phone number
const phoneField = document.getElementById('phone');
originalValues.set(phoneField, phoneField.value);
smartForms.setFormat(phoneField, { type: 'phone' });

// Format currency
const priceField = document.getElementById('price');
originalValues.set(priceField, priceField.value);
smartForms.setFormat(priceField, { 
  type: 'currency', 
  locale: 'en-US'
});

// Format date
const dateField = document.getElementById('date');
originalValues.set(dateField, dateField.value);
smartForms.setFormat(dateField, { 
  type: 'date', 
  locale: 'en-US' 
});

// Apply formats when field becomes readonly
function onReadonlyChange(field) {
  if (field.readOnly) {
    // Store value before formatting if not already stored
    if (!originalValues.has(field)) {
      originalValues.set(field, field.value);
    }
    smartForms.applyFormatToField(field);
  } else {
    // Restore original value when editable
    if (originalValues.has(field)) {
      field.value = originalValues.get(field);
    }
  }
}

// Available formatters: date, datetime, currency, phone, number, percent, custom
// Note: Formatters automatically handle already-formatted values (remove $, commas, etc.)

Example 23: Field Groups (v3.1.0)

Manage related fields as cohesive units:

// Enable smart forms with groups
const smartForms = ReadonlyManager.smartForms;
smartForms.enable({ groups: true });

// Add field group
smartForms.addGroup({
  name: 'personal-info',
  selector: '[data-group="personal"]'
});

// Set entire group readonly
smartForms.setGroupReadonly('personal-info', true);

// Get all groups
const groups = smartForms.getGroups();
console.log(`Registered groups: ${groups.map(g => g.name).join(', ')}`);

// Remove a group
smartForms.removeGroup('personal-info');

JavaScript API

ReadonlyManager.init()

Initialize the readonly manager. Called automatically on page load.

ReadonlyManager.init();

ReadonlyManager.refresh(rootElement?)

Manually re-process all readonly controls. Useful after dynamic DOM changes.

// Refresh entire document
ReadonlyManager.refresh();

// Refresh specific container
ReadonlyManager.refresh(document.getElementById('myForm'));

ReadonlyManager.setReadonly(element, readonly)

Programmatically set readonly state on an element.

const form = document.getElementById('myForm');

// Make readonly
ReadonlyManager.setReadonly(form, true);

// Make editable
ReadonlyManager.setReadonly(form, false);

ReadonlyManager.isReadonly(element)

Check if an element is currently readonly.

const form = document.getElementById('myForm');

if (ReadonlyManager.isReadonly(form)) {
  console.log('Form is readonly');
}

ReadonlyManager.destroy()

Disconnect the observer and stop watching for changes.

// Clean up when done (e.g., SPA navigation)
ReadonlyManager.destroy();

ReadonlyManager.configure(options)

Update configuration options at runtime.

ReadonlyManager.configure({
  DEBOUNCE_MS: 50,           // Faster response
  DEBUG: true,               // Enable logging
  TRAVERSE_SHADOW_DOM: true  // Enable Shadow DOM support
});

ReadonlyManager.getConfig()

Get current configuration.

const config = ReadonlyManager.getConfig();
console.log(config.DEBOUNCE_MS); // 100

ReadonlyManager.logger

Access the logger instance for debugging.

// Enable debug logging
ReadonlyManager.logger.enable();
ReadonlyManager.logger.setLevel(0); // DEBUG level

// Disable logging
ReadonlyManager.logger.disable();

ReadonlyManager.features

Check browser feature support.

const features = ReadonlyManager.features;

if (features.shadowDOM) {
  console.log('Shadow DOM is supported');
}

if (features.mutationObserver) {
  console.log('Auto-updates are available');
}

Configuration Options

OptionTypeDefaultDescription
EDITABLE_SELECTORstring'input, select, textarea, button, [contenteditable]'CSS selector for editable controls
CONTAINER_SELECTORstring'div, section, span, ...'CSS selector for container elements
CLASSES.CAN_BE_READONLYstring'canBeReadOnly'Class marking controllable elements
CLASSES.READONLYstring'readonly'Class activating readonly state
CLASSES.INPUT_READONLYstring'input-readonly'Alternative readonly class
CLASSES.OMMIT_READONLYstring'ommitReadOnly'Class for exempted elements
CLASSES.READONLY_APPLIEDstring'fl-readonly-applied'Visual indicator class
DEBOUNCE_MSnumber50Debounce delay for observer (ms)
DEBUGbooleanfalseEnable debug logging
TRAVERSE_SHADOW_DOMbooleantrueEnable Shadow DOM traversal

Custom Configuration Example

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 TypeReadonly 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"
textareareadonly="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
selectdisabled
buttondisabled
[contenteditable]contenteditable="false"

Shadow DOM Support

The manager can traverse into Shadow DOM to process elements inside web components.

Enable Shadow DOM (Default)

ReadonlyManager.configure({ TRAVERSE_SHADOW_DOM: true });

Web Component Example

<my-custom-form class="canBeReadOnly readonly">
  <!-- Light DOM content -->
  <input type="text" name="light-input">
</my-custom-form>

<script>
  class MyCustomForm extends HTMLElement {
    constructor() {
      super();
      const shadow = this.attachShadow({ mode: 'open' });
      shadow.innerHTML = `
        <div class="canBeReadOnly readonly">
          <input type="text" name="shadow-input">
        </div>
      `;
    }
  }
  customElements.define('my-custom-form', MyCustomForm);
</script>

Both light-input and shadow-input will be processed.

Logger Utility

Built-in logger for debugging with enable/disable control.

// 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();

Log Level Constants

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

BrowserVersionNotes
Chrome54+Full support
Firefox63+Full support
Safari10.1+Full support
Edge79+Full support (Chromium)
Edge Legacy15+No Shadow DOM
IE 11āŒNot supported

Feature Detection

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.

Import Types

import { 
  ReadonlyManager,
  ReadonlyManagerConfig,
  ILogger,
  LogLevel,
  FeatureSupport
} from 'fl-readonly-manager';

Type-Safe Configuration

import { ReadonlyManagerConfig } from 'fl-readonly-manager';

const config: Partial<ReadonlyManagerConfig> = {
  DEBOUNCE_MS: 50,
  DEBUG: true
};

ReadonlyManager.configure(config);

Using the Core Class Directly

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

Breaking Changes

  • Function renamed: FL_HandleReadonlyControls() is now ReadonlyManager.refresh()
  • Configuration: Settings now use ReadonlyManager.configure() instead of global variables
  • Logger: Console logging now requires enabling ReadonlyManager.logger.enable()

Migration Steps

// v1 (old)
FL_HandleReadonlyControls();

// v2 (new)
ReadonlyManager.refresh();

// v1 (old) - no configuration API

// v2 (new)
ReadonlyManager.configure({ DEBOUNCE_MS: 50 });

Backward Compatibility

The legacy function is still available for backward compatibility:

// Still works in v2
FL_HandleReadonlyControls();

Contributing

  • Fork the repository
  • Create a feature branch: git checkout -b feature/my-feature
  • Make your changes
  • Run tests: npm test
  • Submit a pull request

Development

# 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

Publishing

Publish from the fl-readonly-manager package folder.

1. Prepare the release

# Install dependencies
npm install

# Update package version if needed
npm version patch
# or
npm version minor
# or
npm version major

# Verify the package
npm run lint
npm run typecheck
npm test
npm run build

2. Preview the package contents

# Review what npm will publish
npm pack --dry-run

The package publishes the files declared in package.json:

  • dist
  • examples
  • README.md
  • USE_CASES.md
  • LICENSE

3. Publish to npm

# Authenticate if needed
npm login

# Publish the package
npm publish

prepublishOnly runs automatically during publish and executes:

npm run build && npm test

If this is the first public publish for the package, use:

npm publish --access public

License

MIT Ā© FL Team

Support

Keywords

readonly

FAQs

Package last updated on 27 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts