@ozankurt/file-chips 0.0.0-stage → 1.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +220 -2
- package/dist/esm/file-chips.mjs +200 -0
- package/dist/esm/file-chips.mjs.map +7 -0
- package/dist/file-chips.cjs +229 -0
- package/dist/file-chips.css +215 -0
- package/dist/file-chips.d.ts +43 -0
- package/dist/file-chips.js +206 -0
- package/dist/file-chips.js.map +7 -0
- package/dist/file-chips.min.css +1 -0
- package/dist/file-chips.min.js +2 -0
- package/dist/file-chips.min.js.map +7 -0
- package/package.json +73 -5
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// js/index.js
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
close: () => close,
|
|
23
|
+
default: () => index_default,
|
|
24
|
+
getOpen: () => getOpen,
|
|
25
|
+
init: () => init,
|
|
26
|
+
position: () => position
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// js/popover.js
|
|
31
|
+
var GAP = 8;
|
|
32
|
+
var EDGE = 8;
|
|
33
|
+
var openPopover = null;
|
|
34
|
+
var openChip = null;
|
|
35
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
36
|
+
var popoverFor = (chip) => chip.ownerDocument.getElementById(chip.dataset.fileChip);
|
|
37
|
+
var position = (chip, popover) => {
|
|
38
|
+
const rect = chip.getBoundingClientRect();
|
|
39
|
+
const width = popover.offsetWidth;
|
|
40
|
+
const height = popover.offsetHeight;
|
|
41
|
+
let top = rect.bottom + GAP;
|
|
42
|
+
const left = clamp(rect.left, EDGE, Math.max(EDGE, window.innerWidth - width - EDGE));
|
|
43
|
+
if (top + height > window.innerHeight) {
|
|
44
|
+
const above = rect.top - height - GAP;
|
|
45
|
+
if (above >= EDGE) {
|
|
46
|
+
top = above;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
top = clamp(top, EDGE, window.innerHeight - height - EDGE);
|
|
50
|
+
popover.style.top = "0px";
|
|
51
|
+
popover.style.left = "0px";
|
|
52
|
+
const origin = popover.getBoundingClientRect();
|
|
53
|
+
popover.style.top = `${Math.round(top - origin.top)}px`;
|
|
54
|
+
popover.style.left = `${Math.round(left - origin.left)}px`;
|
|
55
|
+
};
|
|
56
|
+
var close = ({ restoreFocus = false } = {}) => {
|
|
57
|
+
if (!openPopover) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const popover = openPopover;
|
|
61
|
+
const chip = openChip;
|
|
62
|
+
const hadFocus = popover.contains(popover.ownerDocument.activeElement);
|
|
63
|
+
popover.hidden = true;
|
|
64
|
+
chip?.setAttribute("aria-expanded", "false");
|
|
65
|
+
openPopover = null;
|
|
66
|
+
openChip = null;
|
|
67
|
+
if (restoreFocus && hadFocus) {
|
|
68
|
+
chip?.focus();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var open = (chip) => {
|
|
72
|
+
close();
|
|
73
|
+
const popover = popoverFor(chip);
|
|
74
|
+
if (!popover) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
popover.hidden = false;
|
|
78
|
+
chip.setAttribute("aria-expanded", "true");
|
|
79
|
+
openPopover = popover;
|
|
80
|
+
openChip = chip;
|
|
81
|
+
position(chip, popover);
|
|
82
|
+
if (typeof requestAnimationFrame === "function") {
|
|
83
|
+
requestAnimationFrame(() => openPopover === popover && position(chip, popover));
|
|
84
|
+
}
|
|
85
|
+
return popover;
|
|
86
|
+
};
|
|
87
|
+
var getOpen = () => openPopover;
|
|
88
|
+
var getOpenChip = () => openChip;
|
|
89
|
+
|
|
90
|
+
// js/marks.js
|
|
91
|
+
var DEFAULT_MARKED = "Marked";
|
|
92
|
+
var DEFAULT_UNMARKED = "Remove on save";
|
|
93
|
+
var targetFor = (chip) => chip.closest("[data-file-chips]")?.querySelector("[data-file-chips-bucket]") ?? chip.closest("form");
|
|
94
|
+
var findInput = (target, name, id) => [...target.querySelectorAll('input[type="hidden"]')].find((input) => input.name === name && input.value === id) ?? null;
|
|
95
|
+
var render = (chip, button, marked) => {
|
|
96
|
+
chip.classList.toggle("fc-chip--marked", marked);
|
|
97
|
+
button.classList.toggle("fc-btn--active", marked);
|
|
98
|
+
button.setAttribute("aria-pressed", String(marked));
|
|
99
|
+
const label = button.querySelector("[data-file-chip-remove-label]") ?? button;
|
|
100
|
+
label.textContent = marked ? button.dataset.labelMarked ?? DEFAULT_MARKED : button.dataset.labelUnmarked ?? DEFAULT_UNMARKED;
|
|
101
|
+
};
|
|
102
|
+
var buttonIn = (popover) => popover.querySelector("[data-file-chip-remove]");
|
|
103
|
+
var sync = (chip, popover) => {
|
|
104
|
+
const button = buttonIn(popover);
|
|
105
|
+
const target = targetFor(chip);
|
|
106
|
+
if (!button || !target) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
render(chip, button, findInput(target, button.dataset.fileChipInput, button.dataset.fileChipId) !== null);
|
|
110
|
+
};
|
|
111
|
+
var toggle = (chip, button) => {
|
|
112
|
+
const target = targetFor(chip);
|
|
113
|
+
if (!target) {
|
|
114
|
+
console.warn("[FileChips] No [data-file-chips-bucket] or <form> found for this chip; cannot mark it for removal.");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const name = button.dataset.fileChipInput;
|
|
118
|
+
const id = button.dataset.fileChipId;
|
|
119
|
+
const existing = findInput(target, name, id);
|
|
120
|
+
if (existing) {
|
|
121
|
+
existing.remove();
|
|
122
|
+
} else {
|
|
123
|
+
const input = chip.ownerDocument.createElement("input");
|
|
124
|
+
input.type = "hidden";
|
|
125
|
+
input.name = name;
|
|
126
|
+
input.value = id;
|
|
127
|
+
target.appendChild(input);
|
|
128
|
+
}
|
|
129
|
+
const marked = !existing;
|
|
130
|
+
render(chip, button, marked);
|
|
131
|
+
chip.dispatchEvent(new CustomEvent("file-chips:change", { bubbles: true, detail: { id, input: name, marked } }));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// js/index.js
|
|
135
|
+
var documents = /* @__PURE__ */ new Map();
|
|
136
|
+
var handled = (doc, element) => [...documents.get(doc)?.roots ?? []].some((root) => root.contains(element));
|
|
137
|
+
var openWithState = (chip) => {
|
|
138
|
+
const popover = open(chip);
|
|
139
|
+
if (popover) {
|
|
140
|
+
sync(chip, popover);
|
|
141
|
+
}
|
|
142
|
+
return popover;
|
|
143
|
+
};
|
|
144
|
+
var listenersFor = (doc) => ({
|
|
145
|
+
click(event) {
|
|
146
|
+
const target = event.target;
|
|
147
|
+
const remove = target.closest?.("[data-file-chip-remove]");
|
|
148
|
+
if (remove && getOpen()?.contains(remove)) {
|
|
149
|
+
toggle(getOpenChip(), remove);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const chip = target.closest?.("[data-file-chip]");
|
|
153
|
+
if (!chip || !handled(doc, chip)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
if (getOpenChip() === chip) {
|
|
158
|
+
close();
|
|
159
|
+
} else {
|
|
160
|
+
openWithState(chip);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
mousedown(event) {
|
|
164
|
+
const popover = getOpen();
|
|
165
|
+
if (popover && !popover.contains(event.target) && !event.target.closest?.("[data-file-chip]")) {
|
|
166
|
+
close();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
keydown(event) {
|
|
170
|
+
if (event.key === "Escape" && getOpen()) {
|
|
171
|
+
close({ restoreFocus: true });
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
reposition() {
|
|
175
|
+
const popover = getOpen();
|
|
176
|
+
if (popover) {
|
|
177
|
+
position(getOpenChip(), popover);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
var listen = (doc, listeners, method) => {
|
|
182
|
+
doc[method]("click", listeners.click);
|
|
183
|
+
doc[method]("mousedown", listeners.mousedown);
|
|
184
|
+
doc[method]("keydown", listeners.keydown);
|
|
185
|
+
doc.defaultView?.[method]("resize", listeners.reposition);
|
|
186
|
+
doc.defaultView?.[method]("scroll", listeners.reposition, true);
|
|
187
|
+
};
|
|
188
|
+
var init = ({ root = document } = {}) => {
|
|
189
|
+
const doc = root.ownerDocument ?? root;
|
|
190
|
+
if (handled(doc, root)) {
|
|
191
|
+
return () => {
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (!documents.has(doc)) {
|
|
195
|
+
const listeners = listenersFor(doc);
|
|
196
|
+
documents.set(doc, { roots: /* @__PURE__ */ new Set(), listeners });
|
|
197
|
+
listen(doc, listeners, "addEventListener");
|
|
198
|
+
}
|
|
199
|
+
const entry = documents.get(doc);
|
|
200
|
+
entry.roots.add(root);
|
|
201
|
+
return () => {
|
|
202
|
+
if (!entry.roots.delete(root)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (getOpenChip() && root.contains(getOpenChip())) {
|
|
206
|
+
close();
|
|
207
|
+
}
|
|
208
|
+
if (entry.roots.size === 0) {
|
|
209
|
+
listen(doc, entry.listeners, "removeEventListener");
|
|
210
|
+
documents.delete(doc);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
var FileChips = {
|
|
215
|
+
init,
|
|
216
|
+
open: openWithState,
|
|
217
|
+
close: () => close(),
|
|
218
|
+
getOpen,
|
|
219
|
+
position
|
|
220
|
+
};
|
|
221
|
+
var index_default = FileChips;
|
|
222
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
223
|
+
0 && (module.exports = {
|
|
224
|
+
close,
|
|
225
|
+
getOpen,
|
|
226
|
+
init,
|
|
227
|
+
position
|
|
228
|
+
});
|
|
229
|
+
module.exports = module.exports.default;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* File Chips
|
|
3
|
+
* Every colour, radius and size is a custom property, so apps can theme the
|
|
4
|
+
* component by overriding the variables on :root or on any ancestor.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
--fc-font-size: 0.875rem;
|
|
9
|
+
--fc-gap: 0.5rem;
|
|
10
|
+
--fc-radius: 0.5rem;
|
|
11
|
+
--fc-chip-max-width: 220px;
|
|
12
|
+
--fc-chip-bg: #fff;
|
|
13
|
+
--fc-chip-bg-hover: rgba(0, 0, 0, 0.03);
|
|
14
|
+
--fc-border: rgba(0, 0, 0, 0.1);
|
|
15
|
+
--fc-text: inherit;
|
|
16
|
+
--fc-muted: #6b7280;
|
|
17
|
+
--fc-danger: #dc3545;
|
|
18
|
+
--fc-danger-bg: rgba(220, 53, 69, 0.06);
|
|
19
|
+
--fc-primary: #2563eb;
|
|
20
|
+
--fc-primary-text: #fff;
|
|
21
|
+
--fc-focus: rgba(37, 99, 235, 0.35);
|
|
22
|
+
--fc-ext-bg: rgba(0, 0, 0, 0.06);
|
|
23
|
+
--fc-popover-width: 520px;
|
|
24
|
+
--fc-popover-bg: #fff;
|
|
25
|
+
--fc-popover-border: rgba(0, 0, 0, 0.12);
|
|
26
|
+
--fc-popover-radius: 0.75rem;
|
|
27
|
+
--fc-popover-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
|
28
|
+
--fc-popover-z: 1080;
|
|
29
|
+
--fc-thumb-size: 80px;
|
|
30
|
+
--fc-thumb-bg: #fafafa;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.fc-chips {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-wrap: wrap;
|
|
36
|
+
gap: var(--fc-gap);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.fc-chip {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 0.375rem;
|
|
43
|
+
max-width: var(--fc-chip-max-width);
|
|
44
|
+
padding: 0.375rem 0.5rem;
|
|
45
|
+
border: 1px solid var(--fc-border);
|
|
46
|
+
border-radius: var(--fc-radius);
|
|
47
|
+
background: var(--fc-chip-bg);
|
|
48
|
+
color: var(--fc-text);
|
|
49
|
+
font: inherit;
|
|
50
|
+
font-size: var(--fc-font-size);
|
|
51
|
+
line-height: 1.25;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.fc-chip:hover {
|
|
58
|
+
background: var(--fc-chip-bg-hover);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.fc-chip:focus-visible,
|
|
62
|
+
.fc-btn:focus-visible {
|
|
63
|
+
outline: none;
|
|
64
|
+
box-shadow: 0 0 0 3px var(--fc-focus);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.fc-chip--marked {
|
|
68
|
+
border-color: var(--fc-danger);
|
|
69
|
+
background: var(--fc-danger-bg);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.fc-chip--marked .fc-chip__name {
|
|
73
|
+
text-decoration: line-through;
|
|
74
|
+
opacity: 0.8;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.fc-chip__name,
|
|
78
|
+
.fc-popover__title {
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
text-overflow: ellipsis;
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.fc-chip__ext {
|
|
85
|
+
flex: none;
|
|
86
|
+
padding: 0.0625rem 0.3125rem;
|
|
87
|
+
border-radius: 0.25rem;
|
|
88
|
+
background: var(--fc-ext-bg);
|
|
89
|
+
font-size: 0.6875rem;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
letter-spacing: 0.02em;
|
|
92
|
+
text-transform: uppercase;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.fc-popover {
|
|
96
|
+
position: fixed;
|
|
97
|
+
z-index: var(--fc-popover-z);
|
|
98
|
+
width: var(--fc-popover-width);
|
|
99
|
+
max-width: 92vw;
|
|
100
|
+
padding: 1rem;
|
|
101
|
+
border: 1px solid var(--fc-popover-border);
|
|
102
|
+
border-radius: var(--fc-popover-radius);
|
|
103
|
+
background: var(--fc-popover-bg);
|
|
104
|
+
box-shadow: var(--fc-popover-shadow);
|
|
105
|
+
color: var(--fc-text);
|
|
106
|
+
font-size: var(--fc-font-size);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.fc-popover[hidden] {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.fc-popover__head {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: flex-start;
|
|
116
|
+
gap: 0.75rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.fc-popover__info {
|
|
120
|
+
min-width: 0;
|
|
121
|
+
flex: 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.fc-popover__title {
|
|
125
|
+
margin-bottom: 0.25rem;
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.fc-popover__meta,
|
|
130
|
+
.fc-popover__excerpt {
|
|
131
|
+
color: var(--fc-muted);
|
|
132
|
+
font-size: 0.8125rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.fc-popover__meta {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-wrap: wrap;
|
|
138
|
+
gap: 0.5rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.fc-popover__excerpt {
|
|
142
|
+
margin-top: 0.5rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.fc-popover__actions {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-wrap: wrap;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: 0.5rem;
|
|
150
|
+
margin-top: 0.75rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.fc-popover__actions [data-file-chip-remove] {
|
|
154
|
+
margin-left: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.fc-thumb {
|
|
158
|
+
display: flex;
|
|
159
|
+
flex: none;
|
|
160
|
+
align-items: center;
|
|
161
|
+
justify-content: center;
|
|
162
|
+
width: var(--fc-thumb-size);
|
|
163
|
+
height: var(--fc-thumb-size);
|
|
164
|
+
overflow: hidden;
|
|
165
|
+
border: 1px solid var(--fc-border);
|
|
166
|
+
border-radius: var(--fc-radius);
|
|
167
|
+
background: var(--fc-thumb-bg);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.fc-thumb img {
|
|
171
|
+
display: block;
|
|
172
|
+
max-width: 100%;
|
|
173
|
+
max-height: 100%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.fc-thumb .fc-chip__ext {
|
|
177
|
+
font-size: 0.875rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.fc-btn {
|
|
181
|
+
display: inline-flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
gap: 0.25rem;
|
|
184
|
+
padding: 0.25rem 0.625rem;
|
|
185
|
+
border: 1px solid transparent;
|
|
186
|
+
border-radius: 0.375rem;
|
|
187
|
+
background: transparent;
|
|
188
|
+
color: inherit;
|
|
189
|
+
font: inherit;
|
|
190
|
+
font-size: 0.8125rem;
|
|
191
|
+
line-height: 1.5;
|
|
192
|
+
text-decoration: none;
|
|
193
|
+
cursor: pointer;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.fc-btn--primary {
|
|
197
|
+
border-color: var(--fc-primary);
|
|
198
|
+
background: var(--fc-primary);
|
|
199
|
+
color: var(--fc-primary-text);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.fc-btn--outline {
|
|
203
|
+
border-color: var(--fc-primary);
|
|
204
|
+
color: var(--fc-primary);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.fc-btn--danger {
|
|
208
|
+
border-color: var(--fc-danger);
|
|
209
|
+
color: var(--fc-danger);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.fc-btn--danger.fc-btn--active {
|
|
213
|
+
background: var(--fc-danger);
|
|
214
|
+
color: #fff;
|
|
215
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Type declarations for @ozankurt/file-chips.
|
|
2
|
+
|
|
3
|
+
export interface InitOptions {
|
|
4
|
+
/** Handle chips inside this element only. Default: the whole document. */
|
|
5
|
+
root?: Document | Element;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Detail of the bubbling `file-chips:change` event dispatched on a chip. */
|
|
9
|
+
export interface FileChipChangeDetail {
|
|
10
|
+
id: string;
|
|
11
|
+
input: string;
|
|
12
|
+
marked: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Start handling chips inside `root`. A root that is already handled, itself
|
|
17
|
+
* or through an ancestor, is a no-op. Returns a function that stops handling it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function init(options?: InitOptions): () => void;
|
|
20
|
+
export declare function close(): void;
|
|
21
|
+
export declare function getOpen(): HTMLElement | null;
|
|
22
|
+
export declare function position(chip: HTMLElement, popover: HTMLElement): void;
|
|
23
|
+
|
|
24
|
+
declare const FileChips: {
|
|
25
|
+
init: typeof init;
|
|
26
|
+
/** Open a chip's popover; returns the popover, or null when it has none. */
|
|
27
|
+
open(chip: HTMLElement): HTMLElement | null;
|
|
28
|
+
close: typeof close;
|
|
29
|
+
getOpen: typeof getOpen;
|
|
30
|
+
position: typeof position;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default FileChips;
|
|
34
|
+
|
|
35
|
+
declare global {
|
|
36
|
+
interface HTMLElementEventMap {
|
|
37
|
+
'file-chips:change': CustomEvent<FileChipChangeDetail>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface DocumentEventMap {
|
|
41
|
+
'file-chips:change': CustomEvent<FileChipChangeDetail>;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
// js/popover.js
|
|
3
|
+
var GAP = 8;
|
|
4
|
+
var EDGE = 8;
|
|
5
|
+
var openPopover = null;
|
|
6
|
+
var openChip = null;
|
|
7
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
8
|
+
var popoverFor = (chip) => chip.ownerDocument.getElementById(chip.dataset.fileChip);
|
|
9
|
+
var position = (chip, popover) => {
|
|
10
|
+
const rect = chip.getBoundingClientRect();
|
|
11
|
+
const width = popover.offsetWidth;
|
|
12
|
+
const height = popover.offsetHeight;
|
|
13
|
+
let top = rect.bottom + GAP;
|
|
14
|
+
const left = clamp(rect.left, EDGE, Math.max(EDGE, window.innerWidth - width - EDGE));
|
|
15
|
+
if (top + height > window.innerHeight) {
|
|
16
|
+
const above = rect.top - height - GAP;
|
|
17
|
+
if (above >= EDGE) {
|
|
18
|
+
top = above;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
top = clamp(top, EDGE, window.innerHeight - height - EDGE);
|
|
22
|
+
popover.style.top = "0px";
|
|
23
|
+
popover.style.left = "0px";
|
|
24
|
+
const origin = popover.getBoundingClientRect();
|
|
25
|
+
popover.style.top = `${Math.round(top - origin.top)}px`;
|
|
26
|
+
popover.style.left = `${Math.round(left - origin.left)}px`;
|
|
27
|
+
};
|
|
28
|
+
var close = ({ restoreFocus = false } = {}) => {
|
|
29
|
+
if (!openPopover) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const popover = openPopover;
|
|
33
|
+
const chip = openChip;
|
|
34
|
+
const hadFocus = popover.contains(popover.ownerDocument.activeElement);
|
|
35
|
+
popover.hidden = true;
|
|
36
|
+
chip?.setAttribute("aria-expanded", "false");
|
|
37
|
+
openPopover = null;
|
|
38
|
+
openChip = null;
|
|
39
|
+
if (restoreFocus && hadFocus) {
|
|
40
|
+
chip?.focus();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var open = (chip) => {
|
|
44
|
+
close();
|
|
45
|
+
const popover = popoverFor(chip);
|
|
46
|
+
if (!popover) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
popover.hidden = false;
|
|
50
|
+
chip.setAttribute("aria-expanded", "true");
|
|
51
|
+
openPopover = popover;
|
|
52
|
+
openChip = chip;
|
|
53
|
+
position(chip, popover);
|
|
54
|
+
if (typeof requestAnimationFrame === "function") {
|
|
55
|
+
requestAnimationFrame(() => openPopover === popover && position(chip, popover));
|
|
56
|
+
}
|
|
57
|
+
return popover;
|
|
58
|
+
};
|
|
59
|
+
var getOpen = () => openPopover;
|
|
60
|
+
var getOpenChip = () => openChip;
|
|
61
|
+
|
|
62
|
+
// js/marks.js
|
|
63
|
+
var DEFAULT_MARKED = "Marked";
|
|
64
|
+
var DEFAULT_UNMARKED = "Remove on save";
|
|
65
|
+
var targetFor = (chip) => chip.closest("[data-file-chips]")?.querySelector("[data-file-chips-bucket]") ?? chip.closest("form");
|
|
66
|
+
var findInput = (target, name, id) => [...target.querySelectorAll('input[type="hidden"]')].find((input) => input.name === name && input.value === id) ?? null;
|
|
67
|
+
var render = (chip, button, marked) => {
|
|
68
|
+
chip.classList.toggle("fc-chip--marked", marked);
|
|
69
|
+
button.classList.toggle("fc-btn--active", marked);
|
|
70
|
+
button.setAttribute("aria-pressed", String(marked));
|
|
71
|
+
const label = button.querySelector("[data-file-chip-remove-label]") ?? button;
|
|
72
|
+
label.textContent = marked ? button.dataset.labelMarked ?? DEFAULT_MARKED : button.dataset.labelUnmarked ?? DEFAULT_UNMARKED;
|
|
73
|
+
};
|
|
74
|
+
var buttonIn = (popover) => popover.querySelector("[data-file-chip-remove]");
|
|
75
|
+
var sync = (chip, popover) => {
|
|
76
|
+
const button = buttonIn(popover);
|
|
77
|
+
const target = targetFor(chip);
|
|
78
|
+
if (!button || !target) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
render(chip, button, findInput(target, button.dataset.fileChipInput, button.dataset.fileChipId) !== null);
|
|
82
|
+
};
|
|
83
|
+
var toggle = (chip, button) => {
|
|
84
|
+
const target = targetFor(chip);
|
|
85
|
+
if (!target) {
|
|
86
|
+
console.warn("[FileChips] No [data-file-chips-bucket] or <form> found for this chip; cannot mark it for removal.");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const name = button.dataset.fileChipInput;
|
|
90
|
+
const id = button.dataset.fileChipId;
|
|
91
|
+
const existing = findInput(target, name, id);
|
|
92
|
+
if (existing) {
|
|
93
|
+
existing.remove();
|
|
94
|
+
} else {
|
|
95
|
+
const input = chip.ownerDocument.createElement("input");
|
|
96
|
+
input.type = "hidden";
|
|
97
|
+
input.name = name;
|
|
98
|
+
input.value = id;
|
|
99
|
+
target.appendChild(input);
|
|
100
|
+
}
|
|
101
|
+
const marked = !existing;
|
|
102
|
+
render(chip, button, marked);
|
|
103
|
+
chip.dispatchEvent(new CustomEvent("file-chips:change", { bubbles: true, detail: { id, input: name, marked } }));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// js/index.js
|
|
107
|
+
var documents = /* @__PURE__ */ new Map();
|
|
108
|
+
var handled = (doc, element) => [...documents.get(doc)?.roots ?? []].some((root) => root.contains(element));
|
|
109
|
+
var openWithState = (chip) => {
|
|
110
|
+
const popover = open(chip);
|
|
111
|
+
if (popover) {
|
|
112
|
+
sync(chip, popover);
|
|
113
|
+
}
|
|
114
|
+
return popover;
|
|
115
|
+
};
|
|
116
|
+
var listenersFor = (doc) => ({
|
|
117
|
+
click(event) {
|
|
118
|
+
const target = event.target;
|
|
119
|
+
const remove = target.closest?.("[data-file-chip-remove]");
|
|
120
|
+
if (remove && getOpen()?.contains(remove)) {
|
|
121
|
+
toggle(getOpenChip(), remove);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const chip = target.closest?.("[data-file-chip]");
|
|
125
|
+
if (!chip || !handled(doc, chip)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
if (getOpenChip() === chip) {
|
|
130
|
+
close();
|
|
131
|
+
} else {
|
|
132
|
+
openWithState(chip);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
mousedown(event) {
|
|
136
|
+
const popover = getOpen();
|
|
137
|
+
if (popover && !popover.contains(event.target) && !event.target.closest?.("[data-file-chip]")) {
|
|
138
|
+
close();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
keydown(event) {
|
|
142
|
+
if (event.key === "Escape" && getOpen()) {
|
|
143
|
+
close({ restoreFocus: true });
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
reposition() {
|
|
147
|
+
const popover = getOpen();
|
|
148
|
+
if (popover) {
|
|
149
|
+
position(getOpenChip(), popover);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
var listen = (doc, listeners, method) => {
|
|
154
|
+
doc[method]("click", listeners.click);
|
|
155
|
+
doc[method]("mousedown", listeners.mousedown);
|
|
156
|
+
doc[method]("keydown", listeners.keydown);
|
|
157
|
+
doc.defaultView?.[method]("resize", listeners.reposition);
|
|
158
|
+
doc.defaultView?.[method]("scroll", listeners.reposition, true);
|
|
159
|
+
};
|
|
160
|
+
var init = ({ root = document } = {}) => {
|
|
161
|
+
const doc = root.ownerDocument ?? root;
|
|
162
|
+
if (handled(doc, root)) {
|
|
163
|
+
return () => {
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (!documents.has(doc)) {
|
|
167
|
+
const listeners = listenersFor(doc);
|
|
168
|
+
documents.set(doc, { roots: /* @__PURE__ */ new Set(), listeners });
|
|
169
|
+
listen(doc, listeners, "addEventListener");
|
|
170
|
+
}
|
|
171
|
+
const entry = documents.get(doc);
|
|
172
|
+
entry.roots.add(root);
|
|
173
|
+
return () => {
|
|
174
|
+
if (!entry.roots.delete(root)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (getOpenChip() && root.contains(getOpenChip())) {
|
|
178
|
+
close();
|
|
179
|
+
}
|
|
180
|
+
if (entry.roots.size === 0) {
|
|
181
|
+
listen(doc, entry.listeners, "removeEventListener");
|
|
182
|
+
documents.delete(doc);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
var FileChips = {
|
|
187
|
+
init,
|
|
188
|
+
open: openWithState,
|
|
189
|
+
close: () => close(),
|
|
190
|
+
getOpen,
|
|
191
|
+
position
|
|
192
|
+
};
|
|
193
|
+
var index_default = FileChips;
|
|
194
|
+
|
|
195
|
+
// js/browser.js
|
|
196
|
+
window.FileChips = index_default;
|
|
197
|
+
var script = document.currentScript;
|
|
198
|
+
if (!script?.hasAttribute("data-manual")) {
|
|
199
|
+
if (document.readyState === "loading") {
|
|
200
|
+
document.addEventListener("DOMContentLoaded", () => index_default.init(), { once: true });
|
|
201
|
+
} else {
|
|
202
|
+
index_default.init();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
//# sourceMappingURL=file-chips.js.map
|