@richpods/coloeus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,55 @@
1
+ # Blue Oak Model License
2
+
3
+ Version 1.0.0
4
+
5
+ ## Purpose
6
+
7
+ This license gives everyone as much permission to work with
8
+ this software as possible, while protecting contributors
9
+ from liability.
10
+
11
+ ## Acceptance
12
+
13
+ In order to receive this license, you must agree to its
14
+ rules. The rules of this license are both obligations
15
+ under that agreement and conditions to your license.
16
+ You must not do anything with this software that triggers
17
+ a rule that you cannot or will not follow.
18
+
19
+ ## Copyright
20
+
21
+ Each contributor licenses you to do everything with this
22
+ software that would otherwise infringe that contributor's
23
+ copyright in it.
24
+
25
+ ## Notices
26
+
27
+ You must ensure that everyone who gets a copy of
28
+ any part of this software from you, with or without
29
+ changes, also gets the text of this license or a link to
30
+ <https://blueoakcouncil.org/license/1.0.0>.
31
+
32
+ ## Excuse
33
+
34
+ If anyone notifies you in writing that you have not
35
+ complied with [Notices](#notices), you can keep your
36
+ license by taking all practical steps to comply within 30
37
+ days after the notice. If you do not do so, your license
38
+ ends immediately.
39
+
40
+ ## Patent
41
+
42
+ Each contributor licenses you to do everything with this
43
+ software that would otherwise infringe any patent claims
44
+ they can license or become able to license.
45
+
46
+ ## Reliability
47
+
48
+ No contributor can revoke this license.
49
+
50
+ ## No Liability
51
+
52
+ ***As far as the law allows, this software comes as is,
53
+ without any warranty or condition, and no contributor
54
+ will be liable to anyone for any damages related to this
55
+ software or this license, under any kind of legal claim.***
@@ -0,0 +1 @@
1
+ .coloeus-poll{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;max-width:600px;margin:0 auto;padding:1.5rem;background:#fff;border-radius:8px;box-shadow:0 2px 8px #0000001a}.coloeus-poll__title{font-size:1.25rem;font-weight:600;margin:0 0 .5rem;color:#1a1a1a}.coloeus-poll__form{margin:0}.coloeus-poll__fieldset{border:none;margin:0;padding:0}.coloeus-poll__instructions{font-size:.875rem;color:#4b5563;margin:0 0 1rem}.coloeus-poll__instructions-note{display:block;margin-top:.25rem;font-size:.8125rem;color:#6b7280}.coloeus-poll__options{list-style:none;padding:0;margin:0}.coloeus-poll__option{position:relative;margin-bottom:.75rem;border-radius:6px;overflow:hidden;transition:transform .15s ease}.coloeus-poll__option:hover:not(.coloeus-poll__option--disabled){transform:translate(4px)}.coloeus-poll__option--selected{outline:none}.coloeus-poll__option--voted .coloeus-poll__option-content{background-color:#f0f9ff}.coloeus-poll__option--disabled{cursor:default}.coloeus-poll__option-label{display:block}.coloeus-poll__option-content{width:100%;display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.875rem 1rem;background:#f5f5f5;border:1px solid #e5e5e5;border-radius:6px;font-size:1rem;text-align:left;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease;position:relative;z-index:1;cursor:pointer;overflow:hidden}.coloeus-poll__option:not(.coloeus-poll__option--disabled) .coloeus-poll__option-content:hover{background:#ebebeb}.coloeus-poll__option--disabled .coloeus-poll__option-content{cursor:default;opacity:.9}.coloeus-poll__option--selected .coloeus-poll__option-content{border-color:#3b82f6;box-shadow:0 0 0 2px #3b82f640}.coloeus-poll__option-input{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.coloeus-poll__option-input:focus-visible+.coloeus-poll__option-content{box-shadow:0 0 0 3px #2563eb73}.coloeus-poll__option-progress{position:absolute;top:0;left:0;height:100%;background:#dbeafe;border-radius:6px;transition:width .3s ease;z-index:0}.coloeus-poll__option-text{flex:1;color:#1a1a1a}.coloeus-poll__option-stats{display:flex;align-items:center;gap:.5rem;font-size:.875rem;color:#666}.coloeus-poll__option-percentage{font-weight:600;color:#3b82f6}.coloeus-poll__sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.coloeus-poll__footer{margin-top:1rem;display:flex;align-items:center;justify-content:space-between}.coloeus-poll__vote-btn{padding:.75rem 1.5rem;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:1rem;font-weight:500;cursor:pointer;transition:background-color .15s ease}.coloeus-poll__vote-btn:hover:not(:disabled){background:#2563eb}.coloeus-poll__vote-btn:disabled{background:#94a3b8;cursor:not-allowed}.coloeus-poll__total{font-size:.875rem;color:#666}.coloeus-poll__status-group{display:flex;align-items:center;gap:.5rem}.coloeus-poll__error{margin-top:.75rem;padding:.75rem;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:#dc2626;font-size:.875rem}.coloeus-poll__loading{text-align:center;padding:2rem;color:#666}.coloeus-poll__status{display:inline-block;padding:.25rem .5rem;border-radius:4px;font-size:.75rem;font-weight:500}.coloeus-poll__status--active{background:#dcfce7;color:#166534}.coloeus-poll__status--open{background:#e0f2fe;color:#075985}.coloeus-poll__status--inactive{background:#fef3c7;color:#92400e}.coloeus-editor{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;max-width:600px;margin:0 auto;padding:1.5rem;background:#fff;border-radius:8px;box-shadow:0 2px 8px #0000001a}.coloeus-editor__title{font-size:1.25rem;font-weight:600;margin:0 0 1.5rem;color:#1a1a1a}.coloeus-editor__field{margin-bottom:1.25rem}.coloeus-editor__label{display:block;font-size:.875rem;font-weight:500;color:#374151;margin-bottom:.5rem}.coloeus-editor__input,.coloeus-editor__textarea{width:100%;padding:.75rem;border:1px solid #d1d5db;border-radius:6px;font-size:1rem;transition:border-color .15s ease;box-sizing:border-box}.coloeus-editor__input:focus,.coloeus-editor__textarea:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.coloeus-editor__options{list-style:none;padding:0;margin:0}.coloeus-editor__option{display:flex;gap:.5rem;margin-bottom:.5rem}.coloeus-editor__option-input{flex:1;padding:.625rem .75rem;border:1px solid #d1d5db;border-radius:6px;font-size:.9375rem}.coloeus-editor__option-input:focus{outline:none;border-color:#3b82f6}.coloeus-editor__remove-btn{padding:.625rem .875rem;background:#fee2e2;color:#dc2626;border:none;border-radius:6px;cursor:pointer;font-size:.875rem;transition:background-color .15s ease}.coloeus-editor__remove-btn:hover{background:#fecaca}.coloeus-editor__add-btn{margin-top:.5rem;padding:.625rem 1rem;background:#f3f4f6;color:#374151;border:1px dashed #d1d5db;border-radius:6px;cursor:pointer;font-size:.875rem;width:100%;transition:background-color .15s ease}.coloeus-editor__add-btn:hover{background:#e5e7eb}.coloeus-editor__actions{margin-top:1.5rem;display:flex;gap:.75rem}.coloeus-editor__submit-btn{flex:1;padding:.75rem 1.5rem;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:1rem;font-weight:500;cursor:pointer;transition:background-color .15s ease}.coloeus-editor__submit-btn:hover:not(:disabled){background:#2563eb}.coloeus-editor__submit-btn:disabled{background:#94a3b8;cursor:not-allowed}.coloeus-editor__cancel-btn{padding:.75rem 1.5rem;background:#f3f4f6;color:#374151;border:1px solid #d1d5db;border-radius:6px;font-size:1rem;cursor:pointer;transition:background-color .15s ease}.coloeus-editor__cancel-btn:hover{background:#e5e7eb}.coloeus-editor__error{margin-top:1rem;padding:.75rem;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:#dc2626;font-size:.875rem}
@@ -0,0 +1,595 @@
1
+ import { ref as g, computed as _, onMounted as H, watch as Y, defineComponent as K, createElementBlock as u, openBlock as d, Fragment as U, createElementVNode as s, createCommentVNode as E, withModifiers as x, toDisplayString as h, createTextVNode as N, renderList as Q, normalizeClass as ce, normalizeStyle as ue, withDirectives as j, vModelText as z } from "vue";
2
+ let C = {
3
+ apiUrl: ""
4
+ };
5
+ function po(t) {
6
+ C = { ...C, ...t };
7
+ }
8
+ function _o() {
9
+ return C;
10
+ }
11
+ async function J() {
12
+ const t = {
13
+ "Content-Type": "application/json"
14
+ };
15
+ if (C.getAuthToken) {
16
+ const l = await C.getAuthToken();
17
+ l && (t.Authorization = `Bearer ${l}`);
18
+ }
19
+ return t;
20
+ }
21
+ async function L(t) {
22
+ if (!t.ok) {
23
+ const l = await t.json().catch(() => ({ error: "Unknown error" }));
24
+ throw new Error(l.error || `HTTP error ${t.status}`);
25
+ }
26
+ return t.json();
27
+ }
28
+ async function de(t) {
29
+ const l = await fetch(`${C.apiUrl}/polls/${t}`, {
30
+ method: "GET",
31
+ headers: {
32
+ "Content-Type": "application/json"
33
+ }
34
+ });
35
+ return L(l);
36
+ }
37
+ async function pe(t, l) {
38
+ if (l.length === 0)
39
+ throw new Error("At least one option must be selected");
40
+ const c = await fetch(`${C.apiUrl}/polls/${t}/vote`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json"
44
+ },
45
+ body: JSON.stringify({ optionIndices: l })
46
+ });
47
+ return L(c);
48
+ }
49
+ async function _e(t) {
50
+ const l = await J(), c = await fetch(`${C.apiUrl}/admin/polls`, {
51
+ method: "POST",
52
+ headers: l,
53
+ body: JSON.stringify(t)
54
+ });
55
+ return L(c);
56
+ }
57
+ async function ve(t, l) {
58
+ const c = await J(), e = await fetch(`${C.apiUrl}/admin/polls/${t}`, {
59
+ method: "PUT",
60
+ headers: c,
61
+ body: JSON.stringify(l)
62
+ });
63
+ return L(e);
64
+ }
65
+ async function fe(t) {
66
+ const l = await J(), c = await fetch(`${C.apiUrl}/admin/polls/${t}`, {
67
+ method: "GET",
68
+ headers: l
69
+ });
70
+ return L(c);
71
+ }
72
+ function me(t) {
73
+ const l = g(null), c = g(!0), e = g(null), p = _(() => l.value?.totalVotes ?? 0), y = _(() => l.value?.isActive ?? !1), n = _(() => !l.value || p.value === 0 ? l.value?.options.map(() => 0) ?? [] : l.value.options.map(
74
+ (i) => Math.round(i.voteCount / p.value * 100)
75
+ ));
76
+ async function o() {
77
+ c.value = !0, e.value = null;
78
+ try {
79
+ l.value = await de(t);
80
+ } catch (i) {
81
+ e.value = i instanceof Error ? i.message : "Failed to load poll", l.value = null;
82
+ } finally {
83
+ c.value = !1;
84
+ }
85
+ }
86
+ function a(i) {
87
+ l.value = i;
88
+ }
89
+ return H(() => {
90
+ o();
91
+ }), {
92
+ poll: l,
93
+ loading: c,
94
+ error: e,
95
+ totalVotes: p,
96
+ isActive: y,
97
+ optionPercentages: n,
98
+ loadPoll: o,
99
+ updatePoll: a
100
+ };
101
+ }
102
+ const W = "coloeus-votes";
103
+ function he() {
104
+ try {
105
+ const t = localStorage.getItem(W);
106
+ return t ? JSON.parse(t) : {};
107
+ } catch {
108
+ return {};
109
+ }
110
+ }
111
+ function R(t) {
112
+ try {
113
+ localStorage.setItem(W, JSON.stringify(t));
114
+ } catch {
115
+ }
116
+ }
117
+ function ye() {
118
+ const t = g(he());
119
+ function l(n) {
120
+ return n in t.value;
121
+ }
122
+ function c(n) {
123
+ const o = t.value[n];
124
+ if (o == null)
125
+ return [];
126
+ const a = Array.isArray(o) ? o : [o];
127
+ return Array.from(new Set(a)).sort((i, O) => i - O);
128
+ }
129
+ function e(n, o) {
130
+ const a = Array.isArray(o) ? o : [o], i = Array.from(new Set(a)).sort((O, T) => O - T);
131
+ t.value = {
132
+ ...t.value,
133
+ [n]: i.length === 1 ? i[0] : i
134
+ }, R(t.value);
135
+ }
136
+ function p(n) {
137
+ const o = { ...t.value };
138
+ delete o[n], t.value = o, R(t.value);
139
+ }
140
+ function y() {
141
+ t.value = {}, R(t.value);
142
+ }
143
+ return {
144
+ votes: t,
145
+ hasVoted: l,
146
+ getVotedOptions: c,
147
+ recordVote: e,
148
+ clearVote: p,
149
+ clearAllVotes: y
150
+ };
151
+ }
152
+ function ge(t, l) {
153
+ const { hasVoted: c, getVotedOptions: e, recordVote: p } = ye(), y = g(!1), n = g(null), o = g([]), a = g(1);
154
+ l?.selectionLimit && Y(
155
+ l.selectionLimit,
156
+ (f) => {
157
+ a.value = be(f), o.value.length > a.value && (o.value = o.value.slice(0, a.value));
158
+ },
159
+ { immediate: !0 }
160
+ );
161
+ const i = _(() => c(t)), O = _(() => e(t)), T = _(() => a.value > 1), b = _(() => o.value.length > 0);
162
+ Y(
163
+ O,
164
+ (f) => {
165
+ i.value && f.length && (o.value = [...f]);
166
+ },
167
+ { immediate: !0 }
168
+ );
169
+ function P(f) {
170
+ if (!i.value)
171
+ if (k(), T.value) {
172
+ if (o.value.includes(f)) {
173
+ o.value = o.value.filter((S) => S !== f);
174
+ return;
175
+ }
176
+ if (o.value.length < a.value) {
177
+ o.value = [...o.value, f];
178
+ return;
179
+ }
180
+ n.value = `You can select up to ${a.value} options.`;
181
+ } else
182
+ o.value = [f];
183
+ }
184
+ async function w() {
185
+ if (!b.value)
186
+ return n.value = "Please select at least one option", null;
187
+ if (i.value)
188
+ return n.value = "You have already voted on this poll", null;
189
+ y.value = !0, n.value = null;
190
+ const f = [...o.value].sort((S, A) => S - A);
191
+ try {
192
+ const S = await pe(t, f);
193
+ return p(t, f), S.poll;
194
+ } catch (S) {
195
+ return n.value = S instanceof Error ? S.message : "Failed to submit vote", null;
196
+ } finally {
197
+ y.value = !1;
198
+ }
199
+ }
200
+ function k() {
201
+ n.value = null;
202
+ }
203
+ return {
204
+ submitting: y,
205
+ error: n,
206
+ selectedOptions: o,
207
+ selectionLimit: a,
208
+ isMultiSelect: T,
209
+ hasSelection: b,
210
+ alreadyVoted: i,
211
+ votedOptionIndices: O,
212
+ selectOption: P,
213
+ submitVote: w,
214
+ clearError: k
215
+ };
216
+ }
217
+ function be(t) {
218
+ return typeof t != "number" || !Number.isFinite(t) || t <= 0 ? 1 : Math.max(1, Math.floor(t));
219
+ }
220
+ const Se = /* @__PURE__ */ K({
221
+ __name: "ColoeusPolls",
222
+ props: {
223
+ id: { type: String, required: !0 }
224
+ },
225
+ emits: ["voted", "error"],
226
+ setup(t, { expose: l, emit: c }) {
227
+ l();
228
+ const e = t, p = c, { poll: y, loading: n, error: o, totalVotes: a, isActive: i, optionPercentages: O, updatePoll: T } = me(e.id), b = _(() => {
229
+ const m = y.value?.maxSelections;
230
+ return typeof m == "number" && m > 0 ? Math.floor(m) : 1;
231
+ }), {
232
+ submitting: P,
233
+ error: w,
234
+ selectedOptions: k,
235
+ selectionLimit: f,
236
+ isMultiSelect: S,
237
+ hasSelection: A,
238
+ alreadyVoted: I,
239
+ votedOptionIndices: F,
240
+ selectOption: M,
241
+ submitVote: r,
242
+ clearError: v
243
+ } = ge(e.id, { selectionLimit: b }), V = _(() => o.value || w.value), D = _(() => i.value && !I.value), Z = _(() => I.value || !i.value), $ = _(() => `coloeus-poll-title-${e.id}`), ee = _(() => `coloeus-poll-instructions-${e.id}`), oe = _(() => `coloeus-poll-status-${e.id}`), te = _(() => i.value ? I.value ? "You have already voted on this poll" : "Voting is open" : "Voting is closed"), le = _(() => S.value ? "checkbox" : "radio"), ne = _(() => S.value ? `Select up to ${f.value} options and submit your vote.` : "Select one option and submit your vote.");
244
+ async function se() {
245
+ v();
246
+ const m = await r();
247
+ m ? (T(m), p("voted", e.id, [...k.value])) : w.value && p("error", w.value);
248
+ }
249
+ function ie(m) {
250
+ D.value && M(m);
251
+ }
252
+ function ae(m) {
253
+ return `coloeus-poll-option-${e.id}-stats-${m}`;
254
+ }
255
+ function q(m) {
256
+ return k.value.includes(m);
257
+ }
258
+ function B(m) {
259
+ return F.value.includes(m);
260
+ }
261
+ function re(m) {
262
+ return q(m) || B(m);
263
+ }
264
+ const G = { props: e, emit: p, poll: y, loading: n, pollError: o, totalVotes: a, isActive: i, optionPercentages: O, updatePoll: T, pollSelectionLimit: b, submitting: P, voteError: w, selectedOptions: k, selectionLimit: f, isMultiSelect: S, hasSelection: A, alreadyVoted: I, votedOptionIndices: F, selectOption: M, submitVote: r, clearError: v, displayError: V, canVote: D, showResults: Z, pollTitleId: $, pollInstructionsId: ee, pollStatusId: oe, pollStatusMessage: te, optionInputType: le, selectionInstruction: ne, handleVote: se, handleOptionSelect: ie, getOptionStatsId: ae, isOptionCurrentlySelected: q, hasVotedForOption: B, isOptionChecked: re };
265
+ return Object.defineProperty(G, "__isScriptSetup", { enumerable: !1, value: !0 }), G;
266
+ }
267
+ }), X = (t, l) => {
268
+ const c = t.__vccOpts || t;
269
+ for (const [e, p] of l)
270
+ c[e] = p;
271
+ return c;
272
+ }, Oe = { class: "coloeus-poll" }, Ve = {
273
+ key: 0,
274
+ class: "coloeus-poll__loading"
275
+ }, Te = ["aria-labelledby", "aria-describedby"], we = ["aria-describedby", "aria-disabled"], ke = ["id"], Ce = ["id"], Ee = {
276
+ key: 0,
277
+ class: "coloeus-poll__instructions-note"
278
+ }, Pe = {
279
+ class: "coloeus-poll__options",
280
+ role: "list"
281
+ }, Ie = { class: "coloeus-poll__option-label" }, Ae = ["type", "name", "value", "checked", "disabled", "aria-describedby", "onChange"], Me = { class: "coloeus-poll__option-content" }, Ue = { class: "coloeus-poll__option-text" }, Le = {
282
+ key: 0,
283
+ class: "coloeus-poll__option-stats",
284
+ "aria-hidden": "true"
285
+ }, Fe = { class: "coloeus-poll__option-percentage" }, De = ["id"], Ne = { key: 0 }, je = { class: "coloeus-poll__footer" }, ze = ["disabled"], Re = {
286
+ class: "coloeus-poll__total",
287
+ "aria-live": "polite"
288
+ }, Ye = ["id"], Je = {
289
+ key: 0,
290
+ class: "coloeus-poll__status coloeus-poll__status--inactive"
291
+ }, qe = {
292
+ key: 1,
293
+ class: "coloeus-poll__status coloeus-poll__status--active"
294
+ }, Be = {
295
+ key: 2,
296
+ class: "coloeus-poll__status coloeus-poll__status--open"
297
+ }, Ge = { class: "coloeus-poll__sr" }, He = {
298
+ key: 0,
299
+ class: "coloeus-poll__error",
300
+ role: "alert",
301
+ "aria-live": "assertive"
302
+ }, Ke = {
303
+ key: 2,
304
+ class: "coloeus-poll__error"
305
+ };
306
+ function xe(t, l, c, e, p, y) {
307
+ return d(), u("div", Oe, [
308
+ e.loading ? (d(), u("div", Ve, "Loading poll...")) : e.poll ? (d(), u(U, { key: 1 }, [
309
+ s("form", {
310
+ class: "coloeus-poll__form",
311
+ role: "form",
312
+ "aria-labelledby": e.pollTitleId,
313
+ "aria-describedby": `${e.pollInstructionsId} ${e.pollStatusId}`,
314
+ onSubmit: x(e.handleVote, ["prevent"])
315
+ }, [
316
+ s("fieldset", {
317
+ class: "coloeus-poll__fieldset",
318
+ "aria-describedby": `${e.pollInstructionsId} ${e.pollStatusId}`,
319
+ "aria-disabled": !e.canVote
320
+ }, [
321
+ s("legend", {
322
+ id: e.pollTitleId,
323
+ class: "coloeus-poll__title"
324
+ }, h(e.poll.title), 9, ke),
325
+ s("p", {
326
+ id: e.pollInstructionsId,
327
+ class: "coloeus-poll__instructions"
328
+ }, [
329
+ N(h(e.selectionInstruction) + " ", 1),
330
+ e.canVote ? E("", !0) : (d(), u("span", Ee, h(e.pollStatusMessage), 1))
331
+ ], 8, Ce),
332
+ s("ul", Pe, [
333
+ (d(!0), u(U, null, Q(e.poll.options, (n, o) => (d(), u("li", {
334
+ key: o,
335
+ class: ce(["coloeus-poll__option", {
336
+ "coloeus-poll__option--selected": e.isOptionCurrentlySelected(o),
337
+ "coloeus-poll__option--voted": e.hasVotedForOption(o),
338
+ "coloeus-poll__option--disabled": !e.canVote
339
+ }])
340
+ }, [
341
+ s("label", Ie, [
342
+ s("input", {
343
+ type: e.optionInputType,
344
+ class: "coloeus-poll__option-input",
345
+ name: `coloeus-poll-${e.props.id}`,
346
+ value: o,
347
+ checked: e.isOptionChecked(o),
348
+ disabled: !e.canVote,
349
+ "aria-describedby": e.getOptionStatsId(o),
350
+ onChange: (a) => e.handleOptionSelect(o)
351
+ }, null, 40, Ae),
352
+ s("span", Me, [
353
+ s("span", {
354
+ class: "coloeus-poll__option-progress",
355
+ style: ue({ width: `${e.optionPercentages[o]}%` }),
356
+ "aria-hidden": "true"
357
+ }, null, 4),
358
+ s("span", Ue, h(n.text), 1),
359
+ e.showResults ? (d(), u("span", Le, [
360
+ s("span", Fe, h(e.optionPercentages[o]) + "%", 1),
361
+ s("span", null, "(" + h(n.voteCount) + ")", 1)
362
+ ])) : E("", !0),
363
+ s("span", {
364
+ id: e.getOptionStatsId(o),
365
+ class: "coloeus-poll__sr"
366
+ }, [
367
+ e.showResults ? (d(), u(U, { key: 0 }, [
368
+ N(h(n.voteCount) + " vote" + h(n.voteCount === 1 ? "" : "s") + " (" + h(e.optionPercentages[o]) + " percent). ", 1),
369
+ e.hasVotedForOption(o) ? (d(), u("span", Ne, "You selected this option.")) : E("", !0)
370
+ ], 64)) : (d(), u(U, { key: 1 }, [
371
+ N(" Results are hidden until voting closes. ")
372
+ ], 64))
373
+ ], 8, De)
374
+ ])
375
+ ])
376
+ ], 2))), 128))
377
+ ])
378
+ ], 8, we),
379
+ s("div", je, [
380
+ e.canVote ? (d(), u("button", {
381
+ key: 0,
382
+ type: "submit",
383
+ class: "coloeus-poll__vote-btn",
384
+ disabled: !e.hasSelection || e.submitting
385
+ }, h(e.submitting ? "Voting..." : "Vote"), 9, ze)) : E("", !0),
386
+ s("span", Re, h(e.totalVotes) + " vote" + h(e.totalVotes === 1 ? "" : "s") + " cast ", 1),
387
+ s("div", {
388
+ id: e.pollStatusId,
389
+ class: "coloeus-poll__status-group",
390
+ role: "status",
391
+ "aria-live": "polite"
392
+ }, [
393
+ e.isActive ? e.alreadyVoted ? (d(), u("span", qe, " You voted ")) : (d(), u("span", Be, " Voting open ")) : (d(), u("span", Je, " Voting closed ")),
394
+ s("span", Ge, h(e.pollStatusMessage), 1)
395
+ ], 8, Ye)
396
+ ])
397
+ ], 40, Te),
398
+ e.displayError ? (d(), u("div", He, h(e.displayError), 1)) : E("", !0)
399
+ ], 64)) : (d(), u("div", Ke, "Failed to load poll"))
400
+ ]);
401
+ }
402
+ const vo = /* @__PURE__ */ X(Se, [["render", xe], ["__file", "/Users/philipp/Code/coloeus/frontend/components/src/components/ColoeusPolls.vue"]]), Qe = /* @__PURE__ */ K({
403
+ __name: "ColoeusEditor",
404
+ props: {
405
+ id: { type: String, required: !1 }
406
+ },
407
+ emits: ["saved", "cancel", "error"],
408
+ setup(t, { expose: l, emit: c }) {
409
+ l();
410
+ const e = t, p = c, y = _(() => !!e.id), n = g(""), o = g(["", ""]), a = g(""), i = g(""), O = g(!1), T = g(!1), b = g(null), P = _(() => !(!n.value.trim() || o.value.filter((v) => v.trim()).length < 2));
411
+ async function w() {
412
+ if (e.id) {
413
+ O.value = !0, b.value = null;
414
+ try {
415
+ const r = await fe(e.id);
416
+ n.value = r.title, o.value = r.options.map((v) => v.text), a.value = r.startTime ? k(r.startTime) : "", i.value = r.endTime ? k(r.endTime) : "";
417
+ } catch (r) {
418
+ b.value = r instanceof Error ? r.message : "Failed to load poll", p("error", b.value);
419
+ } finally {
420
+ O.value = !1;
421
+ }
422
+ }
423
+ }
424
+ function k(r) {
425
+ const v = new Date(r), V = v.getTimezoneOffset();
426
+ return new Date(v.getTime() - V * 60 * 1e3).toISOString().slice(0, 16);
427
+ }
428
+ function f() {
429
+ o.value.length < 20 && (o.value = [...o.value, ""]);
430
+ }
431
+ function S(r) {
432
+ o.value.length > 2 && (o.value = o.value.filter((v, V) => V !== r));
433
+ }
434
+ function A(r, v) {
435
+ const V = [...o.value];
436
+ V[r] = v, o.value = V;
437
+ }
438
+ async function I() {
439
+ if (P.value) {
440
+ T.value = !0, b.value = null;
441
+ try {
442
+ const r = o.value.filter((v) => v.trim());
443
+ if (y.value && e.id) {
444
+ const v = {
445
+ title: n.value.trim(),
446
+ startTime: a.value ? new Date(a.value).toISOString() : null,
447
+ endTime: i.value ? new Date(i.value).toISOString() : null
448
+ }, V = await ve(e.id, v);
449
+ p("saved", V);
450
+ } else {
451
+ const v = {
452
+ title: n.value.trim(),
453
+ options: r,
454
+ startTime: a.value ? new Date(a.value).toISOString() : void 0,
455
+ endTime: i.value ? new Date(i.value).toISOString() : void 0
456
+ }, V = await _e(v);
457
+ p("saved", V);
458
+ }
459
+ } catch (r) {
460
+ b.value = r instanceof Error ? r.message : "Failed to save poll", p("error", b.value);
461
+ } finally {
462
+ T.value = !1;
463
+ }
464
+ }
465
+ }
466
+ function F() {
467
+ p("cancel");
468
+ }
469
+ H(() => {
470
+ e.id && w();
471
+ }), Y(
472
+ () => e.id,
473
+ (r) => {
474
+ r ? w() : (n.value = "", o.value = ["", ""], a.value = "", i.value = "", b.value = null);
475
+ }
476
+ );
477
+ const M = { props: e, emit: p, isEditMode: y, title: n, options: o, startTime: a, endTime: i, loading: O, submitting: T, error: b, canSubmit: P, loadPoll: w, formatDateTimeLocal: k, addOption: f, removeOption: S, updateOption: A, handleSubmit: I, handleCancel: F };
478
+ return Object.defineProperty(M, "__isScriptSetup", { enumerable: !1, value: !0 }), M;
479
+ }
480
+ }), We = { class: "coloeus-editor" }, Xe = { class: "coloeus-editor__title" }, Ze = {
481
+ key: 0,
482
+ class: "coloeus-poll__loading"
483
+ }, $e = { class: "coloeus-editor__field" }, eo = { class: "coloeus-editor__field" }, oo = { class: "coloeus-editor__options" }, to = ["value", "placeholder", "disabled", "onInput"], lo = ["onClick"], no = { class: "coloeus-editor__field" }, so = { class: "coloeus-editor__field" }, io = { class: "coloeus-editor__actions" }, ao = ["disabled"], ro = {
484
+ key: 0,
485
+ class: "coloeus-editor__error"
486
+ };
487
+ function co(t, l, c, e, p, y) {
488
+ return d(), u("div", We, [
489
+ s("h3", Xe, h(e.isEditMode ? "Edit Poll" : "Create Poll"), 1),
490
+ e.loading ? (d(), u("div", Ze, "Loading...")) : (d(), u("form", {
491
+ key: 1,
492
+ onSubmit: x(e.handleSubmit, ["prevent"])
493
+ }, [
494
+ s("div", $e, [
495
+ l[3] || (l[3] = s("label", {
496
+ class: "coloeus-editor__label",
497
+ for: "poll-title"
498
+ }, "Title", -1)),
499
+ j(s("input", {
500
+ id: "poll-title",
501
+ "onUpdate:modelValue": l[0] || (l[0] = (n) => e.title = n),
502
+ type: "text",
503
+ class: "coloeus-editor__input",
504
+ placeholder: "Enter poll title",
505
+ maxlength: "500",
506
+ required: ""
507
+ }, null, 512), [
508
+ [z, e.title]
509
+ ])
510
+ ]),
511
+ s("div", eo, [
512
+ l[4] || (l[4] = s("label", { class: "coloeus-editor__label" }, "Options", -1)),
513
+ s("ul", oo, [
514
+ (d(!0), u(U, null, Q(e.options, (n, o) => (d(), u("li", {
515
+ key: o,
516
+ class: "coloeus-editor__option"
517
+ }, [
518
+ s("input", {
519
+ value: n,
520
+ type: "text",
521
+ class: "coloeus-editor__option-input",
522
+ placeholder: `Option ${o + 1}`,
523
+ maxlength: "150",
524
+ disabled: e.isEditMode,
525
+ onInput: (a) => e.updateOption(o, a.target.value)
526
+ }, null, 40, to),
527
+ e.options.length > 2 && !e.isEditMode ? (d(), u("button", {
528
+ key: 0,
529
+ type: "button",
530
+ class: "coloeus-editor__remove-btn",
531
+ onClick: (a) => e.removeOption(o)
532
+ }, " Remove ", 8, lo)) : E("", !0)
533
+ ]))), 128))
534
+ ]),
535
+ e.options.length < 20 && !e.isEditMode ? (d(), u("button", {
536
+ key: 0,
537
+ type: "button",
538
+ class: "coloeus-editor__add-btn",
539
+ onClick: e.addOption
540
+ }, " + Add Option ")) : E("", !0)
541
+ ]),
542
+ s("div", no, [
543
+ l[5] || (l[5] = s("label", {
544
+ class: "coloeus-editor__label",
545
+ for: "poll-start"
546
+ }, "Start Time (optional)", -1)),
547
+ j(s("input", {
548
+ id: "poll-start",
549
+ "onUpdate:modelValue": l[1] || (l[1] = (n) => e.startTime = n),
550
+ type: "datetime-local",
551
+ class: "coloeus-editor__input"
552
+ }, null, 512), [
553
+ [z, e.startTime]
554
+ ])
555
+ ]),
556
+ s("div", so, [
557
+ l[6] || (l[6] = s("label", {
558
+ class: "coloeus-editor__label",
559
+ for: "poll-end"
560
+ }, "End Time (optional)", -1)),
561
+ j(s("input", {
562
+ id: "poll-end",
563
+ "onUpdate:modelValue": l[2] || (l[2] = (n) => e.endTime = n),
564
+ type: "datetime-local",
565
+ class: "coloeus-editor__input"
566
+ }, null, 512), [
567
+ [z, e.endTime]
568
+ ])
569
+ ]),
570
+ s("div", io, [
571
+ s("button", {
572
+ type: "submit",
573
+ class: "coloeus-editor__submit-btn",
574
+ disabled: !e.canSubmit || e.submitting
575
+ }, h(e.submitting ? "Saving..." : e.isEditMode ? "Update Poll" : "Create Poll"), 9, ao),
576
+ s("button", {
577
+ type: "button",
578
+ class: "coloeus-editor__cancel-btn",
579
+ onClick: e.handleCancel
580
+ }, " Cancel ")
581
+ ]),
582
+ e.error ? (d(), u("div", ro, h(e.error), 1)) : E("", !0)
583
+ ], 32))
584
+ ]);
585
+ }
586
+ const fo = /* @__PURE__ */ X(Qe, [["render", co], ["__file", "/Users/philipp/Code/coloeus/frontend/components/src/components/ColoeusEditor.vue"]]);
587
+ export {
588
+ fo as ColoeusEditor,
589
+ vo as ColoeusPolls,
590
+ po as configureColoeus,
591
+ _o as getConfig,
592
+ ye as useLocalStorage,
593
+ me as usePoll,
594
+ ge as useVote
595
+ };
@@ -0,0 +1 @@
1
+ (function(y,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(y=typeof globalThis<"u"?globalThis:y||self,e(y.ColoeusComponents={},y.Vue))})(this,(function(y,e){"use strict";let E={apiUrl:""};function R(l){E={...E,...l}}function Y(){return E}async function v(){const l={"Content-Type":"application/json"};if(E.getAuthToken){const n=await E.getAuthToken();n&&(l.Authorization=`Bearer ${n}`)}return l}async function C(l){if(!l.ok){const n=await l.json().catch(()=>({error:"Unknown error"}));throw new Error(n.error||`HTTP error ${l.status}`)}return l.json()}async function J(l){const n=await fetch(`${E.apiUrl}/polls/${l}`,{method:"GET",headers:{"Content-Type":"application/json"}});return C(n)}async function q(l,n){if(n.length===0)throw new Error("At least one option must be selected");const c=await fetch(`${E.apiUrl}/polls/${l}/vote`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({optionIndices:n})});return C(c)}async function G(l){const n=await v(),c=await fetch(`${E.apiUrl}/admin/polls`,{method:"POST",headers:n,body:JSON.stringify(l)});return C(c)}async function H(l,n){const c=await v(),t=await fetch(`${E.apiUrl}/admin/polls/${l}`,{method:"PUT",headers:c,body:JSON.stringify(n)});return C(t)}async function K(l){const n=await v(),c=await fetch(`${E.apiUrl}/admin/polls/${l}`,{method:"GET",headers:n});return C(c)}function I(l){const n=e.ref(null),c=e.ref(!0),t=e.ref(null),d=e.computed(()=>n.value?.totalVotes??0),_=e.computed(()=>n.value?.isActive??!1),s=e.computed(()=>!n.value||d.value===0?n.value?.options.map(()=>0)??[]:n.value.options.map(a=>Math.round(a.voteCount/d.value*100)));async function o(){c.value=!0,t.value=null;try{n.value=await J(l)}catch(a){t.value=a instanceof Error?a.message:"Failed to load poll",n.value=null}finally{c.value=!1}}function i(a){n.value=a}return e.onMounted(()=>{o()}),{poll:n,loading:c,error:t,totalVotes:d,isActive:_,optionPercentages:s,loadPoll:o,updatePoll:i}}const M="coloeus-votes";function Q(){try{const l=localStorage.getItem(M);return l?JSON.parse(l):{}}catch{return{}}}function P(l){try{localStorage.setItem(M,JSON.stringify(l))}catch{}}function A(){const l=e.ref(Q());function n(s){return s in l.value}function c(s){const o=l.value[s];if(o==null)return[];const i=Array.isArray(o)?o:[o];return Array.from(new Set(i)).sort((a,g)=>a-g)}function t(s,o){const i=Array.isArray(o)?o:[o],a=Array.from(new Set(i)).sort((g,b)=>g-b);l.value={...l.value,[s]:a.length===1?a[0]:a},P(l.value)}function d(s){const o={...l.value};delete o[s],l.value=o,P(l.value)}function _(){l.value={},P(l.value)}return{votes:l,hasVoted:n,getVotedOptions:c,recordVote:t,clearVote:d,clearAllVotes:_}}function L(l,n){const{hasVoted:c,getVotedOptions:t,recordVote:d}=A(),_=e.ref(!1),s=e.ref(null),o=e.ref([]),i=e.ref(1);n?.selectionLimit&&e.watch(n.selectionLimit,u=>{i.value=W(u),o.value.length>i.value&&(o.value=o.value.slice(0,i.value))},{immediate:!0});const a=e.computed(()=>c(l)),g=e.computed(()=>t(l)),b=e.computed(()=>i.value>1),f=e.computed(()=>o.value.length>0);e.watch(g,u=>{a.value&&u.length&&(o.value=[...u])},{immediate:!0});function N(u){if(!a.value)if(S(),b.value){if(o.value.includes(u)){o.value=o.value.filter(h=>h!==u);return}if(o.value.length<i.value){o.value=[...o.value,u];return}s.value=`You can select up to ${i.value} options.`}else o.value=[u]}async function k(){if(!f.value)return s.value="Please select at least one option",null;if(a.value)return s.value="You have already voted on this poll",null;_.value=!0,s.value=null;const u=[...o.value].sort((h,B)=>h-B);try{const h=await q(l,u);return d(l,u),h.poll}catch(h){return s.value=h instanceof Error?h.message:"Failed to submit vote",null}finally{_.value=!1}}function S(){s.value=null}return{submitting:_,error:s,selectedOptions:o,selectionLimit:i,isMultiSelect:b,hasSelection:f,alreadyVoted:a,votedOptionIndices:g,selectOption:N,submitVote:k,clearError:S}}function W(l){return typeof l!="number"||!Number.isFinite(l)||l<=0?1:Math.max(1,Math.floor(l))}const X=e.defineComponent({__name:"ColoeusPolls",props:{id:{type:String,required:!0}},emits:["voted","error"],setup(l,{expose:n,emit:c}){n();const t=l,d=c,{poll:_,loading:s,error:o,totalVotes:i,isActive:a,optionPercentages:g,updatePoll:b}=I(t.id),f=e.computed(()=>{const m=_.value?.maxSelections;return typeof m=="number"&&m>0?Math.floor(m):1}),{submitting:N,error:k,selectedOptions:S,selectionLimit:u,isMultiSelect:h,hasSelection:B,alreadyVoted:O,votedOptionIndices:w,selectOption:T,submitVote:r,clearError:p}=L(t.id,{selectionLimit:f}),V=e.computed(()=>o.value||k.value),D=e.computed(()=>a.value&&!O.value),Re=e.computed(()=>O.value||!a.value),Ye=e.computed(()=>`coloeus-poll-title-${t.id}`),Je=e.computed(()=>`coloeus-poll-instructions-${t.id}`),qe=e.computed(()=>`coloeus-poll-status-${t.id}`),Ge=e.computed(()=>a.value?O.value?"You have already voted on this poll":"Voting is open":"Voting is closed"),He=e.computed(()=>h.value?"checkbox":"radio"),Ke=e.computed(()=>h.value?`Select up to ${u.value} options and submit your vote.`:"Select one option and submit your vote.");async function Qe(){p();const m=await r();m?(b(m),d("voted",t.id,[...S.value])):k.value&&d("error",k.value)}function We(m){D.value&&T(m)}function Xe(m){return`coloeus-poll-option-${t.id}-stats-${m}`}function U(m){return S.value.includes(m)}function j(m){return w.value.includes(m)}function Ze(m){return U(m)||j(m)}const z={props:t,emit:d,poll:_,loading:s,pollError:o,totalVotes:i,isActive:a,optionPercentages:g,updatePoll:b,pollSelectionLimit:f,submitting:N,voteError:k,selectedOptions:S,selectionLimit:u,isMultiSelect:h,hasSelection:B,alreadyVoted:O,votedOptionIndices:w,selectOption:T,submitVote:r,clearError:p,displayError:V,canVote:D,showResults:Re,pollTitleId:Ye,pollInstructionsId:Je,pollStatusId:qe,pollStatusMessage:Ge,optionInputType:He,selectionInstruction:Ke,handleVote:Qe,handleOptionSelect:We,getOptionStatsId:Xe,isOptionCurrentlySelected:U,hasVotedForOption:j,isOptionChecked:Ze};return Object.defineProperty(z,"__isScriptSetup",{enumerable:!1,value:!0}),z}}),F=(l,n)=>{const c=l.__vccOpts||l;for(const[t,d]of n)c[t]=d;return c},Z={class:"coloeus-poll"},x={key:0,class:"coloeus-poll__loading"},$=["aria-labelledby","aria-describedby"],ee=["aria-describedby","aria-disabled"],te=["id"],oe=["id"],le={key:0,class:"coloeus-poll__instructions-note"},ne={class:"coloeus-poll__options",role:"list"},se={class:"coloeus-poll__option-label"},ae=["type","name","value","checked","disabled","aria-describedby","onChange"],ie={class:"coloeus-poll__option-content"},re={class:"coloeus-poll__option-text"},ce={key:0,class:"coloeus-poll__option-stats","aria-hidden":"true"},de={class:"coloeus-poll__option-percentage"},pe=["id"],ue={key:0},me={class:"coloeus-poll__footer"},_e=["disabled"],fe={class:"coloeus-poll__total","aria-live":"polite"},he=["id"],ye={key:0,class:"coloeus-poll__status coloeus-poll__status--inactive"},ge={key:1,class:"coloeus-poll__status coloeus-poll__status--active"},Ve={key:2,class:"coloeus-poll__status coloeus-poll__status--open"},be={class:"coloeus-poll__sr"},Ee={key:0,class:"coloeus-poll__error",role:"alert","aria-live":"assertive"},ke={key:2,class:"coloeus-poll__error"};function Se(l,n,c,t,d,_){return e.openBlock(),e.createElementBlock("div",Z,[t.loading?(e.openBlock(),e.createElementBlock("div",x,"Loading poll...")):t.poll?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createElementVNode("form",{class:"coloeus-poll__form",role:"form","aria-labelledby":t.pollTitleId,"aria-describedby":`${t.pollInstructionsId} ${t.pollStatusId}`,onSubmit:e.withModifiers(t.handleVote,["prevent"])},[e.createElementVNode("fieldset",{class:"coloeus-poll__fieldset","aria-describedby":`${t.pollInstructionsId} ${t.pollStatusId}`,"aria-disabled":!t.canVote},[e.createElementVNode("legend",{id:t.pollTitleId,class:"coloeus-poll__title"},e.toDisplayString(t.poll.title),9,te),e.createElementVNode("p",{id:t.pollInstructionsId,class:"coloeus-poll__instructions"},[e.createTextVNode(e.toDisplayString(t.selectionInstruction)+" ",1),t.canVote?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("span",le,e.toDisplayString(t.pollStatusMessage),1))],8,oe),e.createElementVNode("ul",ne,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.poll.options,(s,o)=>(e.openBlock(),e.createElementBlock("li",{key:o,class:e.normalizeClass(["coloeus-poll__option",{"coloeus-poll__option--selected":t.isOptionCurrentlySelected(o),"coloeus-poll__option--voted":t.hasVotedForOption(o),"coloeus-poll__option--disabled":!t.canVote}])},[e.createElementVNode("label",se,[e.createElementVNode("input",{type:t.optionInputType,class:"coloeus-poll__option-input",name:`coloeus-poll-${t.props.id}`,value:o,checked:t.isOptionChecked(o),disabled:!t.canVote,"aria-describedby":t.getOptionStatsId(o),onChange:i=>t.handleOptionSelect(o)},null,40,ae),e.createElementVNode("span",ie,[e.createElementVNode("span",{class:"coloeus-poll__option-progress",style:e.normalizeStyle({width:`${t.optionPercentages[o]}%`}),"aria-hidden":"true"},null,4),e.createElementVNode("span",re,e.toDisplayString(s.text),1),t.showResults?(e.openBlock(),e.createElementBlock("span",ce,[e.createElementVNode("span",de,e.toDisplayString(t.optionPercentages[o])+"%",1),e.createElementVNode("span",null,"("+e.toDisplayString(s.voteCount)+")",1)])):e.createCommentVNode("",!0),e.createElementVNode("span",{id:t.getOptionStatsId(o),class:"coloeus-poll__sr"},[t.showResults?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createTextVNode(e.toDisplayString(s.voteCount)+" vote"+e.toDisplayString(s.voteCount===1?"":"s")+" ("+e.toDisplayString(t.optionPercentages[o])+" percent). ",1),t.hasVotedForOption(o)?(e.openBlock(),e.createElementBlock("span",ue,"You selected this option.")):e.createCommentVNode("",!0)],64)):(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createTextVNode(" Results are hidden until voting closes. ")],64))],8,pe)])])],2))),128))])],8,ee),e.createElementVNode("div",me,[t.canVote?(e.openBlock(),e.createElementBlock("button",{key:0,type:"submit",class:"coloeus-poll__vote-btn",disabled:!t.hasSelection||t.submitting},e.toDisplayString(t.submitting?"Voting...":"Vote"),9,_e)):e.createCommentVNode("",!0),e.createElementVNode("span",fe,e.toDisplayString(t.totalVotes)+" vote"+e.toDisplayString(t.totalVotes===1?"":"s")+" cast ",1),e.createElementVNode("div",{id:t.pollStatusId,class:"coloeus-poll__status-group",role:"status","aria-live":"polite"},[t.isActive?t.alreadyVoted?(e.openBlock(),e.createElementBlock("span",ge," You voted ")):(e.openBlock(),e.createElementBlock("span",Ve," Voting open ")):(e.openBlock(),e.createElementBlock("span",ye," Voting closed ")),e.createElementVNode("span",be,e.toDisplayString(t.pollStatusMessage),1)],8,he)])],40,$),t.displayError?(e.openBlock(),e.createElementBlock("div",Ee,e.toDisplayString(t.displayError),1)):e.createCommentVNode("",!0)],64)):(e.openBlock(),e.createElementBlock("div",ke,"Failed to load poll"))])}const Ne=F(X,[["render",Se],["__file","/Users/philipp/Code/coloeus/frontend/components/src/components/ColoeusPolls.vue"]]),Oe=e.defineComponent({__name:"ColoeusEditor",props:{id:{type:String,required:!1}},emits:["saved","cancel","error"],setup(l,{expose:n,emit:c}){n();const t=l,d=c,_=e.computed(()=>!!t.id),s=e.ref(""),o=e.ref(["",""]),i=e.ref(""),a=e.ref(""),g=e.ref(!1),b=e.ref(!1),f=e.ref(null),N=e.computed(()=>!(!s.value.trim()||o.value.filter(p=>p.trim()).length<2));async function k(){if(t.id){g.value=!0,f.value=null;try{const r=await K(t.id);s.value=r.title,o.value=r.options.map(p=>p.text),i.value=r.startTime?S(r.startTime):"",a.value=r.endTime?S(r.endTime):""}catch(r){f.value=r instanceof Error?r.message:"Failed to load poll",d("error",f.value)}finally{g.value=!1}}}function S(r){const p=new Date(r),V=p.getTimezoneOffset();return new Date(p.getTime()-V*60*1e3).toISOString().slice(0,16)}function u(){o.value.length<20&&(o.value=[...o.value,""])}function h(r){o.value.length>2&&(o.value=o.value.filter((p,V)=>V!==r))}function B(r,p){const V=[...o.value];V[r]=p,o.value=V}async function O(){if(N.value){b.value=!0,f.value=null;try{const r=o.value.filter(p=>p.trim());if(_.value&&t.id){const p={title:s.value.trim(),startTime:i.value?new Date(i.value).toISOString():null,endTime:a.value?new Date(a.value).toISOString():null},V=await H(t.id,p);d("saved",V)}else{const p={title:s.value.trim(),options:r,startTime:i.value?new Date(i.value).toISOString():void 0,endTime:a.value?new Date(a.value).toISOString():void 0},V=await G(p);d("saved",V)}}catch(r){f.value=r instanceof Error?r.message:"Failed to save poll",d("error",f.value)}finally{b.value=!1}}}function w(){d("cancel")}e.onMounted(()=>{t.id&&k()}),e.watch(()=>t.id,r=>{r?k():(s.value="",o.value=["",""],i.value="",a.value="",f.value=null)});const T={props:t,emit:d,isEditMode:_,title:s,options:o,startTime:i,endTime:a,loading:g,submitting:b,error:f,canSubmit:N,loadPoll:k,formatDateTimeLocal:S,addOption:u,removeOption:h,updateOption:B,handleSubmit:O,handleCancel:w};return Object.defineProperty(T,"__isScriptSetup",{enumerable:!1,value:!0}),T}}),Ce={class:"coloeus-editor"},Be={class:"coloeus-editor__title"},Te={key:0,class:"coloeus-poll__loading"},we={class:"coloeus-editor__field"},ve={class:"coloeus-editor__field"},Pe={class:"coloeus-editor__options"},De=["value","placeholder","disabled","onInput"],Ie=["onClick"],Me={class:"coloeus-editor__field"},Ae={class:"coloeus-editor__field"},Le={class:"coloeus-editor__actions"},Fe=["disabled"],Ue={key:0,class:"coloeus-editor__error"};function je(l,n,c,t,d,_){return e.openBlock(),e.createElementBlock("div",Ce,[e.createElementVNode("h3",Be,e.toDisplayString(t.isEditMode?"Edit Poll":"Create Poll"),1),t.loading?(e.openBlock(),e.createElementBlock("div",Te,"Loading...")):(e.openBlock(),e.createElementBlock("form",{key:1,onSubmit:e.withModifiers(t.handleSubmit,["prevent"])},[e.createElementVNode("div",we,[n[3]||(n[3]=e.createElementVNode("label",{class:"coloeus-editor__label",for:"poll-title"},"Title",-1)),e.withDirectives(e.createElementVNode("input",{id:"poll-title","onUpdate:modelValue":n[0]||(n[0]=s=>t.title=s),type:"text",class:"coloeus-editor__input",placeholder:"Enter poll title",maxlength:"500",required:""},null,512),[[e.vModelText,t.title]])]),e.createElementVNode("div",ve,[n[4]||(n[4]=e.createElementVNode("label",{class:"coloeus-editor__label"},"Options",-1)),e.createElementVNode("ul",Pe,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.options,(s,o)=>(e.openBlock(),e.createElementBlock("li",{key:o,class:"coloeus-editor__option"},[e.createElementVNode("input",{value:s,type:"text",class:"coloeus-editor__option-input",placeholder:`Option ${o+1}`,maxlength:"150",disabled:t.isEditMode,onInput:i=>t.updateOption(o,i.target.value)},null,40,De),t.options.length>2&&!t.isEditMode?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"coloeus-editor__remove-btn",onClick:i=>t.removeOption(o)}," Remove ",8,Ie)):e.createCommentVNode("",!0)]))),128))]),t.options.length<20&&!t.isEditMode?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"coloeus-editor__add-btn",onClick:t.addOption}," + Add Option ")):e.createCommentVNode("",!0)]),e.createElementVNode("div",Me,[n[5]||(n[5]=e.createElementVNode("label",{class:"coloeus-editor__label",for:"poll-start"},"Start Time (optional)",-1)),e.withDirectives(e.createElementVNode("input",{id:"poll-start","onUpdate:modelValue":n[1]||(n[1]=s=>t.startTime=s),type:"datetime-local",class:"coloeus-editor__input"},null,512),[[e.vModelText,t.startTime]])]),e.createElementVNode("div",Ae,[n[6]||(n[6]=e.createElementVNode("label",{class:"coloeus-editor__label",for:"poll-end"},"End Time (optional)",-1)),e.withDirectives(e.createElementVNode("input",{id:"poll-end","onUpdate:modelValue":n[2]||(n[2]=s=>t.endTime=s),type:"datetime-local",class:"coloeus-editor__input"},null,512),[[e.vModelText,t.endTime]])]),e.createElementVNode("div",Le,[e.createElementVNode("button",{type:"submit",class:"coloeus-editor__submit-btn",disabled:!t.canSubmit||t.submitting},e.toDisplayString(t.submitting?"Saving...":t.isEditMode?"Update Poll":"Create Poll"),9,Fe),e.createElementVNode("button",{type:"button",class:"coloeus-editor__cancel-btn",onClick:t.handleCancel}," Cancel ")]),t.error?(e.openBlock(),e.createElementBlock("div",Ue,e.toDisplayString(t.error),1)):e.createCommentVNode("",!0)],32))])}const ze=F(Oe,[["render",je],["__file","/Users/philipp/Code/coloeus/frontend/components/src/components/ColoeusEditor.vue"]]);y.ColoeusEditor=ze,y.ColoeusPolls=Ne,y.configureColoeus=R,y.getConfig=Y,y.useLocalStorage=A,y.usePoll=I,y.useVote=L,Object.defineProperty(y,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,15 @@
1
+ import type { AdminPoll } from "../types/index.js";
2
+ import "../styles/poll.css";
3
+ type __VLS_Props = {
4
+ id?: string;
5
+ };
6
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ error: (error: string) => any;
8
+ saved: (poll: AdminPoll) => any;
9
+ cancel: () => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onError?: ((error: string) => any) | undefined;
12
+ onSaved?: ((poll: AdminPoll) => any) | undefined;
13
+ onCancel?: (() => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import "../styles/poll.css";
2
+ type __VLS_Props = {
3
+ id: string;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ error: (error: string) => any;
7
+ voted: (pollId: string, optionIndices: number[]) => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ onError?: ((error: string) => any) | undefined;
10
+ onVoted?: ((pollId: string, optionIndices: number[]) => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ export default _default;
@@ -0,0 +1,13 @@
1
+ type VoteRecord = number | number[];
2
+ interface VoteStorage {
3
+ [pollId: string]: VoteRecord;
4
+ }
5
+ export declare function useLocalStorage(): {
6
+ votes: import("vue").Ref<VoteStorage, VoteStorage>;
7
+ hasVoted: (pollId: string) => boolean;
8
+ getVotedOptions: (pollId: string) => number[];
9
+ recordVote: (pollId: string, optionIndices: number | number[]) => void;
10
+ clearVote: (pollId: string) => void;
11
+ clearAllVotes: () => void;
12
+ };
13
+ export {};
@@ -0,0 +1,35 @@
1
+ import type { PublicPoll } from "../types/index.js";
2
+ export declare function usePoll(pollId: string): {
3
+ poll: import("vue").Ref<{
4
+ id: string;
5
+ title: string;
6
+ options: {
7
+ text: string;
8
+ voteCount: number;
9
+ }[];
10
+ maxSelections: number;
11
+ startTime: string | null;
12
+ endTime: string | null;
13
+ totalVotes: number;
14
+ isActive: boolean;
15
+ } | null, PublicPoll | {
16
+ id: string;
17
+ title: string;
18
+ options: {
19
+ text: string;
20
+ voteCount: number;
21
+ }[];
22
+ maxSelections: number;
23
+ startTime: string | null;
24
+ endTime: string | null;
25
+ totalVotes: number;
26
+ isActive: boolean;
27
+ } | null>;
28
+ loading: import("vue").Ref<boolean, boolean>;
29
+ error: import("vue").Ref<string | null, string | null>;
30
+ totalVotes: import("vue").ComputedRef<number>;
31
+ isActive: import("vue").ComputedRef<boolean>;
32
+ optionPercentages: import("vue").ComputedRef<number[]>;
33
+ loadPoll: () => Promise<void>;
34
+ updatePoll: (updatedPoll: PublicPoll) => void;
35
+ };
@@ -0,0 +1,19 @@
1
+ import { type ComputedRef, type Ref } from "vue";
2
+ import type { PublicPoll } from "../types/index.js";
3
+ interface UseVoteOptions {
4
+ selectionLimit?: Ref<number> | ComputedRef<number>;
5
+ }
6
+ export declare function useVote(pollId: string, options?: UseVoteOptions): {
7
+ submitting: Ref<boolean, boolean>;
8
+ error: Ref<string | null, string | null>;
9
+ selectedOptions: Ref<number[], number[]>;
10
+ selectionLimit: Ref<number, number>;
11
+ isMultiSelect: ComputedRef<boolean>;
12
+ hasSelection: ComputedRef<boolean>;
13
+ alreadyVoted: ComputedRef<boolean>;
14
+ votedOptionIndices: ComputedRef<number[]>;
15
+ selectOption: (index: number) => void;
16
+ submitVote: () => Promise<PublicPoll | null>;
17
+ clearError: () => void;
18
+ };
19
+ export {};
@@ -0,0 +1,8 @@
1
+ export { default as ColoeusPolls } from "./components/ColoeusPolls.vue";
2
+ export { default as ColoeusEditor } from "./components/ColoeusEditor.vue";
3
+ export { usePoll } from "./composables/usePoll.js";
4
+ export { useVote } from "./composables/useVote.js";
5
+ export { useLocalStorage } from "./composables/useLocalStorage.js";
6
+ export { configureColoeus, getConfig } from "./services/api.js";
7
+ export type { PublicPoll, PollOption, VoteResponse, CreatePollInput, UpdatePollInput, AdminPoll, ColoeusConfig, } from "./types/index.js";
8
+ import "./styles/poll.css";
@@ -0,0 +1,9 @@
1
+ import type { PublicPoll, VoteResponse, CreatePollInput, UpdatePollInput, AdminPoll, ColoeusConfig } from "../types/index.js";
2
+ export declare function configureColoeus(config: ColoeusConfig): void;
3
+ export declare function getConfig(): ColoeusConfig;
4
+ export declare function fetchPoll(pollId: string): Promise<PublicPoll>;
5
+ export declare function submitVote(pollId: string, optionIndices: number[]): Promise<VoteResponse>;
6
+ export declare function createPoll(input: CreatePollInput): Promise<AdminPoll>;
7
+ export declare function updatePoll(pollId: string, input: UpdatePollInput): Promise<AdminPoll>;
8
+ export declare function fetchAdminPoll(pollId: string): Promise<AdminPoll>;
9
+ export declare function deletePoll(pollId: string): Promise<void>;
@@ -0,0 +1,45 @@
1
+ export interface PollOption {
2
+ text: string;
3
+ voteCount: number;
4
+ }
5
+ export interface PublicPoll {
6
+ id: string;
7
+ title: string;
8
+ options: PollOption[];
9
+ maxSelections: number;
10
+ startTime: string | null;
11
+ endTime: string | null;
12
+ totalVotes: number;
13
+ isActive: boolean;
14
+ }
15
+ export interface VoteResponse {
16
+ success: boolean;
17
+ poll: PublicPoll;
18
+ }
19
+ export interface CreatePollInput {
20
+ title: string;
21
+ options: string[];
22
+ maxSelections?: number;
23
+ startTime?: string;
24
+ endTime?: string;
25
+ }
26
+ export interface UpdatePollInput {
27
+ title?: string;
28
+ startTime?: string | null;
29
+ endTime?: string | null;
30
+ }
31
+ export interface AdminPoll {
32
+ id: string;
33
+ title: string;
34
+ options: PollOption[];
35
+ maxSelections: number;
36
+ startTime: string | null;
37
+ endTime: string | null;
38
+ deleted: boolean;
39
+ createdAt: string;
40
+ totalVotes: number;
41
+ }
42
+ export interface ColoeusConfig {
43
+ apiUrl: string;
44
+ getAuthToken?: () => Promise<string | null>;
45
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@richpods/coloeus",
3
+ "version": "0.1.0",
4
+ "description": "Coloeus Poll Components - Embeddable Vue 3 components",
5
+ "license": "BlueOak-1.0.0",
6
+ "author": "Philipp Naderer-Puiu",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/richpods/coloeus.git"
10
+ },
11
+ "keywords": ["vue", "poll", "components", "voting", "embed"],
12
+ "type": "module",
13
+ "main": "./dist/coloeus-components.umd.cjs",
14
+ "module": "./dist/coloeus-components.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/coloeus-components.js",
20
+ "require": "./dist/coloeus-components.umd.cjs"
21
+ },
22
+ "./style.css": "./dist/style.css"
23
+ },
24
+ "files": ["dist", "LICENSE"],
25
+ "scripts": {
26
+ "dev": "vite",
27
+ "build": "vite build && vue-tsc --emitDeclarationOnly",
28
+ "preview": "vite preview",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
31
+ },
32
+ "peerDependencies": {
33
+ "vue": "^3.5.0"
34
+ },
35
+ "dependencies": {
36
+ "vue": "^3.5.26"
37
+ },
38
+ "devDependencies": {
39
+ "@vitejs/plugin-vue": "^6.0.3",
40
+ "@vue/test-utils": "^2.4.6",
41
+ "typescript": "^5.9.3",
42
+ "vite": "^7.3.0",
43
+ "vite-plugin-dts": "^4.5.4",
44
+ "vitest": "^4.0.16",
45
+ "vue-tsc": "^2.2.12"
46
+ },
47
+ "engines": {
48
+ "node": ">=24.0.0"
49
+ }
50
+ }