universal-readonly-rest 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +154 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +154 -0
- package/dist/src/types.d.ts +14 -0
- package/dist/src/types.js +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# universal-readonly
|
|
2
|
+
|
|
3
|
+
Universal readonly / interaction-control layer for web applications.
|
|
4
|
+
|
|
5
|
+
This package lets you lock a page or a section of the DOM into a read-only state while still allowing safe viewing actions (scrolling, reading, selection, copying, etc.) and optionally permitting some interactions such as links or keyboard input.
|
|
6
|
+
|
|
7
|
+
It is framework-agnostic and works in plain browser code, React, Vue, or any app that can run JavaScript in the DOM.
|
|
8
|
+
|
|
9
|
+
Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install universal-readonly-rest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Basic usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { readonlyMode } from 'universal-readonly-rest';
|
|
19
|
+
|
|
20
|
+
// Enable globally for the entire page
|
|
21
|
+
readonlyMode.enable();
|
|
22
|
+
|
|
23
|
+
// Enable only for one form or section
|
|
24
|
+
readonlyMode.enable({ selector: '#order-form' });
|
|
25
|
+
|
|
26
|
+
// Enable with custom permissions
|
|
27
|
+
readonlyMode.enable({
|
|
28
|
+
selector: '#order-form',
|
|
29
|
+
allow: {
|
|
30
|
+
scroll: true,
|
|
31
|
+
selection: true,
|
|
32
|
+
copy: true,
|
|
33
|
+
links: false,
|
|
34
|
+
download: false,
|
|
35
|
+
focus: true,
|
|
36
|
+
contextMenu: false,
|
|
37
|
+
keyboard: false
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Disable all readonly behavior
|
|
42
|
+
readonlyMode.disable();
|
|
43
|
+
|
|
44
|
+
// Toggle between on/off
|
|
45
|
+
readonlyMode.toggle();
|
|
46
|
+
|
|
47
|
+
// Check if the readonly mode is active
|
|
48
|
+
console.log(readonlyMode.isEnabled());
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Options
|
|
52
|
+
|
|
53
|
+
`readonlyMode.enable(options?)` and `readonlyMode.toggle(options?)` accept:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
interface ReadonlyOptions {
|
|
57
|
+
selector?: string | string[];
|
|
58
|
+
allow?: {
|
|
59
|
+
scroll?: boolean;
|
|
60
|
+
selection?: boolean;
|
|
61
|
+
copy?: boolean;
|
|
62
|
+
links?: boolean;
|
|
63
|
+
download?: boolean;
|
|
64
|
+
focus?: boolean;
|
|
65
|
+
contextMenu?: boolean;
|
|
66
|
+
keyboard?: boolean;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `selector`: CSS selector or list of selectors to scope readonly mode. If omitted, the package applies to the whole document.
|
|
72
|
+
- `allow`: fine-grained control over what behavior is still permitted while readonly is active.
|
|
73
|
+
|
|
74
|
+
Default allow configuration
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
{
|
|
78
|
+
scroll: true,
|
|
79
|
+
selection: true,
|
|
80
|
+
copy: true,
|
|
81
|
+
links: true,
|
|
82
|
+
download: true,
|
|
83
|
+
focus: true,
|
|
84
|
+
contextMenu: true,
|
|
85
|
+
keyboard: false
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
What gets blocked
|
|
90
|
+
|
|
91
|
+
When readonly mode is active, the library listens to DOM events in the capture phase and prevents state-changing actions such as:
|
|
92
|
+
|
|
93
|
+
- form submit
|
|
94
|
+
- button click
|
|
95
|
+
- input/change events
|
|
96
|
+
- paste/cut/drop
|
|
97
|
+
- context menu
|
|
98
|
+
- keyboard editing while `keyboard: false`
|
|
99
|
+
|
|
100
|
+
The default behavior is to keep viewing and reading actions available while blocking edits and interactive mutations.
|
|
101
|
+
|
|
102
|
+
React example
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import React, { useEffect } from 'react';
|
|
106
|
+
import { readonlyMode } from 'universal-readonly-rest';
|
|
107
|
+
|
|
108
|
+
export function OrderFormDemo() {
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
readonlyMode.enable({
|
|
111
|
+
selector: '#order-form',
|
|
112
|
+
allow: {
|
|
113
|
+
scroll: true,
|
|
114
|
+
selection: true,
|
|
115
|
+
copy: true,
|
|
116
|
+
links: false,
|
|
117
|
+
download: false,
|
|
118
|
+
focus: true,
|
|
119
|
+
contextMenu: false,
|
|
120
|
+
keyboard: false
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return () => readonlyMode.disable();
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<form id="order-form">
|
|
129
|
+
<input name="name" placeholder="Name" />
|
|
130
|
+
<button type="submit">Submit</button>
|
|
131
|
+
</form>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
In this example, only the form with id `order-form` is locked. Other parts of the app remain interactive unless they match the selector.
|
|
137
|
+
|
|
138
|
+
Notes
|
|
139
|
+
|
|
140
|
+
- The package is intentionally small and browser-oriented.
|
|
141
|
+
- It hooks into DOM events on the capture phase to stop interaction before it reaches the target element.
|
|
142
|
+
- Because it works with selectors, you can apply readonly mode to a single form, a section, or the entire page.
|
|
143
|
+
- If you want all interactions disabled, set all `allow` values to `false` for a strict lock.
|
|
144
|
+
|
|
145
|
+
Example: strict read-only mode
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
readonlyMode.enable({
|
|
149
|
+
allow: {
|
|
150
|
+
scroll: false,
|
|
151
|
+
selection: false,
|
|
152
|
+
copy: false,
|
|
153
|
+
links: false,
|
|
154
|
+
download: false,
|
|
155
|
+
focus: false,
|
|
156
|
+
contextMenu: false,
|
|
157
|
+
keyboard: false
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This effectively prevents most user-driven state changes while leaving the page visually readable (depending on browser behavior and the element set you target).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ReadonlyOptions } from './types';
|
|
2
|
+
declare class ReadonlyMode {
|
|
3
|
+
private enabled;
|
|
4
|
+
private roots;
|
|
5
|
+
private options;
|
|
6
|
+
private listeners;
|
|
7
|
+
enable(options?: ReadonlyOptions): void;
|
|
8
|
+
disable(): void;
|
|
9
|
+
toggle(options?: ReadonlyOptions): void;
|
|
10
|
+
isEnabled(): boolean;
|
|
11
|
+
private normalizeSelectors;
|
|
12
|
+
private isEventInRoots;
|
|
13
|
+
private isEditable;
|
|
14
|
+
}
|
|
15
|
+
export declare const readonlyMode: ReadonlyMode;
|
|
16
|
+
export default readonlyMode;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const DEFAULT_ALLOW = {
|
|
2
|
+
scroll: true,
|
|
3
|
+
selection: true,
|
|
4
|
+
copy: true,
|
|
5
|
+
links: true,
|
|
6
|
+
download: true,
|
|
7
|
+
focus: true,
|
|
8
|
+
contextMenu: true,
|
|
9
|
+
keyboard: false
|
|
10
|
+
};
|
|
11
|
+
class ReadonlyMode {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.enabled = false;
|
|
14
|
+
this.roots = [];
|
|
15
|
+
this.options = {};
|
|
16
|
+
this.listeners = new Map();
|
|
17
|
+
}
|
|
18
|
+
enable(options) {
|
|
19
|
+
if (this.enabled)
|
|
20
|
+
return;
|
|
21
|
+
this.enabled = true;
|
|
22
|
+
this.options = options || {};
|
|
23
|
+
const allow = { ...DEFAULT_ALLOW, ...(this.options.allow || {}) };
|
|
24
|
+
// Resolve roots
|
|
25
|
+
const selectors = this.normalizeSelectors(this.options.selector);
|
|
26
|
+
if (selectors.length === 0) {
|
|
27
|
+
this.roots = [document.documentElement];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const set = new Set();
|
|
31
|
+
for (const sel of selectors) {
|
|
32
|
+
try {
|
|
33
|
+
document.querySelectorAll(sel).forEach(el => set.add(el));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// ignore invalid selectors
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.roots = Array.from(set);
|
|
40
|
+
}
|
|
41
|
+
// Attach capturing listeners to intercept state-changing interactions
|
|
42
|
+
const capture = (e) => {
|
|
43
|
+
if (!this.isEventInRoots(e))
|
|
44
|
+
return;
|
|
45
|
+
if (e.type === 'submit') {
|
|
46
|
+
// Block form submissions
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
e.stopImmediatePropagation();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (e.type === 'click') {
|
|
52
|
+
// Allow links optionally
|
|
53
|
+
const t = e.target;
|
|
54
|
+
if (!t)
|
|
55
|
+
return;
|
|
56
|
+
const anchor = t.closest('a[href]');
|
|
57
|
+
if (anchor) {
|
|
58
|
+
if (allow.links)
|
|
59
|
+
return; // allow
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
e.stopImmediatePropagation();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const btn = t.closest('button, input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"]');
|
|
65
|
+
if (btn) {
|
|
66
|
+
// block clicking that could change state
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
e.stopImmediatePropagation();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (e.type === 'keydown' || e.type === 'keypress' || e.type === 'beforeinput') {
|
|
73
|
+
if (allow.keyboard)
|
|
74
|
+
return;
|
|
75
|
+
const t = e.target;
|
|
76
|
+
if (!t)
|
|
77
|
+
return;
|
|
78
|
+
if (this.isEditable(t)) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
e.stopImmediatePropagation();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (e.type === 'input' || e.type === 'change' || e.type === 'paste' || e.type === 'cut' || e.type === 'drop') {
|
|
85
|
+
// Block state-changing input events
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
e.stopImmediatePropagation();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (e.type === 'contextmenu') {
|
|
91
|
+
if (allow.contextMenu)
|
|
92
|
+
return;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
e.stopImmediatePropagation();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const events = ['submit', 'click', 'keydown', 'keypress', 'beforeinput', 'input', 'change', 'paste', 'cut', 'drop', 'contextmenu'];
|
|
99
|
+
for (const ev of events) {
|
|
100
|
+
const listener = capture.bind(this);
|
|
101
|
+
document.addEventListener(ev, listener, { capture: true, passive: false });
|
|
102
|
+
this.listeners.set(ev, listener);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
disable() {
|
|
106
|
+
if (!this.enabled)
|
|
107
|
+
return;
|
|
108
|
+
this.enabled = false;
|
|
109
|
+
for (const [ev, listener] of this.listeners.entries()) {
|
|
110
|
+
document.removeEventListener(ev, listener, { capture: true });
|
|
111
|
+
}
|
|
112
|
+
this.listeners.clear();
|
|
113
|
+
this.roots = [];
|
|
114
|
+
this.options = {};
|
|
115
|
+
}
|
|
116
|
+
toggle(options) {
|
|
117
|
+
if (this.enabled)
|
|
118
|
+
this.disable();
|
|
119
|
+
else
|
|
120
|
+
this.enable(options);
|
|
121
|
+
}
|
|
122
|
+
isEnabled() {
|
|
123
|
+
return this.enabled;
|
|
124
|
+
}
|
|
125
|
+
normalizeSelectors(selector) {
|
|
126
|
+
if (!selector)
|
|
127
|
+
return [];
|
|
128
|
+
if (typeof selector === 'string') {
|
|
129
|
+
// support comma-separated lists
|
|
130
|
+
return selector.split(',').map(s => s.trim()).filter(Boolean);
|
|
131
|
+
}
|
|
132
|
+
return selector.map(s => s.trim()).filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
isEventInRoots(e) {
|
|
135
|
+
const target = e.target;
|
|
136
|
+
if (!target)
|
|
137
|
+
return false;
|
|
138
|
+
for (const root of this.roots) {
|
|
139
|
+
if (root.contains(target))
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
isEditable(el) {
|
|
145
|
+
const tag = el.tagName.toLowerCase();
|
|
146
|
+
if (tag === 'input' || tag === 'textarea' || el.isContentEditable)
|
|
147
|
+
return true;
|
|
148
|
+
if (el.closest && el.closest('input, textarea, [contenteditable="true"]'))
|
|
149
|
+
return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export const readonlyMode = new ReadonlyMode();
|
|
154
|
+
export default readonlyMode;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ReadonlyOptions } from './types';
|
|
2
|
+
declare class ReadonlyMode {
|
|
3
|
+
private enabled;
|
|
4
|
+
private roots;
|
|
5
|
+
private options;
|
|
6
|
+
private listeners;
|
|
7
|
+
enable(options?: ReadonlyOptions): void;
|
|
8
|
+
disable(): void;
|
|
9
|
+
toggle(options?: ReadonlyOptions): void;
|
|
10
|
+
isEnabled(): boolean;
|
|
11
|
+
private normalizeSelectors;
|
|
12
|
+
private isEventInRoots;
|
|
13
|
+
private isEditable;
|
|
14
|
+
}
|
|
15
|
+
export declare const readonlyMode: ReadonlyMode;
|
|
16
|
+
export default readonlyMode;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const DEFAULT_ALLOW = {
|
|
2
|
+
scroll: true,
|
|
3
|
+
selection: true,
|
|
4
|
+
copy: true,
|
|
5
|
+
links: true,
|
|
6
|
+
download: true,
|
|
7
|
+
focus: true,
|
|
8
|
+
contextMenu: true,
|
|
9
|
+
keyboard: false
|
|
10
|
+
};
|
|
11
|
+
class ReadonlyMode {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.enabled = false;
|
|
14
|
+
this.roots = [];
|
|
15
|
+
this.options = {};
|
|
16
|
+
this.listeners = new Map();
|
|
17
|
+
}
|
|
18
|
+
enable(options) {
|
|
19
|
+
if (this.enabled)
|
|
20
|
+
return;
|
|
21
|
+
this.enabled = true;
|
|
22
|
+
this.options = options || {};
|
|
23
|
+
const allow = { ...DEFAULT_ALLOW, ...(this.options.allow || {}) };
|
|
24
|
+
// Resolve roots
|
|
25
|
+
const selectors = this.normalizeSelectors(this.options.selector);
|
|
26
|
+
if (selectors.length === 0) {
|
|
27
|
+
this.roots = [document.documentElement];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const set = new Set();
|
|
31
|
+
for (const sel of selectors) {
|
|
32
|
+
try {
|
|
33
|
+
document.querySelectorAll(sel).forEach(el => set.add(el));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// ignore invalid selectors
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
this.roots = Array.from(set);
|
|
40
|
+
}
|
|
41
|
+
// Attach capturing listeners to intercept state-changing interactions
|
|
42
|
+
const capture = (e) => {
|
|
43
|
+
if (!this.isEventInRoots(e))
|
|
44
|
+
return;
|
|
45
|
+
if (e.type === 'submit') {
|
|
46
|
+
// Block form submissions
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
e.stopImmediatePropagation();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (e.type === 'click') {
|
|
52
|
+
// Allow links optionally
|
|
53
|
+
const t = e.target;
|
|
54
|
+
if (!t)
|
|
55
|
+
return;
|
|
56
|
+
const anchor = t.closest('a[href]');
|
|
57
|
+
if (anchor) {
|
|
58
|
+
if (allow.links)
|
|
59
|
+
return; // allow
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
e.stopImmediatePropagation();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const btn = t.closest('button, input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"]');
|
|
65
|
+
if (btn) {
|
|
66
|
+
// block clicking that could change state
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
e.stopImmediatePropagation();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (e.type === 'keydown' || e.type === 'keypress' || e.type === 'beforeinput') {
|
|
73
|
+
if (allow.keyboard)
|
|
74
|
+
return;
|
|
75
|
+
const t = e.target;
|
|
76
|
+
if (!t)
|
|
77
|
+
return;
|
|
78
|
+
if (this.isEditable(t)) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
e.stopImmediatePropagation();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (e.type === 'input' || e.type === 'change' || e.type === 'paste' || e.type === 'cut' || e.type === 'drop') {
|
|
85
|
+
// Block state-changing input events
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
e.stopImmediatePropagation();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (e.type === 'contextmenu') {
|
|
91
|
+
if (allow.contextMenu)
|
|
92
|
+
return;
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
e.stopImmediatePropagation();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const events = ['submit', 'click', 'keydown', 'keypress', 'beforeinput', 'input', 'change', 'paste', 'cut', 'drop', 'contextmenu'];
|
|
99
|
+
for (const ev of events) {
|
|
100
|
+
const listener = capture.bind(this);
|
|
101
|
+
document.addEventListener(ev, listener, { capture: true, passive: false });
|
|
102
|
+
this.listeners.set(ev, listener);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
disable() {
|
|
106
|
+
if (!this.enabled)
|
|
107
|
+
return;
|
|
108
|
+
this.enabled = false;
|
|
109
|
+
for (const [ev, listener] of this.listeners.entries()) {
|
|
110
|
+
document.removeEventListener(ev, listener, { capture: true });
|
|
111
|
+
}
|
|
112
|
+
this.listeners.clear();
|
|
113
|
+
this.roots = [];
|
|
114
|
+
this.options = {};
|
|
115
|
+
}
|
|
116
|
+
toggle(options) {
|
|
117
|
+
if (this.enabled)
|
|
118
|
+
this.disable();
|
|
119
|
+
else
|
|
120
|
+
this.enable(options);
|
|
121
|
+
}
|
|
122
|
+
isEnabled() {
|
|
123
|
+
return this.enabled;
|
|
124
|
+
}
|
|
125
|
+
normalizeSelectors(selector) {
|
|
126
|
+
if (!selector)
|
|
127
|
+
return [];
|
|
128
|
+
if (typeof selector === 'string') {
|
|
129
|
+
// support comma-separated lists
|
|
130
|
+
return selector.split(',').map(s => s.trim()).filter(Boolean);
|
|
131
|
+
}
|
|
132
|
+
return selector.map(s => s.trim()).filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
isEventInRoots(e) {
|
|
135
|
+
const target = e.target;
|
|
136
|
+
if (!target)
|
|
137
|
+
return false;
|
|
138
|
+
for (const root of this.roots) {
|
|
139
|
+
if (root.contains(target))
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
isEditable(el) {
|
|
145
|
+
const tag = el.tagName.toLowerCase();
|
|
146
|
+
if (tag === 'input' || tag === 'textarea' || el.isContentEditable)
|
|
147
|
+
return true;
|
|
148
|
+
if (el.closest && el.closest('input, textarea, [contenteditable="true"]'))
|
|
149
|
+
return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export const readonlyMode = new ReadonlyMode();
|
|
154
|
+
export default readonlyMode;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ReadonlyAllowOptions {
|
|
2
|
+
scroll?: boolean;
|
|
3
|
+
selection?: boolean;
|
|
4
|
+
copy?: boolean;
|
|
5
|
+
links?: boolean;
|
|
6
|
+
download?: boolean;
|
|
7
|
+
focus?: boolean;
|
|
8
|
+
contextMenu?: boolean;
|
|
9
|
+
keyboard?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ReadonlyOptions {
|
|
12
|
+
selector?: string | string[];
|
|
13
|
+
allow?: ReadonlyAllowOptions;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ReadonlyAllowOptions {
|
|
2
|
+
scroll?: boolean;
|
|
3
|
+
selection?: boolean;
|
|
4
|
+
copy?: boolean;
|
|
5
|
+
links?: boolean;
|
|
6
|
+
download?: boolean;
|
|
7
|
+
focus?: boolean;
|
|
8
|
+
contextMenu?: boolean;
|
|
9
|
+
keyboard?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ReadonlyOptions {
|
|
12
|
+
selector?: string | string[];
|
|
13
|
+
allow?: ReadonlyAllowOptions;
|
|
14
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "universal-readonly-rest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal readonly / interaction-control layer for web applications (framework-agnostic).",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc --project tsconfig.json",
|
|
13
|
+
"test": "vitest run --environment jsdom",
|
|
14
|
+
"test:watch": "vitest --environment jsdom"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"readonly",
|
|
18
|
+
"readonly-mode",
|
|
19
|
+
"ui",
|
|
20
|
+
"accessibility",
|
|
21
|
+
"interaction-control"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/jsdom": "^30.0.0",
|
|
27
|
+
"@types/node": "^26.4.1",
|
|
28
|
+
"jsdom": "^30.0.1",
|
|
29
|
+
"typescript": "^7.0.2",
|
|
30
|
+
"vitest": "^4.1.11"
|
|
31
|
+
}
|
|
32
|
+
}
|