@livenetworks/ashlar 1.3.2
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 +177 -0
- package/js/COMPONENTS.md +1102 -0
- package/js/index.js +41 -0
- package/js/ln-accordion/README.md +137 -0
- package/js/ln-accordion/ln-accordion.js +1 -0
- package/js/ln-accordion/src/ln-accordion.js +41 -0
- package/js/ln-ajax/README.md +91 -0
- package/js/ln-ajax/ln-ajax.js +1 -0
- package/js/ln-ajax/src/ln-ajax.js +277 -0
- package/js/ln-api-connector/README.md +150 -0
- package/js/ln-api-connector/ln-api-connector.js +1 -0
- package/js/ln-api-connector/src/ln-api-connector.js +265 -0
- package/js/ln-autoresize/README.md +80 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -0
- package/js/ln-autoresize/src/ln-autoresize.js +47 -0
- package/js/ln-autosave/README.md +92 -0
- package/js/ln-autosave/ln-autosave.js +1 -0
- package/js/ln-autosave/src/ln-autosave.js +147 -0
- package/js/ln-circular-progress/README.md +161 -0
- package/js/ln-circular-progress/ln-circular-progress.js +1 -0
- package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
- package/js/ln-confirm/README.md +86 -0
- package/js/ln-confirm/_ln-confirm.scss +13 -0
- package/js/ln-confirm/ln-confirm.js +1 -0
- package/js/ln-confirm/src/ln-confirm.js +131 -0
- package/js/ln-core/crypto.js +83 -0
- package/js/ln-core/helpers.js +411 -0
- package/js/ln-core/index.js +5 -0
- package/js/ln-core/persist.js +71 -0
- package/js/ln-core/positioning.js +207 -0
- package/js/ln-core/reactive.js +74 -0
- package/js/ln-couchdb-connector/README.md +156 -0
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
- package/js/ln-data-coordinator/README.md +165 -0
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
- package/js/ln-data-store/README.md +94 -0
- package/js/ln-data-store/ln-data-store.js +1 -0
- package/js/ln-data-store/src/ln-data-store.js +699 -0
- package/js/ln-data-table/README.md +110 -0
- package/js/ln-data-table/ln-data-table.js +1 -0
- package/js/ln-data-table/ln-data-table.scss +10 -0
- package/js/ln-data-table/src/ln-data-table.js +1103 -0
- package/js/ln-date/README.md +151 -0
- package/js/ln-date/ln-date.js +1 -0
- package/js/ln-date/src/ln-date.js +442 -0
- package/js/ln-dropdown/README.md +117 -0
- package/js/ln-dropdown/ln-dropdown.js +1 -0
- package/js/ln-dropdown/ln-dropdown.scss +15 -0
- package/js/ln-dropdown/src/ln-dropdown.js +174 -0
- package/js/ln-external-links/README.md +341 -0
- package/js/ln-external-links/ln-external-links.js +1 -0
- package/js/ln-external-links/src/ln-external-links.js +116 -0
- package/js/ln-filter/README.md +99 -0
- package/js/ln-filter/ln-filter.js +1 -0
- package/js/ln-filter/ln-filter.scss +7 -0
- package/js/ln-filter/src/ln-filter.js +404 -0
- package/js/ln-form/README.md +101 -0
- package/js/ln-form/ln-form.js +1 -0
- package/js/ln-form/src/ln-form.js +199 -0
- package/js/ln-http/README.md +89 -0
- package/js/ln-http/ln-http.js +1 -0
- package/js/ln-http/src/ln-http.js +219 -0
- package/js/ln-icons/README.md +88 -0
- package/js/ln-icons/ln-icons.js +1 -0
- package/js/ln-icons/src/ln-icons.js +169 -0
- package/js/ln-link/README.md +303 -0
- package/js/ln-link/ln-link.js +1 -0
- package/js/ln-link/src/ln-link.js +196 -0
- package/js/ln-modal/README.md +154 -0
- package/js/ln-modal/ln-modal.js +1 -0
- package/js/ln-modal/ln-modal.scss +11 -0
- package/js/ln-modal/src/ln-modal.js +201 -0
- package/js/ln-nav/README.md +70 -0
- package/js/ln-nav/ln-nav.js +1 -0
- package/js/ln-nav/src/ln-nav.js +177 -0
- package/js/ln-number/README.md +122 -0
- package/js/ln-number/ln-number.js +1 -0
- package/js/ln-number/src/ln-number.js +302 -0
- package/js/ln-popover/README.md +127 -0
- package/js/ln-popover/ln-popover.js +1 -0
- package/js/ln-popover/src/ln-popover.js +288 -0
- package/js/ln-progress/README.md +442 -0
- package/js/ln-progress/ln-progress.js +1 -0
- package/js/ln-progress/src/ln-progress.js +150 -0
- package/js/ln-search/README.md +83 -0
- package/js/ln-search/ln-search.js +1 -0
- package/js/ln-search/ln-search.scss +7 -0
- package/js/ln-search/src/ln-search.js +114 -0
- package/js/ln-sortable/README.md +95 -0
- package/js/ln-sortable/ln-sortable.js +1 -0
- package/js/ln-sortable/src/ln-sortable.js +203 -0
- package/js/ln-table/README.md +101 -0
- package/js/ln-table/ln-table-sort.js +1 -0
- package/js/ln-table/ln-table.js +1 -0
- package/js/ln-table/ln-table.scss +11 -0
- package/js/ln-table/src/ln-table-sort.js +168 -0
- package/js/ln-table/src/ln-table.js +473 -0
- package/js/ln-tabs/README.md +137 -0
- package/js/ln-tabs/ln-tabs.js +1 -0
- package/js/ln-tabs/src/ln-tabs.js +171 -0
- package/js/ln-time/README.md +81 -0
- package/js/ln-time/ln-time.js +1 -0
- package/js/ln-time/src/ln-time.js +192 -0
- package/js/ln-toast/README.md +122 -0
- package/js/ln-toast/ln-toast.js +15 -0
- package/js/ln-toast/src/ln-toast.js +210 -0
- package/js/ln-toast/template.html +14 -0
- package/js/ln-toggle/README.md +137 -0
- package/js/ln-toggle/ln-toggle.js +1 -0
- package/js/ln-toggle/src/ln-toggle.js +139 -0
- package/js/ln-tooltip/README.md +58 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -0
- package/js/ln-tooltip/ln-tooltip.scss +9 -0
- package/js/ln-tooltip/src/ln-tooltip.js +169 -0
- package/js/ln-translations/README.md +96 -0
- package/js/ln-translations/ln-translations.js +1 -0
- package/js/ln-translations/src/ln-translations.js +275 -0
- package/js/ln-upload/README.md +180 -0
- package/js/ln-upload/ln-upload.js +1 -0
- package/js/ln-upload/ln-upload.scss +20 -0
- package/js/ln-upload/src/ln-upload.js +407 -0
- package/js/ln-validate/README.md +108 -0
- package/js/ln-validate/ln-validate.js +1 -0
- package/js/ln-validate/src/ln-validate.js +160 -0
- package/package.json +55 -0
- package/scss/base/_global.scss +83 -0
- package/scss/base/_reset.scss +17 -0
- package/scss/base/_typography.scss +125 -0
- package/scss/components/_accordion.scss +34 -0
- package/scss/components/_ajax.scss +15 -0
- package/scss/components/_alert.scss +5 -0
- package/scss/components/_app-shell.scss +15 -0
- package/scss/components/_avatar.scss +6 -0
- package/scss/components/_breadcrumbs.scss +33 -0
- package/scss/components/_button.scss +20 -0
- package/scss/components/_card.scss +10 -0
- package/scss/components/_chip.scss +5 -0
- package/scss/components/_circular-progress.scss +29 -0
- package/scss/components/_confirm.scss +5 -0
- package/scss/components/_data-table.scss +83 -0
- package/scss/components/_dropdown.scss +25 -0
- package/scss/components/_empty-state.scss +22 -0
- package/scss/components/_form.scss +100 -0
- package/scss/components/_layout.scss +8 -0
- package/scss/components/_link.scss +11 -0
- package/scss/components/_ln-table.scss +60 -0
- package/scss/components/_loader.scss +6 -0
- package/scss/components/_modal.scss +20 -0
- package/scss/components/_nav.scss +9 -0
- package/scss/components/_page-header.scss +10 -0
- package/scss/components/_popover.scss +10 -0
- package/scss/components/_progress.scss +17 -0
- package/scss/components/_prose.scss +5 -0
- package/scss/components/_scrollbar.scss +32 -0
- package/scss/components/_sections.scss +12 -0
- package/scss/components/_sidebar.scss +5 -0
- package/scss/components/_stat-card.scss +5 -0
- package/scss/components/_status-badge.scss +4 -0
- package/scss/components/_stepper.scss +5 -0
- package/scss/components/_table.scss +19 -0
- package/scss/components/_tabs.scss +21 -0
- package/scss/components/_timeline.scss +14 -0
- package/scss/components/_toast.scss +41 -0
- package/scss/components/_toggle.scss +81 -0
- package/scss/components/_tooltip.scss +18 -0
- package/scss/components/_translations.scss +111 -0
- package/scss/components/_upload.scss +51 -0
- package/scss/config/_breakpoints.scss +72 -0
- package/scss/config/_density.scss +117 -0
- package/scss/config/_icons.scss +37 -0
- package/scss/config/_mixins.scss +13 -0
- package/scss/config/_theme.scss +216 -0
- package/scss/config/_tokens.scss +419 -0
- package/scss/config/mixins/_accordion.scss +52 -0
- package/scss/config/mixins/_ajax.scss +39 -0
- package/scss/config/mixins/_alert.scss +82 -0
- package/scss/config/mixins/_app-shell.scss +312 -0
- package/scss/config/mixins/_avatar.scss +109 -0
- package/scss/config/mixins/_borders.scss +36 -0
- package/scss/config/mixins/_breadcrumbs.scss +72 -0
- package/scss/config/mixins/_breakpoints.scss +62 -0
- package/scss/config/mixins/_btn.scss +179 -0
- package/scss/config/mixins/_card.scss +338 -0
- package/scss/config/mixins/_chip.scss +66 -0
- package/scss/config/mixins/_circular-progress.scss +71 -0
- package/scss/config/mixins/_collapsible.scss +24 -0
- package/scss/config/mixins/_colors.scss +46 -0
- package/scss/config/mixins/_confirm.scss +31 -0
- package/scss/config/mixins/_data-table.scss +346 -0
- package/scss/config/mixins/_display.scss +32 -0
- package/scss/config/mixins/_dropdown.scss +143 -0
- package/scss/config/mixins/_empty-state.scss +30 -0
- package/scss/config/mixins/_focus.scss +55 -0
- package/scss/config/mixins/_footer.scss +42 -0
- package/scss/config/mixins/_form.scss +601 -0
- package/scss/config/mixins/_index.scss +58 -0
- package/scss/config/mixins/_interaction.scss +15 -0
- package/scss/config/mixins/_kbd.scss +22 -0
- package/scss/config/mixins/_layout.scss +117 -0
- package/scss/config/mixins/_link.scss +55 -0
- package/scss/config/mixins/_ln-table.scss +420 -0
- package/scss/config/mixins/_loader.scss +26 -0
- package/scss/config/mixins/_modal.scss +66 -0
- package/scss/config/mixins/_motion.scss +19 -0
- package/scss/config/mixins/_nav.scss +273 -0
- package/scss/config/mixins/_page-header.scss +69 -0
- package/scss/config/mixins/_popover.scss +25 -0
- package/scss/config/mixins/_position.scss +32 -0
- package/scss/config/mixins/_progress.scss +56 -0
- package/scss/config/mixins/_prose.scss +127 -0
- package/scss/config/mixins/_shadows.scss +8 -0
- package/scss/config/mixins/_sidebar.scss +95 -0
- package/scss/config/mixins/_sizing.scss +6 -0
- package/scss/config/mixins/_spacing.scss +19 -0
- package/scss/config/mixins/_stat-card.scss +68 -0
- package/scss/config/mixins/_status-badge.scss +83 -0
- package/scss/config/mixins/_stepper.scss +78 -0
- package/scss/config/mixins/_table.scss +215 -0
- package/scss/config/mixins/_tabs.scss +64 -0
- package/scss/config/mixins/_timeline.scss +69 -0
- package/scss/config/mixins/_toast.scss +148 -0
- package/scss/config/mixins/_tooltip.scss +111 -0
- package/scss/config/mixins/_transitions.scss +10 -0
- package/scss/config/mixins/_translations.scss +124 -0
- package/scss/config/mixins/_typography.scss +57 -0
- package/scss/config/mixins/_upload.scss +168 -0
- package/scss/ln-ashlar.scss +62 -0
- package/scss/tabler-icons.txt +5039 -0
- package/scss/utilities/_animations.scss +83 -0
- package/scss/utilities/_utilities.scss +49 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// ─── Positioning ───────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Pure helpers for floating UI (popovers, tooltips, dropdowns).
|
|
4
|
+
// No DOM side effects in computePlacement — it takes a rect and
|
|
5
|
+
// returns coordinates. teleportToBody and measureHidden touch DOM
|
|
6
|
+
// because they have to.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compute viewport coordinates for a floating element relative to an
|
|
10
|
+
* anchor rect. Tries the preferred placement first, then opposite,
|
|
11
|
+
* then the perpendicular pair, then pins to viewport edge.
|
|
12
|
+
*
|
|
13
|
+
* Returns { top, left, placement } in viewport pixels. `placement`
|
|
14
|
+
* is the side that won (one of 'top' | 'bottom' | 'left' | 'right').
|
|
15
|
+
*
|
|
16
|
+
* Accepts floating-ui-style placement strings:
|
|
17
|
+
* '<side>' — side only, alignment defaults to 'center'
|
|
18
|
+
* '<side>-start' — aligned to start (left for top/bottom, top for left/right)
|
|
19
|
+
* '<side>-end' — aligned to end (right for top/bottom, bottom for left/right)
|
|
20
|
+
*
|
|
21
|
+
* Alignment is preserved through the fallback chain (e.g. 'bottom-end' → 'top-end'
|
|
22
|
+
* if no room below). The returned `placement` reports the winning side only,
|
|
23
|
+
* not the alignment (e.g. 'bottom', not 'bottom-end') — backward compatible with
|
|
24
|
+
* all existing callers that pass bare 'top' / 'bottom' / 'left' / 'right'.
|
|
25
|
+
*
|
|
26
|
+
* Edge cases:
|
|
27
|
+
* - Anchor scrolled off-screen: returns coordinates relative to viewport
|
|
28
|
+
* anyway; caller decides whether to hide.
|
|
29
|
+
* - Floating element larger than viewport on an axis: pins to 0 on that
|
|
30
|
+
* axis and accepts overflow.
|
|
31
|
+
* - Zero-sized anchor: treated as a point at its top/left.
|
|
32
|
+
*
|
|
33
|
+
* @param {DOMRect|{top:number,left:number,right:number,bottom:number,width:number,height:number}} anchorRect
|
|
34
|
+
* @param {{width:number, height:number}} floatingSize
|
|
35
|
+
* @param {'top'|'bottom'|'left'|'right'|'top-start'|'top-end'|'bottom-start'|'bottom-end'|'left-start'|'left-end'|'right-start'|'right-end'} preferred
|
|
36
|
+
* @param {number} offset Gap in pixels between anchor and floating element.
|
|
37
|
+
* @returns {{top:number, left:number, placement:string}}
|
|
38
|
+
*/
|
|
39
|
+
export function computePlacement(anchorRect, floatingSize, preferred, offset) {
|
|
40
|
+
const gap = typeof offset === 'number' ? offset : 4;
|
|
41
|
+
const vw = window.innerWidth;
|
|
42
|
+
const vh = window.innerHeight;
|
|
43
|
+
const fw = floatingSize.width;
|
|
44
|
+
const fh = floatingSize.height;
|
|
45
|
+
|
|
46
|
+
// Parse '<side>' or '<side>-<alignment>' placement string.
|
|
47
|
+
const parts = (preferred || 'bottom').split('-');
|
|
48
|
+
const side0 = parts[0];
|
|
49
|
+
// 'start' | 'end' | 'center' (default when no suffix)
|
|
50
|
+
const alignment = (parts[1] === 'start' || parts[1] === 'end') ? parts[1] : 'center';
|
|
51
|
+
|
|
52
|
+
// Fallback chain for each preferred side.
|
|
53
|
+
const chains = {
|
|
54
|
+
top: ['top', 'bottom', 'right', 'left'],
|
|
55
|
+
bottom: ['bottom', 'top', 'right', 'left'],
|
|
56
|
+
left: ['left', 'right', 'top', 'bottom'],
|
|
57
|
+
right: ['right', 'left', 'top', 'bottom']
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const chain = chains[side0] || chains.bottom;
|
|
61
|
+
|
|
62
|
+
// Compute the cross-axis (alignment) offset for a given side.
|
|
63
|
+
// For top/bottom sides the cross axis is horizontal (left).
|
|
64
|
+
// For left/right sides the cross axis is vertical (top).
|
|
65
|
+
function crossOffset(side) {
|
|
66
|
+
if (side === 'top' || side === 'bottom') {
|
|
67
|
+
if (alignment === 'start') return anchorRect.left;
|
|
68
|
+
if (alignment === 'end') return anchorRect.right - fw;
|
|
69
|
+
// center (default)
|
|
70
|
+
return anchorRect.left + (anchorRect.width - fw) / 2;
|
|
71
|
+
} else {
|
|
72
|
+
// left / right
|
|
73
|
+
if (alignment === 'start') return anchorRect.top;
|
|
74
|
+
if (alignment === 'end') return anchorRect.bottom - fh;
|
|
75
|
+
// center (default)
|
|
76
|
+
return anchorRect.top + (anchorRect.height - fh) / 2;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function tryPlace(side) {
|
|
81
|
+
let top;
|
|
82
|
+
let left;
|
|
83
|
+
let fits = true;
|
|
84
|
+
|
|
85
|
+
if (side === 'top') {
|
|
86
|
+
top = anchorRect.top - gap - fh;
|
|
87
|
+
left = crossOffset(side);
|
|
88
|
+
if (top < 0) fits = false;
|
|
89
|
+
} else if (side === 'bottom') {
|
|
90
|
+
top = anchorRect.bottom + gap;
|
|
91
|
+
left = crossOffset(side);
|
|
92
|
+
if (top + fh > vh) fits = false;
|
|
93
|
+
} else if (side === 'left') {
|
|
94
|
+
top = crossOffset(side);
|
|
95
|
+
left = anchorRect.left - gap - fw;
|
|
96
|
+
if (left < 0) fits = false;
|
|
97
|
+
} else { // right
|
|
98
|
+
top = crossOffset(side);
|
|
99
|
+
left = anchorRect.right + gap;
|
|
100
|
+
if (left + fw > vw) fits = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Cross-axis fit check (don't reject; we'll clamp later)
|
|
104
|
+
// We only treat the main axis as a hard "doesn't fit" signal.
|
|
105
|
+
return { top: top, left: left, side: side, fits: fits };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let chosen = null;
|
|
109
|
+
for (let i = 0; i < chain.length; i++) {
|
|
110
|
+
const r = tryPlace(chain[i]);
|
|
111
|
+
if (r.fits) { chosen = r; break; }
|
|
112
|
+
}
|
|
113
|
+
if (!chosen) {
|
|
114
|
+
// Nothing fits cleanly — use the preferred side and let clamping
|
|
115
|
+
// handle the overflow.
|
|
116
|
+
chosen = tryPlace(chain[0]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Clamp to viewport. If the floating element is larger than the
|
|
120
|
+
// viewport on an axis, pin to 0 and accept overflow.
|
|
121
|
+
let top = chosen.top;
|
|
122
|
+
let left = chosen.left;
|
|
123
|
+
|
|
124
|
+
if (fw >= vw) {
|
|
125
|
+
left = 0;
|
|
126
|
+
} else {
|
|
127
|
+
if (left < 0) left = 0;
|
|
128
|
+
if (left + fw > vw) left = vw - fw;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (fh >= vh) {
|
|
132
|
+
top = 0;
|
|
133
|
+
} else {
|
|
134
|
+
if (top < 0) top = 0;
|
|
135
|
+
if (top + fh > vh) top = vh - fh;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { top: top, left: left, placement: chosen.side };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Move an element into <body>, leaving a placeholder comment so
|
|
143
|
+
* teleportBack can put it back exactly where it was. Returns a
|
|
144
|
+
* cleanup function — call it to restore the element to its origin.
|
|
145
|
+
*
|
|
146
|
+
* The element receives `position: fixed` style automatically (the
|
|
147
|
+
* caller is responsible for top/left). To avoid violating the
|
|
148
|
+
* "no inline styles" rule, the caller should set position via a
|
|
149
|
+
* CSS rule on its component selector (e.g. [data-ln-popover]
|
|
150
|
+
* already has position: fixed in its co-located scss). teleportToBody
|
|
151
|
+
* itself does NOT set any inline styles.
|
|
152
|
+
*
|
|
153
|
+
* @param {HTMLElement} el
|
|
154
|
+
* @returns {Function} cleanup — restores el to its original parent.
|
|
155
|
+
*/
|
|
156
|
+
export function teleportToBody(el) {
|
|
157
|
+
if (!el || el.parentNode === document.body) {
|
|
158
|
+
// Already in body or no element — return a no-op cleanup.
|
|
159
|
+
return function () {};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const originalParent = el.parentNode;
|
|
163
|
+
const placeholder = document.createComment('ln-teleport');
|
|
164
|
+
originalParent.insertBefore(placeholder, el);
|
|
165
|
+
document.body.appendChild(el);
|
|
166
|
+
|
|
167
|
+
return function restore() {
|
|
168
|
+
if (!placeholder.parentNode) return;
|
|
169
|
+
placeholder.parentNode.insertBefore(el, placeholder);
|
|
170
|
+
placeholder.parentNode.removeChild(placeholder);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Measure an element that may be hidden via display:none.
|
|
176
|
+
* Temporarily applies visibility:hidden + display:block so we can
|
|
177
|
+
* read offsetWidth/offsetHeight without flicker, then restores.
|
|
178
|
+
*
|
|
179
|
+
* Note: this does mutate inline style briefly (project rule allows
|
|
180
|
+
* temporary measurement; ln-dropdown uses the same pattern). Style
|
|
181
|
+
* is restored before the function returns.
|
|
182
|
+
*
|
|
183
|
+
* @param {HTMLElement} el
|
|
184
|
+
* @returns {{width:number, height:number}}
|
|
185
|
+
*/
|
|
186
|
+
export function measureHidden(el) {
|
|
187
|
+
if (!el) return { width: 0, height: 0 };
|
|
188
|
+
|
|
189
|
+
const cs = el.style;
|
|
190
|
+
const prevVisibility = cs.visibility;
|
|
191
|
+
const prevDisplay = cs.display;
|
|
192
|
+
const prevPosition = cs.position;
|
|
193
|
+
|
|
194
|
+
// Force a layout we can measure regardless of current display.
|
|
195
|
+
cs.visibility = 'hidden';
|
|
196
|
+
cs.display = 'block';
|
|
197
|
+
cs.position = 'fixed';
|
|
198
|
+
|
|
199
|
+
const width = el.offsetWidth;
|
|
200
|
+
const height = el.offsetHeight;
|
|
201
|
+
|
|
202
|
+
cs.visibility = prevVisibility;
|
|
203
|
+
cs.display = prevDisplay;
|
|
204
|
+
cs.position = prevPosition;
|
|
205
|
+
|
|
206
|
+
return { width: width, height: height };
|
|
207
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// ─── Shallow Reactive ──────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
export function reactiveState(initial, onChange) {
|
|
4
|
+
return new Proxy(Object.assign({}, initial), {
|
|
5
|
+
set(target, prop, value) {
|
|
6
|
+
const old = target[prop];
|
|
7
|
+
if (old === value) return true;
|
|
8
|
+
target[prop] = value;
|
|
9
|
+
onChange(prop, value, old);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ─── Deep Reactive ─────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const PROXY_FLAG = Symbol('deepReactive');
|
|
18
|
+
|
|
19
|
+
export function deepReactive(obj, onChange) {
|
|
20
|
+
function wrap(target) {
|
|
21
|
+
if (target === null || typeof target !== 'object') return target;
|
|
22
|
+
if (target[PROXY_FLAG]) return target;
|
|
23
|
+
|
|
24
|
+
// Recursively wrap existing nested objects/arrays
|
|
25
|
+
const keys = Object.keys(target);
|
|
26
|
+
for (let i = 0; i < keys.length; i++) {
|
|
27
|
+
const val = target[keys[i]];
|
|
28
|
+
if (val !== null && typeof val === 'object') {
|
|
29
|
+
target[keys[i]] = wrap(val);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new Proxy(target, {
|
|
34
|
+
get(t, prop) {
|
|
35
|
+
if (prop === PROXY_FLAG) return true;
|
|
36
|
+
return t[prop];
|
|
37
|
+
},
|
|
38
|
+
set(t, prop, value) {
|
|
39
|
+
const old = t[prop];
|
|
40
|
+
if (value !== null && typeof value === 'object') {
|
|
41
|
+
value = wrap(value);
|
|
42
|
+
}
|
|
43
|
+
t[prop] = value;
|
|
44
|
+
if (old !== value) onChange();
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
deleteProperty(t, prop) {
|
|
48
|
+
if (prop in t) {
|
|
49
|
+
delete t[prop];
|
|
50
|
+
onChange();
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return wrap(obj);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Render Batcher ────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export function createBatcher(renderFn, afterRender) {
|
|
63
|
+
let pending = false;
|
|
64
|
+
|
|
65
|
+
return function schedule() {
|
|
66
|
+
if (pending) return;
|
|
67
|
+
pending = true;
|
|
68
|
+
queueMicrotask(function () {
|
|
69
|
+
pending = false;
|
|
70
|
+
renderFn();
|
|
71
|
+
if (afterRender) afterRender();
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# `data-ln-couchdb-connector`
|
|
2
|
+
|
|
3
|
+
A zero-dependency, Local-First sync transport component that implements the Transport Gateway pattern of `ln-ashlar` for CouchDB and Sync Gateway instances.
|
|
4
|
+
|
|
5
|
+
This component encapsulates all connection parameters (CouchDB base URL, database name, auth credentials, headers) and provides a declarative, event-driven, or programmatic way to talk to any CouchDB-compatible backend using standard Changes Feed (`_changes`) protocols. It isolates networking concerns completely, making your cache store (`data-ln-data-store`) and visual presentation layers fully network-agnostic.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Declarative DOM Setup
|
|
10
|
+
|
|
11
|
+
Place the connector inside your parent coordinator element alongside your store:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<div data-ln-data-coordinator="tasks">
|
|
15
|
+
<!-- Storage Layer Cache (Blind to networking) -->
|
|
16
|
+
<div data-ln-data-store
|
|
17
|
+
data-ln-store-indexes="due_date,priority">
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Transport Gateway (CouchDB Connector) -->
|
|
21
|
+
<div data-ln-couchdb-connector
|
|
22
|
+
data-ln-couchdb-url="https://couch.livenetworks.com"
|
|
23
|
+
data-ln-couchdb-db="tasks"
|
|
24
|
+
data-ln-couchdb-auth="Basic dXNlcjpwYXNz">
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Attributes
|
|
32
|
+
|
|
33
|
+
All attributes are dynamically observed. Any runtime changes to these attributes instantly update the connector's internal configurations.
|
|
34
|
+
|
|
35
|
+
| Attribute | Category | Description |
|
|
36
|
+
|-----------|----------|-------------|
|
|
37
|
+
| `data-ln-couchdb-connector` | Selector | Creates the component instance. |
|
|
38
|
+
| `data-ln-couchdb-url` | Connection | The base URL of the CouchDB Server or Sync Gateway (e.g. `https://couch.example.com`). Fallbacks: `data-ln-couchdb-connector-url`, `data-ln-api-base-url`, `data-ln-rest-base-url`. |
|
|
39
|
+
| `data-ln-couchdb-db` | Connection | Database name endpoint (e.g. `tasks`). Fallbacks: `data-ln-couchdb-connector-db`, `data-ln-api-path`, `data-ln-rest-path`. |
|
|
40
|
+
| `data-ln-couchdb-auth` | Credentials | Authorization header value (e.g. `Basic dXNlcjpwYXNz` or standard token). |
|
|
41
|
+
| `data-ln-couchdb-headers` | Credentials | JSON-formatted string of custom request headers. |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## JavaScript API Methods
|
|
46
|
+
|
|
47
|
+
You can access the instance methods directly on the element via the `.lnCouchDbConnector` or `.lnConnector` properties:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const connectorEl = document.querySelector('[data-ln-couchdb-connector]');
|
|
51
|
+
const connector = connectorEl.lnCouchDbConnector; // or connectorEl.lnConnector
|
|
52
|
+
|
|
53
|
+
// 1. Fetch changed records since a sequence ID (CouchDB delta protocol)
|
|
54
|
+
connector.fetchDelta("1-g1AAA...")
|
|
55
|
+
.then(payload => {
|
|
56
|
+
console.log('Upserted docs:', payload.data);
|
|
57
|
+
console.log('Deleted IDs:', payload.deleted);
|
|
58
|
+
console.log('New sync seq:', payload.synced_at);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 2. Create a new document (maps payload to CouchDB _id if id is provided)
|
|
62
|
+
connector.create({ title: 'New task', priority: 'High' })
|
|
63
|
+
.then(doc => console.log('Created CouchDB document:', doc));
|
|
64
|
+
|
|
65
|
+
// 3. Update an existing document (handles _rev fetching automatically if omitted)
|
|
66
|
+
connector.update("doc_123", { title: 'Updated Task Title' })
|
|
67
|
+
.then(doc => console.log('Updated CouchDB document:', doc));
|
|
68
|
+
|
|
69
|
+
// 4. Delete a document (handles _rev fetching automatically if omitted)
|
|
70
|
+
connector.delete("doc_123")
|
|
71
|
+
.then(res => console.log('Deleted successfully:', res));
|
|
72
|
+
|
|
73
|
+
// 5. Bulk Delete records (queries all revisions first, then performs _bulk_docs deletion)
|
|
74
|
+
connector.bulkDelete(["doc_17", "doc_23"])
|
|
75
|
+
.then(res => console.log('Bulk deleted result:', res));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## DOM Events
|
|
81
|
+
|
|
82
|
+
### Commands (Dispatched TO the connector)
|
|
83
|
+
|
|
84
|
+
You can trigger mutations and fetches asynchronously by dispatching standard events directly on the connector DOM element. All events are supported in `ln-couchdb-connector`, `ln-api-connector`, and `ln-rest-connector` namespaces for 100% backward compatibility.
|
|
85
|
+
|
|
86
|
+
| Event | `detail` Payload | Description |
|
|
87
|
+
|-------|------------------|-------------|
|
|
88
|
+
| `ln-couchdb-connector:request-sync` | `{ since }` | Triggers a Changes Feed fetch request. |
|
|
89
|
+
| `ln-couchdb-connector:request-create` | `{ data, tempId }` | Triggers a creation request. |
|
|
90
|
+
| `ln-couchdb-connector:request-update` | `{ id, data, expected_version }` | Triggers a PUT update (expected_version maps to CouchDB's revision). |
|
|
91
|
+
| `ln-couchdb-connector:request-delete` | `{ id, rev }` | Triggers a deletion request. |
|
|
92
|
+
| `ln-couchdb-connector:request-bulk-delete` | `{ ids }` | Triggers a bulk-deletion request. |
|
|
93
|
+
|
|
94
|
+
### Notifications (Emitted BY the connector)
|
|
95
|
+
|
|
96
|
+
The connector dispatches bubbles-enabled events to notify parent coordinators of response states.
|
|
97
|
+
|
|
98
|
+
| Event | `detail` Payload | Description |
|
|
99
|
+
|-------|------------------|-------------|
|
|
100
|
+
| `ln-couchdb-connector:fetched` | `{ data, since }` | Dispatched upon successful fetch completion. |
|
|
101
|
+
| `ln-couchdb-connector:created` | `{ record, tempId }` | Dispatched upon successful server record confirmation. |
|
|
102
|
+
| `ln-couchdb-connector:updated` | `{ record, id }` | Dispatched upon successful server update confirmation. |
|
|
103
|
+
| `ln-couchdb-connector:deleted` | `{ response, id }` | Dispatched upon successful server deletion. |
|
|
104
|
+
| `ln-couchdb-connector:bulk-deleted`| `{ response, ids }` | Dispatched upon successful server bulk deletion. |
|
|
105
|
+
| `ln-couchdb-connector:error` | `{ action, error, status, ... }` | Dispatched on failures. Includes `conflictData` if status is 409. |
|
|
106
|
+
| `ln-couchdb-connector:config-changed` | `{ url, db, auth, headers }` | Dispatched when configuration attributes are mutated. |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 3-Tier Integration Architecture (Coordinator Example)
|
|
111
|
+
|
|
112
|
+
Here is how a parent Coordinator links a Cache Store and the CouchDB Connector using standard DOM events:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
(function () {
|
|
116
|
+
const parent = document.querySelector('[data-ln-data-coordinator="tasks"]');
|
|
117
|
+
const storeEl = parent.querySelector('[data-ln-data-store]');
|
|
118
|
+
const connectorEl = parent.querySelector('[data-ln-couchdb-connector]');
|
|
119
|
+
|
|
120
|
+
if (!storeEl || !connectorEl) return;
|
|
121
|
+
|
|
122
|
+
// 1. Storage needs remote sync -> forward request to CouchDB Connector
|
|
123
|
+
storeEl.addEventListener('ln-store:request-remote-sync', function (e) {
|
|
124
|
+
connectorEl.dispatchEvent(new CustomEvent('ln-couchdb-connector:request-sync', {
|
|
125
|
+
detail: { since: e.detail.since }
|
|
126
|
+
}));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// 2. CouchDB Connector finishes changes feed sync -> feed delta back to Storage
|
|
130
|
+
connectorEl.addEventListener('ln-couchdb-connector:fetched', function (e) {
|
|
131
|
+
const payload = e.detail.data;
|
|
132
|
+
// payload: { data: Array, deleted: Array, synced_at: String }
|
|
133
|
+
storeEl.lnStore.applySync(payload.data, payload.deleted || [], payload.synced_at);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// 3. Storage requests remote creation -> forward payload to CouchDB Connector
|
|
137
|
+
storeEl.addEventListener('ln-store:request-remote-create', function (e) {
|
|
138
|
+
connectorEl.dispatchEvent(new CustomEvent('ln-couchdb-connector:request-create', {
|
|
139
|
+
detail: { data: e.detail.data, tempId: e.detail.tempId }
|
|
140
|
+
}));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 4. CouchDB Connector confirms creation -> swap optimistic temp records in Storage
|
|
144
|
+
connectorEl.addEventListener('ln-couchdb-connector:created', function (e) {
|
|
145
|
+
storeEl.lnStore.confirmMutation(e.detail.tempId, e.detail.record, 'create');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// 5. In case of network errors -> trigger rollback in Cache Store
|
|
149
|
+
connectorEl.addEventListener('ln-couchdb-connector:error', function (e) {
|
|
150
|
+
if (e.detail.action === 'create') {
|
|
151
|
+
storeEl.lnStore.revertMutation(e.detail.tempId, 'create', e.detail.error);
|
|
152
|
+
}
|
|
153
|
+
// ... handle updates and deletions similarly
|
|
154
|
+
});
|
|
155
|
+
})();
|
|
156
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function l(i,d,a){i.dispatchEvent(new CustomEvent(d,{bubbles:!0,detail:a||{}}))}function E(i,d){if(!document.body){document.addEventListener("DOMContentLoaded",function(){E(i,d)}),console.warn("["+d+'] Script loaded before <body> — add "defer" to your <script> tag');return}i()}function v(i,d,a,h){if(i.nodeType!==1)return;const g=d.indexOf("[")!==-1||d.indexOf(".")!==-1||d.indexOf("#")!==-1?d:"["+d+"]",e=Array.from(i.querySelectorAll(g));i.matches&&i.matches(g)&&e.push(i);for(const o of e)o[a]||(o[a]=new h(o))}function O(i,d,a,h,p={}){const g=p.extraAttributes||[],e=p.onAttributeChange||null,o=p.onInit||null;function t(r){const n=r||document.body;v(n,i,d,a),o&&o(n)}return E(function(){const r=new MutationObserver(function(c){for(let s=0;s<c.length;s++){const u=c[s];if(u.type==="childList")for(let w=0;w<u.addedNodes.length;w++){const f=u.addedNodes[w];f.nodeType===1&&(v(f,i,d,a),o&&o(f))}else u.type==="attributes"&&(e&&u.target[d]?e(u.target,u.attributeName):(v(u.target,i,d,a),o&&o(u.target)))}});let n=[];if(i.indexOf("[")!==-1){const c=/\[([\w-]+)/g;let s;for(;(s=c.exec(i))!==null;)n.push(s[1])}else n.push(i);r.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:n.concat(g)})},h),window[d]=t,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){t(document.body)}):t(document.body),t}function b(...i){return i.filter(d=>d!=null&&d!=="").map((d,a)=>a===0?d.replace(/\/+$/,""):d.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function m(i,d){return Object.assign({"Content-Type":"application/json",Accept:"application/json"},i,d?{Authorization:d}:null)}function C(i,d="ln-core"){try{return i?JSON.parse(i):{}}catch(a){return console.error(`[${d}] Invalid headers JSON:`,a),{}}}const T={};function k(i,d){T[i]=d}function j(i){return T[i]||{ingress:d=>d,egress:d=>d}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=k,window.lnCore.getDataMapper=j),(function(){const i="data-ln-couchdb-connector",d="lnCouchDbConnector",a="lnConnector";if(window[d]!==void 0)return;function h(e){return this.dom=e,e[d]=this,e[a]=this,this.refreshConfig(),this._handlers=null,p(this),this}h.prototype.refreshConfig=function(){const e=this.dom;this.url=e.getAttribute("data-ln-couchdb-url")||"",this.db=e.getAttribute("data-ln-couchdb-db")||"",this.auth=e.getAttribute("data-ln-couchdb-auth")||"",this.credentials="same-origin";const o=e.getAttribute("data-ln-couchdb-headers")||"";this.headers=C(o,"ln-couchdb-connector"),this.auth&&console.warn("[ln-couchdb-connector] Security Warning: Sensitive authorization credentials detected in data-ln-couchdb-auth attribute. Storing basic authentication credentials in HTML DOM attributes is highly discouraged and vulnerable to XSS credential extraction. Please use HttpOnly session cookies or a Backend Proxy Gateway instead."),o.toLowerCase().includes("authorization")&&console.warn("[ln-couchdb-connector] Security Warning: Sensitive authorization credentials detected in data-ln-couchdb-headers attribute. Please use HttpOnly session cookies or a Backend Proxy Gateway instead."),l(e,"ln-couchdb-connector:config-changed",{url:this.url,db:this.db,auth:this.auth?"[REDACTED]":"",headers:this.headers})},h.prototype.fetchDelta=function(e){const o=this,t=["include_docs=true","feed=normal"];e&&t.push("since="+encodeURIComponent(e));const r=b(o.url,o.db,"_changes")+"?"+t.join("&");return window.fetch(r,{method:"GET",headers:m(o.headers,o.auth),credentials:o.credentials}).then(n=>{if(!n.ok)throw new Error("HTTP "+n.status+": "+n.statusText);return n.json()}).then(n=>{const c=n.results||[];return{data:c.filter(s=>!s.deleted&&s.doc).map(s=>Object.assign({},s.doc,{id:s.doc._id})),deleted:c.filter(s=>s.deleted).map(s=>s.id),synced_at:n.last_seq||e||""}})},h.prototype.create=function(e){const o=this,t=Object.assign({_id:e.id},e);return t._id||delete t._id,window.fetch(b(o.url,o.db),{method:"POST",headers:m(o.headers,o.auth),credentials:o.credentials,body:JSON.stringify(t)}).then(r=>{if(!r.ok)throw new Error("HTTP "+r.status+": "+r.statusText);return r.json()}).then(r=>Object.assign({},t,{id:r.id,_id:r.id,_rev:r.rev}))},h.prototype.update=function(e,o){const t=this,r=Object.assign({id:String(e),_id:String(e)},o),n=r._rev||r.rev;return(n?Promise.resolve(n):window.fetch(b(t.url,t.db,null,e),{method:"GET",headers:m(t.headers,t.auth),credentials:t.credentials}).then(s=>{if(!s.ok)throw new Error("Could not retrieve document for revision mapping");return s.json().then(u=>u._rev)})).then(s=>{const u=Object.assign({},r,{_rev:s});delete u.rev;const w=Object.assign(m(t.headers,t.auth),{"If-Match":s});return window.fetch(b(t.url,t.db,null,e),{method:"PUT",headers:w,credentials:t.credentials,body:JSON.stringify(u)}).then(f=>{if(f.ok)return f.json().then(y=>Object.assign({},u,{_rev:y.rev}));if(f.status===409)return f.json().then(y=>{const _=new Error("Conflict");throw _.status=409,_.data=y,_});throw new Error("HTTP "+f.status+": "+f.statusText)})})},h.prototype.delete=function(e,o){const t=this;return(o?Promise.resolve(o):window.fetch(b(t.url,t.db,null,e),{method:"GET",headers:m(t.headers,t.auth),credentials:t.credentials}).then(n=>{if(!n.ok)throw new Error("Could not retrieve document for revision delete");return n.json().then(c=>c._rev)})).then(n=>{const c=b(t.url,t.db,null,e)+"?rev="+encodeURIComponent(n);return window.fetch(c,{method:"DELETE",headers:m(t.headers,t.auth),credentials:t.credentials}).then(s=>{if(!s.ok)throw new Error("HTTP "+s.status+": "+s.statusText);return s.json()})})},h.prototype.bulkDelete=function(e){const o=this;return!e||e.length===0?Promise.resolve({ok:!0,deletedCount:0}):window.fetch(b(o.url,o.db,"_all_docs"),{method:"POST",headers:m(o.headers,o.auth),credentials:o.credentials,body:JSON.stringify({keys:e})}).then(t=>{if(!t.ok)throw new Error("HTTP "+t.status+": "+t.statusText);return t.json()}).then(t=>{const n=(t.rows||[]).filter(c=>!c.error&&c.value&&c.value.rev).map(c=>({_id:c.id,_rev:c.value.rev,_deleted:!0}));return n.length===0?{ok:!0,deletedCount:0}:window.fetch(b(o.url,o.db,"_bulk_docs"),{method:"POST",headers:m(o.headers,o.auth),credentials:o.credentials,body:JSON.stringify({docs:n})}).then(c=>{if(!c.ok)throw new Error("HTTP "+c.status+": "+c.statusText);return c.json()}).then(c=>({ok:!0,results:c,deletedCount:n.length}))})};function p(e){e._handlers={sync:function(t){const r=t.detail||{};e.fetchDelta(r.since).then(function(n){l(e.dom,"ln-couchdb-connector:fetched",{data:n,since:r.since})}).catch(function(n){l(e.dom,"ln-couchdb-connector:error",{action:"sync",error:n.message,status:n.status||0,since:r.since})})},create:function(t){const r=t.detail||{};e.create(r.data).then(function(n){l(e.dom,"ln-couchdb-connector:created",{record:n,tempId:r.tempId})}).catch(function(n){l(e.dom,"ln-couchdb-connector:error",{action:"create",error:n.message,status:n.status||0,tempId:r.tempId})})},update:function(t){const r=t.detail||{},n=Object.assign({},r.data);r.expected_version!==void 0&&(n._rev=r.expected_version),e.update(r.id,n).then(function(c){l(e.dom,"ln-couchdb-connector:updated",{record:c,id:r.id})}).catch(function(c){l(e.dom,"ln-couchdb-connector:error",{action:"update",error:c.message,status:c.status||0,id:r.id,conflictData:c.status===409?c.data:null})})},delete:function(t){const r=t.detail||{};e.delete(r.id,r.rev).then(function(n){l(e.dom,"ln-couchdb-connector:deleted",{response:n,id:r.id})}).catch(function(n){l(e.dom,"ln-couchdb-connector:error",{action:"delete",error:n.message,status:n.status||0,id:r.id})})},bulkDelete:function(t){const r=t.detail||{};e.bulkDelete(r.ids).then(function(n){l(e.dom,"ln-couchdb-connector:bulk-deleted",{response:n,ids:r.ids})}).catch(function(n){l(e.dom,"ln-couchdb-connector:error",{action:"bulk-delete",error:n.message,status:n.status||0,ids:r.ids})})}},["ln-couchdb-connector","ln-api-connector","ln-rest-connector"].forEach(function(t){e.dom.addEventListener(t+":request-sync",e._handlers.sync),e.dom.addEventListener(t+":request-fetch",e._handlers.sync),e.dom.addEventListener(t+":request-create",e._handlers.create),e.dom.addEventListener(t+":request-update",e._handlers.update),e.dom.addEventListener(t+":request-delete",e._handlers.delete),e.dom.addEventListener(t+":request-bulk-delete",e._handlers.bulkDelete)})}h.prototype.destroy=function(){if(!this.dom[d])return;const e=this;e._handlers&&(["ln-couchdb-connector","ln-api-connector","ln-rest-connector"].forEach(function(t){e.dom.removeEventListener(t+":request-sync",e._handlers.sync),e.dom.removeEventListener(t+":request-fetch",e._handlers.sync),e.dom.removeEventListener(t+":request-create",e._handlers.create),e.dom.removeEventListener(t+":request-update",e._handlers.update),e.dom.removeEventListener(t+":request-delete",e._handlers.delete),e.dom.removeEventListener(t+":request-bulk-delete",e._handlers.bulkDelete)}),e._handlers=null),l(this.dom,"ln-couchdb-connector:destroyed",{target:this.dom}),delete this.dom[d],delete this.dom[a]};function g(e){const o=e[d];o&&o.refreshConfig()}O(i,d,h,"ln-couchdb-connector",{extraAttributes:["data-ln-couchdb-url","data-ln-couchdb-db","data-ln-couchdb-auth","data-ln-couchdb-headers"],onAttributeChange:g})})()})();
|