ts-util-core 2.0.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 +411 -0
- package/dist/core/ajax.d.ts +30 -0
- package/dist/core/ajax.d.ts.map +1 -0
- package/dist/core/ajax.js +110 -0
- package/dist/core/ajax.js.map +1 -0
- package/dist/core/event-emitter.d.ts +29 -0
- package/dist/core/event-emitter.d.ts.map +1 -0
- package/dist/core/event-emitter.js +67 -0
- package/dist/core/event-emitter.js.map +1 -0
- package/dist/core/message.d.ts +28 -0
- package/dist/core/message.d.ts.map +1 -0
- package/dist/core/message.js +172 -0
- package/dist/core/message.js.map +1 -0
- package/dist/core/view.d.ts +49 -0
- package/dist/core/view.d.ts.map +1 -0
- package/dist/core/view.js +87 -0
- package/dist/core/view.js.map +1 -0
- package/dist/formatting/formatters.d.ts +9 -0
- package/dist/formatting/formatters.d.ts.map +1 -0
- package/dist/formatting/formatters.js +109 -0
- package/dist/formatting/formatters.js.map +1 -0
- package/dist/formatting/registry.d.ts +31 -0
- package/dist/formatting/registry.d.ts.map +1 -0
- package/dist/formatting/registry.js +49 -0
- package/dist/formatting/registry.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/dom.d.ts +38 -0
- package/dist/utils/dom.d.ts.map +1 -0
- package/dist/utils/dom.js +95 -0
- package/dist/utils/dom.js.map +1 -0
- package/dist/utils/sprintf.d.ts +16 -0
- package/dist/utils/sprintf.d.ts.map +1 -0
- package/dist/utils/sprintf.js +116 -0
- package/dist/utils/sprintf.js.map +1 -0
- package/dist/validation/constraints.d.ts +23 -0
- package/dist/validation/constraints.d.ts.map +1 -0
- package/dist/validation/constraints.js +131 -0
- package/dist/validation/constraints.js.map +1 -0
- package/dist/validation/validator.d.ts +45 -0
- package/dist/validation/validator.d.ts.map +1 -0
- package/dist/validation/validator.js +210 -0
- package/dist/validation/validator.js.map +1 -0
- package/package.json +26 -0
- package/readme.txt +4 -0
- package/src/core/ajax.ts +127 -0
- package/src/core/event-emitter.ts +84 -0
- package/src/core/message.ts +212 -0
- package/src/core/view.ts +101 -0
- package/src/formatting/formatters.ts +118 -0
- package/src/formatting/registry.ts +53 -0
- package/src/index.ts +142 -0
- package/src/types.ts +85 -0
- package/src/utils/dom.ts +105 -0
- package/src/utils/sprintf.ts +141 -0
- package/src/validation/constraints.ts +168 -0
- package/src/validation/validator.ts +276 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MessageOptions } from '../types.js';
|
|
2
|
+
interface DialogHandle {
|
|
3
|
+
close: () => void;
|
|
4
|
+
element: HTMLElement;
|
|
5
|
+
}
|
|
6
|
+
export declare class Message {
|
|
7
|
+
private currentModal;
|
|
8
|
+
/**
|
|
9
|
+
* Show a brief informational message. Auto-closes after `options.autoclose` ms.
|
|
10
|
+
*/
|
|
11
|
+
info(message: string, options?: MessageOptions): DialogHandle;
|
|
12
|
+
/**
|
|
13
|
+
* Show a modal dialog that must be explicitly dismissed.
|
|
14
|
+
*/
|
|
15
|
+
modal(message: string, options?: MessageOptions): DialogHandle;
|
|
16
|
+
/**
|
|
17
|
+
* Show a Yes/No confirmation dialog. Executes `onConfirm` if "Yes" is chosen.
|
|
18
|
+
*
|
|
19
|
+
* @returns A `DialogHandle` for programmatic control.
|
|
20
|
+
*/
|
|
21
|
+
confirm(title: string, message: string, onConfirm: () => void): DialogHandle;
|
|
22
|
+
/**
|
|
23
|
+
* Close the currently open modal (if any).
|
|
24
|
+
*/
|
|
25
|
+
dismissModal(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/core/message.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+DlD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;CACtB;AAuED,qBAAa,OAAO;IAClB,OAAO,CAAC,YAAY,CAA6B;IAEjD;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,YAAY;IAYjE;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,YAAY;IAalE;;;;OAIG;IACH,OAAO,CACL,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,IAAI,GACpB,YAAY;IAcf;;OAEG;IACH,YAAY,IAAI,IAAI;CAIrB"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// MSG module — vanilla DOM dialog system (no 3rd-party dependency)
|
|
3
|
+
//
|
|
4
|
+
// Design patterns used:
|
|
5
|
+
// - Facade Pattern : simple `info()`, `modal()`, `confirm()` API over
|
|
6
|
+
// raw DOM creation + CSS styling
|
|
7
|
+
// - Strategy Pattern : appearance is controlled via CSS classes, consumers
|
|
8
|
+
// can override styling without touching this module
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const OVERLAY_CLASS = 'rs-msg-overlay';
|
|
11
|
+
const DIALOG_CLASS = 'rs-msg-dialog';
|
|
12
|
+
const TITLE_CLASS = 'rs-msg-title';
|
|
13
|
+
const BODY_CLASS = 'rs-msg-body';
|
|
14
|
+
const BUTTON_BAR_CLASS = 'rs-msg-buttons';
|
|
15
|
+
const BUTTON_CLASS = 'rs-msg-btn';
|
|
16
|
+
let injectedStyles = false;
|
|
17
|
+
function injectStyles() {
|
|
18
|
+
if (injectedStyles)
|
|
19
|
+
return;
|
|
20
|
+
injectedStyles = true;
|
|
21
|
+
const css = `
|
|
22
|
+
.${OVERLAY_CLASS} {
|
|
23
|
+
position: fixed; inset: 0;
|
|
24
|
+
background: rgba(0,0,0,0.4);
|
|
25
|
+
display: flex; align-items: center; justify-content: center;
|
|
26
|
+
z-index: 10000;
|
|
27
|
+
animation: rs-fade-in 0.15s ease-out;
|
|
28
|
+
}
|
|
29
|
+
.${DIALOG_CLASS} {
|
|
30
|
+
background: #fff; border-radius: 8px;
|
|
31
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
|
|
32
|
+
min-width: 300px; max-width: 520px;
|
|
33
|
+
padding: 0; overflow: hidden;
|
|
34
|
+
animation: rs-slide-up 0.2s ease-out;
|
|
35
|
+
}
|
|
36
|
+
.${TITLE_CLASS} {
|
|
37
|
+
padding: 16px 20px; font-weight: 600; font-size: 15px;
|
|
38
|
+
border-bottom: 1px solid #eee;
|
|
39
|
+
}
|
|
40
|
+
.${BODY_CLASS} {
|
|
41
|
+
padding: 20px; font-size: 14px; line-height: 1.6;
|
|
42
|
+
}
|
|
43
|
+
.${BUTTON_BAR_CLASS} {
|
|
44
|
+
padding: 12px 20px; display: flex; justify-content: flex-end; gap: 8px;
|
|
45
|
+
border-top: 1px solid #eee;
|
|
46
|
+
}
|
|
47
|
+
.${BUTTON_CLASS} {
|
|
48
|
+
padding: 8px 18px; border: 1px solid #ccc; border-radius: 4px;
|
|
49
|
+
background: #fff; cursor: pointer; font-size: 14px;
|
|
50
|
+
transition: background 0.15s;
|
|
51
|
+
}
|
|
52
|
+
.${BUTTON_CLASS}:hover { background: #f5f5f5; }
|
|
53
|
+
.${BUTTON_CLASS}[data-primary] {
|
|
54
|
+
background: #2563eb; color: #fff; border-color: #2563eb;
|
|
55
|
+
}
|
|
56
|
+
.${BUTTON_CLASS}[data-primary]:hover { background: #1d4ed8; }
|
|
57
|
+
@keyframes rs-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
58
|
+
@keyframes rs-slide-up {
|
|
59
|
+
from { transform: translateY(20px); opacity: 0; }
|
|
60
|
+
to { transform: translateY(0); opacity: 1; }
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
const style = document.createElement('style');
|
|
64
|
+
style.textContent = css;
|
|
65
|
+
document.head.appendChild(style);
|
|
66
|
+
}
|
|
67
|
+
function createDialog(message, options = {}) {
|
|
68
|
+
injectStyles();
|
|
69
|
+
const overlay = document.createElement('div');
|
|
70
|
+
overlay.className = OVERLAY_CLASS;
|
|
71
|
+
const dialog = document.createElement('div');
|
|
72
|
+
dialog.className = DIALOG_CLASS;
|
|
73
|
+
dialog.setAttribute('role', 'dialog');
|
|
74
|
+
dialog.setAttribute('aria-modal', String(!!options.modal));
|
|
75
|
+
// Title
|
|
76
|
+
if (options.title) {
|
|
77
|
+
const titleEl = document.createElement('div');
|
|
78
|
+
titleEl.className = TITLE_CLASS;
|
|
79
|
+
titleEl.textContent = options.title;
|
|
80
|
+
dialog.appendChild(titleEl);
|
|
81
|
+
}
|
|
82
|
+
// Body
|
|
83
|
+
const body = document.createElement('div');
|
|
84
|
+
body.className = BODY_CLASS;
|
|
85
|
+
body.innerHTML = message;
|
|
86
|
+
dialog.appendChild(body);
|
|
87
|
+
// Buttons
|
|
88
|
+
if (options.buttons && options.buttons.length > 0) {
|
|
89
|
+
const bar = document.createElement('div');
|
|
90
|
+
bar.className = BUTTON_BAR_CLASS;
|
|
91
|
+
for (const btn of options.buttons) {
|
|
92
|
+
const el = document.createElement('button');
|
|
93
|
+
el.className = BUTTON_CLASS;
|
|
94
|
+
el.textContent = btn.label;
|
|
95
|
+
if (btn.primary)
|
|
96
|
+
el.setAttribute('data-primary', '');
|
|
97
|
+
el.addEventListener('click', () => {
|
|
98
|
+
options.onButton?.(btn.value);
|
|
99
|
+
close();
|
|
100
|
+
});
|
|
101
|
+
bar.appendChild(el);
|
|
102
|
+
}
|
|
103
|
+
dialog.appendChild(bar);
|
|
104
|
+
}
|
|
105
|
+
overlay.appendChild(dialog);
|
|
106
|
+
document.body.appendChild(overlay);
|
|
107
|
+
// Close on overlay click (if not modal)
|
|
108
|
+
if (!options.modal) {
|
|
109
|
+
overlay.addEventListener('click', (e) => {
|
|
110
|
+
if (e.target === overlay)
|
|
111
|
+
close();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function close() {
|
|
115
|
+
overlay.remove();
|
|
116
|
+
}
|
|
117
|
+
return { close, element: overlay };
|
|
118
|
+
}
|
|
119
|
+
export class Message {
|
|
120
|
+
currentModal = null;
|
|
121
|
+
/**
|
|
122
|
+
* Show a brief informational message. Auto-closes after `options.autoclose` ms.
|
|
123
|
+
*/
|
|
124
|
+
info(message, options = {}) {
|
|
125
|
+
const { autoclose = 5000, title } = options;
|
|
126
|
+
const handle = createDialog(message, { title });
|
|
127
|
+
if (autoclose > 0) {
|
|
128
|
+
setTimeout(() => handle.close(), autoclose);
|
|
129
|
+
}
|
|
130
|
+
return handle;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Show a modal dialog that must be explicitly dismissed.
|
|
134
|
+
*/
|
|
135
|
+
modal(message, options = {}) {
|
|
136
|
+
this.dismissModal();
|
|
137
|
+
const handle = createDialog(message, {
|
|
138
|
+
title: options.title,
|
|
139
|
+
modal: true,
|
|
140
|
+
buttons: [{ label: 'OK', value: 'ok', primary: true }],
|
|
141
|
+
});
|
|
142
|
+
this.currentModal = handle;
|
|
143
|
+
return handle;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Show a Yes/No confirmation dialog. Executes `onConfirm` if "Yes" is chosen.
|
|
147
|
+
*
|
|
148
|
+
* @returns A `DialogHandle` for programmatic control.
|
|
149
|
+
*/
|
|
150
|
+
confirm(title, message, onConfirm) {
|
|
151
|
+
return createDialog(message, {
|
|
152
|
+
title,
|
|
153
|
+
modal: true,
|
|
154
|
+
buttons: [
|
|
155
|
+
{ label: 'Yes', value: 'Y', primary: true },
|
|
156
|
+
{ label: 'No', value: 'N' },
|
|
157
|
+
],
|
|
158
|
+
onButton: (val) => {
|
|
159
|
+
if (val === 'Y')
|
|
160
|
+
onConfirm();
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Close the currently open modal (if any).
|
|
166
|
+
*/
|
|
167
|
+
dismissModal() {
|
|
168
|
+
this.currentModal?.close();
|
|
169
|
+
this.currentModal = null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/core/message.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { EventEmitter } from './event-emitter.js';
|
|
2
|
+
import type { AppEventMap, ViewLoadParams } from '../types.js';
|
|
3
|
+
import type { Ajax } from './ajax.js';
|
|
4
|
+
export type BeforeLoadHook = (context: HTMLElement) => void;
|
|
5
|
+
export declare class View {
|
|
6
|
+
private emitter;
|
|
7
|
+
private ajax;
|
|
8
|
+
private beforeLoadHooks;
|
|
9
|
+
constructor(emitter: EventEmitter<AppEventMap>, ajax: Ajax);
|
|
10
|
+
/**
|
|
11
|
+
* Register a function that runs on every DOM fragment before it is inserted.
|
|
12
|
+
*
|
|
13
|
+
* This is the **Observer pattern** — modules self-register their
|
|
14
|
+
* initialization logic without the core knowing about them.
|
|
15
|
+
*
|
|
16
|
+
* @returns An unregister function.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Validation module registers itself
|
|
21
|
+
* VIEW.addBeforeLoad((context) => {
|
|
22
|
+
* context.querySelectorAll('[constraint~="date"]').forEach(setupDatepicker);
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Format module registers itself
|
|
26
|
+
* VIEW.addBeforeLoad((context) => {
|
|
27
|
+
* context.querySelectorAll('[format]').forEach(applyMask);
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
addBeforeLoad(hook: BeforeLoadHook): () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Run all registered `beforeLoad` hooks on a DOM element.
|
|
34
|
+
* Also emits the `view:beforeLoad` event for event-based listeners.
|
|
35
|
+
*/
|
|
36
|
+
invokeBeforeLoad(context: HTMLElement): void;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch HTML from a URL, run all beforeLoad hooks, then inject into `target`.
|
|
39
|
+
*
|
|
40
|
+
* @param target - The container element to inject into.
|
|
41
|
+
* @param params - Request parameters (url, data, form, etc.).
|
|
42
|
+
*/
|
|
43
|
+
load(target: HTMLElement, params: ViewLoadParams): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the entire page body — should be called once on DOMContentLoaded.
|
|
46
|
+
*/
|
|
47
|
+
initPage(): void;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/core/view.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;AAE5D,qBAAa,IAAI;IACf,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,eAAe,CAAwB;gBAEnC,OAAO,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI;IAK1D;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,IAAI;IAQ/C;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAO5C;;;;;OAKG;IACG,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBtE;;OAEG;IACH,QAAQ,IAAI,IAAI;CAGjB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// VIEW module — load HTML fragments via AJAX and initialize them
|
|
3
|
+
//
|
|
4
|
+
// Design patterns used:
|
|
5
|
+
// - Observer Pattern : `beforeLoad` hooks run on every loaded fragment
|
|
6
|
+
// - Facade Pattern : `load()` handles fetch → parse → init → inject
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
import { parseHTML } from '../utils/dom.js';
|
|
9
|
+
export class View {
|
|
10
|
+
emitter;
|
|
11
|
+
ajax;
|
|
12
|
+
beforeLoadHooks = [];
|
|
13
|
+
constructor(emitter, ajax) {
|
|
14
|
+
this.emitter = emitter;
|
|
15
|
+
this.ajax = ajax;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register a function that runs on every DOM fragment before it is inserted.
|
|
19
|
+
*
|
|
20
|
+
* This is the **Observer pattern** — modules self-register their
|
|
21
|
+
* initialization logic without the core knowing about them.
|
|
22
|
+
*
|
|
23
|
+
* @returns An unregister function.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* // Validation module registers itself
|
|
28
|
+
* VIEW.addBeforeLoad((context) => {
|
|
29
|
+
* context.querySelectorAll('[constraint~="date"]').forEach(setupDatepicker);
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Format module registers itself
|
|
33
|
+
* VIEW.addBeforeLoad((context) => {
|
|
34
|
+
* context.querySelectorAll('[format]').forEach(applyMask);
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
addBeforeLoad(hook) {
|
|
39
|
+
this.beforeLoadHooks.push(hook);
|
|
40
|
+
return () => {
|
|
41
|
+
const idx = this.beforeLoadHooks.indexOf(hook);
|
|
42
|
+
if (idx !== -1)
|
|
43
|
+
this.beforeLoadHooks.splice(idx, 1);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run all registered `beforeLoad` hooks on a DOM element.
|
|
48
|
+
* Also emits the `view:beforeLoad` event for event-based listeners.
|
|
49
|
+
*/
|
|
50
|
+
invokeBeforeLoad(context) {
|
|
51
|
+
for (const hook of this.beforeLoadHooks) {
|
|
52
|
+
hook(context);
|
|
53
|
+
}
|
|
54
|
+
this.emitter.emit('view:beforeLoad', { context });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetch HTML from a URL, run all beforeLoad hooks, then inject into `target`.
|
|
58
|
+
*
|
|
59
|
+
* @param target - The container element to inject into.
|
|
60
|
+
* @param params - Request parameters (url, data, form, etc.).
|
|
61
|
+
*/
|
|
62
|
+
async load(target, params) {
|
|
63
|
+
const { success, ...requestParams } = params;
|
|
64
|
+
await this.ajax.request({
|
|
65
|
+
...requestParams,
|
|
66
|
+
success: async (data) => {
|
|
67
|
+
const response = data;
|
|
68
|
+
const html = await response.text();
|
|
69
|
+
const fragment = parseHTML(html);
|
|
70
|
+
// Wrap in a container so hooks can querySelector on it
|
|
71
|
+
const wrapper = document.createElement('div');
|
|
72
|
+
wrapper.appendChild(fragment);
|
|
73
|
+
this.invokeBeforeLoad(wrapper);
|
|
74
|
+
target.innerHTML = '';
|
|
75
|
+
target.appendChild(wrapper);
|
|
76
|
+
success?.(wrapper);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the entire page body — should be called once on DOMContentLoaded.
|
|
82
|
+
*/
|
|
83
|
+
initPage() {
|
|
84
|
+
this.invokeBeforeLoad(document.body);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/core/view.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,iEAAiE;AACjE,EAAE;AACF,wBAAwB;AACxB,0EAA0E;AAC1E,yEAAyE;AACzE,8EAA8E;AAK9E,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,IAAI;IACP,OAAO,CAA4B;IACnC,IAAI,CAAO;IACX,eAAe,GAAqB,EAAE,CAAC;IAE/C,YAAY,OAAkC,EAAE,IAAU;QACxD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,aAAa,CAAC,IAAoB;QAChC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAoB;QACnC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChB,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CAAC,MAAmB,EAAE,MAAsB;QACpD,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,CAAC;QAE7C,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;YACtB,GAAG,aAAa;YAChB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBACtB,MAAM,QAAQ,GAAG,IAAgB,CAAC;gBAClC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;gBAEjC,uDAAuD;gBACvD,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;gBAC9C,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;gBAE9B,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;gBAC/B,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC;gBACtB,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBAE5B,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;YACrB,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Formatter } from '../types.js';
|
|
2
|
+
/** Taiwan ID number: A123456789 */
|
|
3
|
+
export declare const idNumberFormatter: Formatter;
|
|
4
|
+
/** Date: YYYY-MM-DD (day is optional via `?`) */
|
|
5
|
+
export declare const dateFormatter: Formatter;
|
|
6
|
+
/** Time: HH:MM */
|
|
7
|
+
export declare const timeFormatter: Formatter;
|
|
8
|
+
export declare const builtInFormatters: Formatter[];
|
|
9
|
+
//# sourceMappingURL=formatters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/formatting/formatters.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAoF7C,mCAAmC;AACnC,eAAO,MAAM,iBAAiB,EAAE,SAG/B,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,aAAa,EAAE,SAG3B,CAAC;AAEF,kBAAkB;AAClB,eAAO,MAAM,aAAa,EAAE,SAG3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,SAAS,EAIxC,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Built-in formatters — vanilla input masking (no 3rd-party dependency)
|
|
3
|
+
//
|
|
4
|
+
// Replaces jquery.maskedinput with a lightweight mask engine.
|
|
5
|
+
//
|
|
6
|
+
// Design patterns used:
|
|
7
|
+
// - Strategy Pattern : each formatter is an independent strategy
|
|
8
|
+
// - Template Method : all formatters share the same `applyMask` core,
|
|
9
|
+
// only the mask definition varies
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Mask characters:
|
|
13
|
+
* `9` → digit (0-9)
|
|
14
|
+
* `A` → uppercase letter (A-Z)
|
|
15
|
+
* `?` → makes the rest of the mask optional
|
|
16
|
+
* Any other character → literal (inserted automatically)
|
|
17
|
+
*/
|
|
18
|
+
function applyMask(element, mask) {
|
|
19
|
+
// Find the position where `?` appears (optional section starts)
|
|
20
|
+
const optionalIdx = mask.indexOf('?');
|
|
21
|
+
const cleanMask = mask.replace('?', '');
|
|
22
|
+
function formatValue(raw) {
|
|
23
|
+
let result = '';
|
|
24
|
+
let rawIdx = 0;
|
|
25
|
+
for (let maskIdx = 0; maskIdx < cleanMask.length && rawIdx < raw.length; maskIdx++) {
|
|
26
|
+
const maskChar = cleanMask[maskIdx];
|
|
27
|
+
if (maskChar === '9') {
|
|
28
|
+
// Expect digit
|
|
29
|
+
while (rawIdx < raw.length && !/\d/.test(raw[rawIdx]))
|
|
30
|
+
rawIdx++;
|
|
31
|
+
if (rawIdx < raw.length) {
|
|
32
|
+
result += raw[rawIdx];
|
|
33
|
+
rawIdx++;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (maskChar === 'A') {
|
|
40
|
+
// Expect uppercase letter
|
|
41
|
+
while (rawIdx < raw.length && !/[A-Z]/i.test(raw[rawIdx]))
|
|
42
|
+
rawIdx++;
|
|
43
|
+
if (rawIdx < raw.length) {
|
|
44
|
+
result += raw[rawIdx].toUpperCase();
|
|
45
|
+
rawIdx++;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Literal — insert it
|
|
53
|
+
result += maskChar;
|
|
54
|
+
// If the raw char matches the literal, skip it
|
|
55
|
+
if (raw[rawIdx] === maskChar)
|
|
56
|
+
rawIdx++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
function getPlaceholder() {
|
|
62
|
+
const required = optionalIdx >= 0 ? cleanMask.substring(0, optionalIdx) : cleanMask;
|
|
63
|
+
return required
|
|
64
|
+
.replace(/9/g, '_')
|
|
65
|
+
.replace(/A/g, '_');
|
|
66
|
+
}
|
|
67
|
+
// Set placeholder to show expected format
|
|
68
|
+
if (!element.placeholder) {
|
|
69
|
+
element.placeholder = getPlaceholder();
|
|
70
|
+
}
|
|
71
|
+
element.addEventListener('input', () => {
|
|
72
|
+
const pos = element.selectionStart ?? 0;
|
|
73
|
+
const oldLen = element.value.length;
|
|
74
|
+
element.value = formatValue(element.value);
|
|
75
|
+
const newLen = element.value.length;
|
|
76
|
+
// Preserve cursor position intelligently
|
|
77
|
+
const newPos = pos + (newLen - oldLen);
|
|
78
|
+
element.setSelectionRange(newPos, newPos);
|
|
79
|
+
});
|
|
80
|
+
element.addEventListener('blur', () => {
|
|
81
|
+
element.value = formatValue(element.value);
|
|
82
|
+
});
|
|
83
|
+
// Format the initial value if present
|
|
84
|
+
if (element.value) {
|
|
85
|
+
element.value = formatValue(element.value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// -- Built-in formatter definitions -----------------------------------------
|
|
89
|
+
/** Taiwan ID number: A123456789 */
|
|
90
|
+
export const idNumberFormatter = {
|
|
91
|
+
key: 'idNumber',
|
|
92
|
+
format: (el) => applyMask(el, 'A999999999'),
|
|
93
|
+
};
|
|
94
|
+
/** Date: YYYY-MM-DD (day is optional via `?`) */
|
|
95
|
+
export const dateFormatter = {
|
|
96
|
+
key: 'date',
|
|
97
|
+
format: (el) => applyMask(el, '9999-99?-99'),
|
|
98
|
+
};
|
|
99
|
+
/** Time: HH:MM */
|
|
100
|
+
export const timeFormatter = {
|
|
101
|
+
key: 'time',
|
|
102
|
+
format: (el) => applyMask(el, '99:99'),
|
|
103
|
+
};
|
|
104
|
+
export const builtInFormatters = [
|
|
105
|
+
idNumberFormatter,
|
|
106
|
+
dateFormatter,
|
|
107
|
+
timeFormatter,
|
|
108
|
+
];
|
|
109
|
+
//# sourceMappingURL=formatters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.js","sourceRoot":"","sources":["../../src/formatting/formatters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Formatter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages input formatters keyed by name.
|
|
4
|
+
*
|
|
5
|
+
* Elements declare their desired format via the `format` HTML attribute.
|
|
6
|
+
* The registry matches elements to their formatter at initialization time.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* registry.add({ key: 'phone', format: (el) => applyPhoneMask(el) });
|
|
11
|
+
*
|
|
12
|
+
* // In HTML:
|
|
13
|
+
* // <input type="text" format="phone" />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare class FormatterRegistry {
|
|
17
|
+
private formatters;
|
|
18
|
+
/**
|
|
19
|
+
* Register a formatter. If a formatter with the same key exists, it is replaced.
|
|
20
|
+
*/
|
|
21
|
+
add(formatter: Formatter): void;
|
|
22
|
+
/**
|
|
23
|
+
* Apply registered formatters to all elements with a `format` attribute
|
|
24
|
+
* within the given context.
|
|
25
|
+
*
|
|
26
|
+
* This is the **beforeLoad hook** — registered with `VIEW.addBeforeLoad`
|
|
27
|
+
* so dynamically loaded content also gets formatted.
|
|
28
|
+
*/
|
|
29
|
+
applyAll(context: HTMLElement): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/formatting/registry.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,UAAU,CAAgC;IAElD;;OAEG;IACH,GAAG,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAI/B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CAUrC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Formatter Registry — register and apply input formatters by key
|
|
3
|
+
//
|
|
4
|
+
// Design patterns used:
|
|
5
|
+
// - Registry Pattern : formatters stored by key, resolved at runtime
|
|
6
|
+
// - Observer Pattern : integrates with VIEW.addBeforeLoad for auto-init
|
|
7
|
+
// - Open/Closed : add new formatters without modifying existing code
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
/**
|
|
10
|
+
* Manages input formatters keyed by name.
|
|
11
|
+
*
|
|
12
|
+
* Elements declare their desired format via the `format` HTML attribute.
|
|
13
|
+
* The registry matches elements to their formatter at initialization time.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* registry.add({ key: 'phone', format: (el) => applyPhoneMask(el) });
|
|
18
|
+
*
|
|
19
|
+
* // In HTML:
|
|
20
|
+
* // <input type="text" format="phone" />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class FormatterRegistry {
|
|
24
|
+
formatters = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Register a formatter. If a formatter with the same key exists, it is replaced.
|
|
27
|
+
*/
|
|
28
|
+
add(formatter) {
|
|
29
|
+
this.formatters.set(formatter.key, formatter);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Apply registered formatters to all elements with a `format` attribute
|
|
33
|
+
* within the given context.
|
|
34
|
+
*
|
|
35
|
+
* This is the **beforeLoad hook** — registered with `VIEW.addBeforeLoad`
|
|
36
|
+
* so dynamically loaded content also gets formatted.
|
|
37
|
+
*/
|
|
38
|
+
applyAll(context) {
|
|
39
|
+
const elements = context.querySelectorAll('[format]');
|
|
40
|
+
for (const el of elements) {
|
|
41
|
+
const key = el.getAttribute('format');
|
|
42
|
+
if (!key)
|
|
43
|
+
continue;
|
|
44
|
+
const formatter = this.formatters.get(key);
|
|
45
|
+
formatter?.format(el);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/formatting/registry.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,kEAAkE;AAClE,EAAE;AACF,wBAAwB;AACxB,uEAAuE;AACvE,0EAA0E;AAC1E,4EAA4E;AAC5E,8EAA8E;AAI9E;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,iBAAiB;IACpB,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAElD;;OAEG;IACH,GAAG,CAAC,SAAoB;QACtB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAChD,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,OAAoB;QAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAmB,UAAU,CAAC,CAAC;QAExE,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3C,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type { ConstraintType, FormDataRecord, AjaxRequestParams, AjaxJsonRequestParams, ViewLoadParams, MessageOptions, Formatter as FormatterDefinition, AppEventMap, ValidationResult, TextareaValidationResult, } from './types.js';
|
|
2
|
+
export type { ConstraintHandler } from './validation/constraints.js';
|
|
3
|
+
export type { BeforeLoadHook } from './core/view.js';
|
|
4
|
+
export { EventEmitter } from './core/event-emitter.js';
|
|
5
|
+
export { Ajax } from './core/ajax.js';
|
|
6
|
+
export { View } from './core/view.js';
|
|
7
|
+
export { Message } from './core/message.js';
|
|
8
|
+
export { Validator } from './validation/validator.js';
|
|
9
|
+
export { FormatterRegistry } from './formatting/registry.js';
|
|
10
|
+
export { sprintf } from './utils/sprintf.js';
|
|
11
|
+
export { formToJSON, isDateValid, parseHTML, scrollToElement, defaults } from './utils/dom.js';
|
|
12
|
+
import { EventEmitter } from './core/event-emitter.js';
|
|
13
|
+
import { Ajax } from './core/ajax.js';
|
|
14
|
+
import { View } from './core/view.js';
|
|
15
|
+
import { Message } from './core/message.js';
|
|
16
|
+
import { Validator } from './validation/validator.js';
|
|
17
|
+
import { FormatterRegistry } from './formatting/registry.js';
|
|
18
|
+
import type { AppEventMap } from './types.js';
|
|
19
|
+
/** AJAX module — HTTP requests with lifecycle hooks. */
|
|
20
|
+
export declare const AJAX: Ajax;
|
|
21
|
+
/** VIEW module — load and initialize HTML fragments. */
|
|
22
|
+
export declare const VIEW: View;
|
|
23
|
+
/** MSG module — dialogs and notifications. */
|
|
24
|
+
export declare const MSG: Message;
|
|
25
|
+
/** Validation module — form validation engine. */
|
|
26
|
+
export declare const Validation: Validator;
|
|
27
|
+
/** Formatter module — input masking registry. */
|
|
28
|
+
export declare const Formatter: FormatterRegistry;
|
|
29
|
+
/** Event bus — subscribe to library-wide lifecycle events. */
|
|
30
|
+
export declare const Events: EventEmitter<AppEventMap>;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2BA,YAAY,EACV,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,SAAS,IAAI,mBAAmB,EAChC,WAAW,EACX,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAM/F,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA4B9C,wDAAwD;AACxD,eAAO,MAAM,IAAI,MAAO,CAAC;AAEzB,wDAAwD;AACxD,eAAO,MAAM,IAAI,MAAO,CAAC;AAEzB,8CAA8C;AAC9C,eAAO,MAAM,GAAG,SAAM,CAAC;AAEvB,kDAAkD;AAClD,eAAO,MAAM,UAAU,WAAY,CAAC;AAEpC,iDAAiD;AACjD,eAAO,MAAM,SAAS,mBAAY,CAAC;AAEnC,8DAA8D;AAC9D,eAAO,MAAM,MAAM,2BAAU,CAAC"}
|