diff --git a/apps/docs/static/loco.min.js b/apps/docs/static/loco.min.js index 4d8c0eb..617a9ab 100644 --- a/apps/docs/static/loco.min.js +++ b/apps/docs/static/loco.min.js @@ -1,6 +1,6 @@ -var Loco = (function () { +const Loco = (function () { 'use strict'; - var i = { + let i = { apiKey: null, apiBase: null, phrases: [], @@ -129,7 +129,7 @@ var Loco = (function () { return !/[a-zA-Z\u00C0-\u024F\u0900-\u097F\u0600-\u06FF]/.test(e); } function se(e) { - var n = e.replace(/\{\{(?:text|number|decimal|date):\d+\}\}/g, ''); + const n = e.replace(/\{\{(?:text|number|decimal|date):\d+\}\}/g, ''); return !n.trim() || A(n); } function _e(e) { @@ -141,7 +141,7 @@ var Loco = (function () { .replace(/'/g, '''); } function ee(e) { - var t; + let t; if (!e || e.nodeType !== 1) return !1; const n = (t = e.tagName) == null ? void 0 : t.toUpperCase(); return ( @@ -153,7 +153,7 @@ var Loco = (function () { e.isContentEditable ); } - var X = new WeakMap(); + let X = new WeakMap(); function Ie(e) { let n = e.parentElement; for (; n && n !== document.body; ) { @@ -176,9 +176,9 @@ var Loco = (function () { function Le() { if (Te) { G = new Set(); - var e = we.slice(); + const e = we.slice(); try { - var n = e.join(','); + const n = e.join(','); document.querySelectorAll(n).forEach(function (t) { G.add(t); }); @@ -246,7 +246,7 @@ var Loco = (function () { const a = (e.textContent || '').trim().replace(/\s+/g, ' '); if (a) return (n = a.substring(0, 60)), z.set(e, n), n; } - var r = rt(e, 200); + const r = rt(e, 200); if (r) { const a = r.split(/\n/)[0].trim().replace(/\s+/g, ' '); a.length >= 2 && !A(a) && (n = a.substring(0, 60)); @@ -256,9 +256,9 @@ var Loco = (function () { function k(e) { if (!e || !Te) return ''; if (Q.has(e)) return Q.get(e); - var n = e.parentElement; + const n = e.parentElement; if (n && n !== document.body && Q.has(n) && !te(e)) { - var t = Q.get(n); + const t = Q.get(n); return Q.set(e, t), t; } const r = [], @@ -266,13 +266,13 @@ var Loco = (function () { let o = e, l = 0; if (e && e.tagName === 'TD') { - var v = e.closest('table'); + const v = e.closest('table'); if (v) { - var h = v.querySelector( + const h = v.querySelector( 'thead tr th:nth-child(' + (e.cellIndex + 1) + ')' ); if (h) { - var s = ne(h); + const s = ne(h); s && (a.add(s), r.push(s)); } } @@ -281,7 +281,7 @@ var Loco = (function () { l++; let c = o.previousElementSibling; for (; c; ) { - var f = c.tagName.toUpperCase(); + const f = c.tagName.toUpperCase(); if (I.has(f)) { c = c.previousElementSibling; continue; @@ -321,7 +321,7 @@ var Loco = (function () { const u = ne(o); u && !a.has(u) && (a.add(u), r.unshift(u)); } - var p = o.tagName; + const p = o.tagName; if (p === 'ARTICLE' || p === 'MAIN') break; o = o.parentElement; } @@ -344,7 +344,7 @@ var Loco = (function () { o < t.length; o++ ) { - var l = t[o], + const l = t[o], v = l.match(/^\{\{(text|number|decimal|date):(\d+)\}\}$/); if (v) if (a.indexOf(l) === 0) @@ -352,7 +352,7 @@ var Loco = (function () { (a = a.substring(l.length)); else { for (var h = '', s = o + 1; s < t.length; s++) { - var f = t[s]; + const f = t[s]; if (/^\{\{(?:text|number|decimal|date):\d+\}\}$/.test(f)) { if (a.indexOf(f) >= 0) { h = f; @@ -366,7 +366,7 @@ var Loco = (function () { var p; if (h === '') (p = a), (a = ''); else { - var g = a.indexOf(h); + const g = a.indexOf(h); g >= 0 ? ((p = a.substring(0, g)), (a = a.substring(g))) : ((p = a), (a = '')); @@ -380,7 +380,7 @@ var Loco = (function () { function ce(e, n) { e.forEach(function (t) { if (!(t.varSlots && t.varSlots.length > 0)) { - var r = n[t.key]; + const r = n[t.key]; r && ((t.varSlots = it(t.key, r)), (t.key = r)); } }); @@ -397,7 +397,7 @@ var Loco = (function () { if (o === 0) r.indexOf(t[o]) === 0 && (r = r.substring(t[o].length)); else if (t[o] === '' && o === t.length - 1) a.push(r), (r = ''); else { - var l = r.indexOf(t[o]); + const l = r.indexOf(t[o]); l >= 0 && (a.push(r.substring(0, l)), (r = r.substring(l + t[o].length))); } @@ -432,7 +432,7 @@ var Loco = (function () { } function q(e, n) { for (var t = [], r = 0; r < n.length; r++) { - var a = n[r]; + const a = n[r]; if ( !( a.indexOf('{{text:') < 0 && @@ -482,7 +482,7 @@ var Loco = (function () { return ( e.forEach(function (u) { if (!c[u.key] && !(u.key.length > B)) { - for (var d = 0; d < t.length; d++) + for (let d = 0; d < t.length; d++) if (t[d].regex.test(u.key) && !ot(u.key, t[d].pattern)) { g[u.key] = t[d].pattern; break; @@ -494,7 +494,7 @@ var Loco = (function () { ); } } - var re = new WeakMap(); + let re = new WeakMap(); function M(e) { if (re.has(e)) return re.get(e); let n = !1, @@ -511,7 +511,7 @@ var Loco = (function () { W.has(o)) && (t = !0); } - var r = n && t; + const r = n && t; return re.set(e, r), r; } function st() { @@ -554,7 +554,7 @@ var Loco = (function () { } return { key: L(n), slots: t }; } - var fe = ['title', 'placeholder', 'aria-label']; + const fe = ['title', 'placeholder', 'aria-label']; function Ke(e) { const n = [], t = new Set(); @@ -569,7 +569,7 @@ var Loco = (function () { p.value.length >= 2 && !A(p.value) ) { - var g = k(p.node || e), + const g = k(p.node || e), c = p.value + '\0' + g; t.has(c) || (t.add(c), @@ -609,7 +609,7 @@ var Loco = (function () { NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { acceptNode(s) { - var m, x, b; + let m, x, b; if (s.nodeType === Node.TEXT_NODE) { const w = L(s.textContent); if ( @@ -626,8 +626,8 @@ var Loco = (function () { ? void 0 : x.toUpperCase(); if (I.has(y) || M(s.parentElement)) return NodeFilter.FILTER_SKIP; - for (var f = s.parentElement, p = 0; f && p < 3; ) { - var g = (b = f.tagName) == null ? void 0 : b.toUpperCase(); + for (let f = s.parentElement, p = 0; f && p < 3; ) { + const g = (b = f.tagName) == null ? void 0 : b.toUpperCase(); if ( (g === 'VAR' || g === 'TIME' || W.has(g)) && f.parentElement && @@ -642,14 +642,14 @@ var Loco = (function () { const w = s.tagName.toUpperCase(); if (I.has(w) || ee(s)) return NodeFilter.FILTER_REJECT; if (w === 'INPUT') { - var c = (s.getAttribute('type') || '').toLowerCase(); + const c = (s.getAttribute('type') || '').toLowerCase(); if (c === 'button' || c === 'submit' || c === 'reset') { - var u = L(s.value); + const u = L(s.value); if (u && u.length >= 2 && !A(u)) return NodeFilter.FILTER_ACCEPT; } - var d = fe.some(function (y) { - var E = L(s.getAttribute(y) || ''); + const d = fe.some(function (y) { + const E = L(s.getAttribute(y) || ''); return E && E.length >= 2 && !A(E); }); return d ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; @@ -686,7 +686,7 @@ var Loco = (function () { var v = (l.getAttribute('type') || '').toLowerCase(), r = k(l); if (v === 'button' || v === 'submit' || v === 'reset') { - var h = L(l.value); + const h = L(l.value); if (h && h.length >= 2 && !A(h)) { var a = h + '\0' + r; t.has(a) || t.add(a), @@ -701,9 +701,9 @@ var Loco = (function () { } } fe.forEach(function (f) { - var p = L(l.getAttribute(f) || ''); + const p = L(l.getAttribute(f) || ''); if (!(!p || p.length < 2 || A(p))) { - var g = p + '\0' + r + '\0' + f; + const g = p + '\0' + r + '\0' + f; t.has(g) || (t.add(g), n.push({ @@ -730,7 +730,7 @@ var Loco = (function () { u.value.length >= 2 && !A(u.value) ) { - var d = k(u.node || l), + const d = k(u.node || l), m = u.value + '\0' + d; t.has(m) || (t.add(m), @@ -785,7 +785,7 @@ var Loco = (function () { d.value.length >= 2 && !A(d.value) ) { - var m = k(d.node || e), + const m = k(d.node || e), x = d.value + '\0' + m; r.has(x) || (r.add(x), @@ -826,7 +826,7 @@ var Loco = (function () { NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { acceptNode(c) { - var y, E, T; + let y, E, T; if (c.nodeType === Node.TEXT_NODE) { const N = L(c.textContent); if ( @@ -843,8 +843,8 @@ var Loco = (function () { ? void 0 : E.toUpperCase(); if (I.has(C) || M(c.parentElement)) return NodeFilter.FILTER_SKIP; - for (var u = c.parentElement, d = 0; u && d < 3; ) { - var m = (T = u.tagName) == null ? void 0 : T.toUpperCase(); + for (let u = c.parentElement, d = 0; u && d < 3; ) { + const m = (T = u.tagName) == null ? void 0 : T.toUpperCase(); if ( (m === 'VAR' || m === 'TIME' || W.has(m)) && u.parentElement && @@ -859,14 +859,14 @@ var Loco = (function () { const N = c.tagName.toUpperCase(); if (I.has(N) || ee(c)) return NodeFilter.FILTER_REJECT; if (N === 'INPUT') { - var x = (c.getAttribute('type') || '').toLowerCase(); + const x = (c.getAttribute('type') || '').toLowerCase(); if (x === 'button' || x === 'submit' || x === 'reset') { - var b = L(c.value); + const b = L(c.value); if (b && b.length >= 2 && !A(b)) return NodeFilter.FILTER_ACCEPT; } - var w = fe.some(function (C) { - var O = L(c.getAttribute(C) || ''); + const w = fe.some(function (C) { + const O = L(c.getAttribute(C) || ''); return O && O.length >= 2 && !A(O); }); return w ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; @@ -878,7 +878,7 @@ var Loco = (function () { } ); for (var h; (h = v.nextNode()); ) l.push(h); - for (var s = 0; s < l.length; s++) { + for (let s = 0; s < l.length; s++) { s > 0 && s % n === 0 && (await new Promise(function (c) { @@ -909,7 +909,7 @@ var Loco = (function () { var p = (f.getAttribute('type') || '').toLowerCase(), a = k(f); if (p === 'button' || p === 'submit' || p === 'reset') { - var g = L(f.value); + const g = L(f.value); if (g && g.length >= 2 && !A(g)) { var o = g + '\0' + a; r.has(o) || r.add(o), @@ -924,9 +924,9 @@ var Loco = (function () { } } fe.forEach(function (u) { - var d = L(f.getAttribute(u) || ''); + const d = L(f.getAttribute(u) || ''); if (!(!d || d.length < 2 || A(d))) { - var m = d + '\0' + a + '\0' + u; + const m = d + '\0' + a + '\0' + u; r.has(m) || (r.add(m), t.push({ @@ -953,7 +953,7 @@ var Loco = (function () { b.value.length >= 2 && !A(b.value) ) { - var w = k(b.node || f), + const w = k(b.node || f), y = b.value + '\0' + w; r.has(y) || (r.add(y), @@ -994,11 +994,11 @@ var Loco = (function () { function de(e) { if (i.scanStopped) return Promise.resolve({ ok: !0, registered: 0, keyMap: {} }); - var n = new Set(), + const n = new Set(), t = []; return ( e.forEach(function (r) { - var a = r.key + '\0' + (r.context || ''); + const a = r.key + '\0' + (r.context || ''); n.has(a) || (r.varSlots && r.varSlots.length > 0 && @@ -1009,7 +1009,7 @@ var Loco = (function () { r.slots.length > 0 && r.slots.forEach(function (o) { if (o.type === 'text' && o.value && !A(o.value)) { - var l = o.value + '\0'; + const l = o.value + '\0'; n.has(l) || (n.add(l), t.push({ key: o.value, context: '' })); } })); @@ -1024,13 +1024,13 @@ var Loco = (function () { ); } function ct(e) { - var n = i.apiBase + '/api/translations?lang=' + encodeURIComponent(e); + const n = i.apiBase + '/api/translations?lang=' + encodeURIComponent(e); return fetch(n, { headers: { 'X-API-Key': i.apiKey } }) .then(function (t) { return t.json(); }) .then(function (t) { - var r = {}; + let r = {}; return ( Array.isArray(t) ? t.forEach(function (a) { @@ -1044,7 +1044,7 @@ var Loco = (function () { } function ut() { if (!(i.fileMode || !i.screenshotsEnabled)) { - var e = document.createElement('script'); + const e = document.createElement('script'); (e.src = i.apiBase + '/cdn/html2canvas.min.js'), (e.onload = function () { typeof html2canvas == 'function' && @@ -1059,7 +1059,7 @@ var Loco = (function () { windowHeight: window.innerHeight, }) .then(function (n) { - var t = n.toDataURL('image/jpeg', 0.5), + const t = n.toDataURL('image/jpeg', 0.5), r = t.split(',')[1]; !r || r.length > 5e5 || @@ -1089,7 +1089,7 @@ var Loco = (function () { } function Ue(e) { if (!Array.isArray(e)) return e; - var n = {}; + const n = {}; return ( e.forEach(function (t) { (n[t.key + '\0' + (t.context || '')] = t.value), @@ -1104,11 +1104,11 @@ var Loco = (function () { return ( e.forEach((a) => { const o = a.key + '\0' + (a.context || ''); - var l = n[o] || n[a.key]; + let l = n[o] || n[a.key]; if (!l && a.slots && a.slots.length > 0) { - var v = a.slots.some(function (p) { + const v = a.slots.some(function (p) { if (p.type !== 'text' || !p.value) return !1; - var g = p.value + '\0' + (a.context || ''), + const g = p.value + '\0' + (a.context || ''), c = p.value + '\0'; return ( n.hasOwnProperty(g) || @@ -1144,13 +1144,13 @@ var Loco = (function () { } if (a.type === 'text' && a.textNode) try { - var h = l; + let h = l; a.varSlots && a.varSlots.length > 0 && (h = h.replace( /\{\{(text|number|decimal|date):(\d+)\}\}/g, function (p, g, c) { - var u = a.varSlots.find(function (d) { + let u = a.varSlots.find(function (d) { return (d.type || 'text') === g && d.index === +c; }); return u || (u = a.varSlots[+c]), u ? u.originalText : p; @@ -1173,14 +1173,14 @@ var Loco = (function () { try { const p = ft(l, a.slots); let g = l; - var s = {}; + const s = {}; a.slots.forEach((c) => { if (c.type === 'date' && c.node) (c.node.textContent = c.display), (s[c.index] = c.node.outerHTML); else if (c.node) { if (c.type === 'text' && c.value) { - var u = c.value + '\0' + (a.context || ''), + const u = c.value + '\0' + (a.context || ''), d = c.value + '\0', m = n[u] || n[d] || n[c.value]; m && (c.node.textContent = m); @@ -1194,12 +1194,14 @@ var Loco = (function () { s.hasOwnProperty(c.index) || (s[c.index] = _e(c.originalText)); }); - var f = g.split(/(\{\{(?:text|number|decimal|date):\d+\}\})/g); + const f = g.split(/(\{\{(?:text|number|decimal|date):\d+\}\})/g); (g = f .map(function (c) { - var u = c.match(/^\{\{(?:text|number|decimal|date):(\d+)\}\}$/); + const u = c.match( + /^\{\{(?:text|number|decimal|date):(\d+)\}\}$/ + ); if (u) { - var d = +u[1]; + const d = +u[1]; return s.hasOwnProperty(d) ? s[d] : ''; } return _e(c); @@ -1228,7 +1230,7 @@ var Loco = (function () { requestAnimationFrame(s); }), (o = performance.now())); - var v = e.slice(l, l + t), + const v = e.slice(l, l + t), h = ke(v, n); (r += h.applied), (a += h.skipped); } @@ -1275,7 +1277,7 @@ var Loco = (function () { r = []; Object.values(n).forEach(function (u) { if (u && (t.add(u), /\{\{(text|number|decimal|date):\d+\}\}/.test(u))) { - var d = u.split(/\{\{(?:text|number|decimal|date):\d+\}\}/g), + const d = u.split(/\{\{(?:text|number|decimal|date):\d+\}\}/g), m = d.map(function (x) { return x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }); @@ -1284,7 +1286,7 @@ var Loco = (function () { }); function a(u) { if (t.has(u)) return !0; - for (var d = 0; d < r.length; d++) if (r[d].test(u)) return !0; + for (let d = 0; d < r.length; d++) if (r[d].test(u)) return !0; return !1; } let o = [], @@ -1295,7 +1297,7 @@ var Loco = (function () { f = !1; function p() { if (o.length !== 0) { - var u = o.splice(0); + const u = o.splice(0); i.fileMode || de(u) .then(function (d) { @@ -1313,7 +1315,7 @@ var Loco = (function () { function g(u) { const d = Ke(u); d.forEach((m) => { - var x = m.key + '\0' + (m.context || ''); + const x = m.key + '\0' + (m.context || ''); a(m.key) || t.has(x) || (t.add(x), t.add(m.key), o.push(m), i.phrases.push(m)); @@ -1330,7 +1332,7 @@ var Loco = (function () { v || (v = requestAnimationFrame(function () { v = null; - var d = Array.from(h); + const d = Array.from(h); h.clear(), d.forEach(function (m) { m.isConnected && g(m); @@ -1340,13 +1342,13 @@ var Loco = (function () { return ( (s = new MutationObserver((u) => { if (!f) { - var d = [], + const d = [], m = new Set(); for (const b of u) for (const w of b.addedNodes) if (w.nodeType === Node.ELEMENT_NODE) d.push(w); else if (w.nodeType === Node.TEXT_NODE) { - var x = w.parentElement; + const x = w.parentElement; x && x.isConnected && m.add(x); } d.forEach(function (b) { @@ -1361,7 +1363,7 @@ var Loco = (function () { s ); } - var Be = [ + const Be = [ 'aria-labelledby', 'aria-label', 'aria-describedby', @@ -1372,13 +1374,13 @@ var Loco = (function () { ], je = '\0'; function dt(e) { - var n = {}; + const n = {}; return ( !e || typeof e != 'object' || Be.forEach(function (t) { if (Object.prototype.hasOwnProperty.call(e, t)) { - var r = e[t]; + const r = e[t]; typeof r == 'string' && r.trim() !== '' && (n[t] = r.trim()); } }), @@ -1386,12 +1388,12 @@ var Loco = (function () { ); } function He(e) { - var n = {}; + const n = {}; return ( Array.isArray(e) && e.forEach(function (t) { if (!(!t || typeof t.key != 'string')) { - var r = typeof t.context == 'string' ? t.context : '', + const r = typeof t.context == 'string' ? t.context : '', a = dt(t.attributes); Object.keys(a).length !== 0 && ((n[t.key + je + r] = a), @@ -1424,12 +1426,12 @@ var Loco = (function () { } function ht(e, n) { if (!e || !n) return !1; - var t = e.__locoAriaPrev || {}, + let t = e.__locoAriaPrev || {}, r = !1; return ( Be.forEach(function (a) { if (Object.prototype.hasOwnProperty.call(n, a)) { - var o = n[a]; + const o = n[a]; if (!(typeof o != 'string' || o.trim() === '')) { Object.prototype.hasOwnProperty.call(t, a) || (t[a] = e.hasAttribute(a) ? e.getAttribute(a) : null); @@ -1447,27 +1449,27 @@ var Loco = (function () { } function V(e, n) { if (!n) return { applied: 0 }; - var t = 0; + let t = 0; return ( e.forEach(function (r) { - var a = r.key + je + (r.context || ''), + const a = r.key + je + (r.context || ''), o = n[a] || n[r.key]; if (o) { - var l = pt(r); + const l = pt(r); l && ht(l, o) && t++; } }), { applied: t } ); } - var Xe = 'loco', + const Xe = 'loco', gt = 2; function ie() { return new Promise(function (e, n) { try { - var t = indexedDB.open(Xe, gt); + const t = indexedDB.open(Xe, gt); (t.onupgradeneeded = function (r) { - var a = r.target.result; + const a = r.target.result; a.objectStoreNames.contains('meta') && r.oldVersion < 2 && a.deleteObjectStore('meta'), @@ -1490,11 +1492,11 @@ var Loco = (function () { function ge() { return ie().then(function (e) { return new Promise(function (n, t) { - var r = e.transaction('meta', 'readonly'), + const r = e.transaction('meta', 'readonly'), a = r.objectStore('meta').get('registry'); (a.onsuccess = function () { e.close(); - var o = a.result; + const o = a.result; n( o ? { @@ -1511,14 +1513,14 @@ var Loco = (function () { }); } function Qe(e) { - var n = 'source:' + e; + const n = 'source:' + e; return ie().then(function (t) { return new Promise(function (r, a) { - var o = t.transaction('meta', 'readonly'), + const o = t.transaction('meta', 'readonly'), l = o.objectStore('meta').get(n); (l.onsuccess = function () { t.close(); - var v = l.result; + const v = l.result; r( v ? { url: v.url, timestamp: v.timestamp, storedAt: v.storedAt } @@ -1534,11 +1536,11 @@ var Loco = (function () { function vt(e) { return ie().then(function (n) { return new Promise(function (t, r) { - var a = n.transaction('translations', 'readonly'), + const a = n.transaction('translations', 'readonly'), o = a.objectStore('translations').get(e); (o.onsuccess = function () { n.close(); - var l = o.result; + const l = o.result; t(l ? l.data : null); }), (o.onerror = function () { @@ -1550,28 +1552,28 @@ var Loco = (function () { function mt() { return ie().then(function (e) { return new Promise(function (n, t) { - var r = e.transaction(['meta', 'translations'], 'readonly'), + const r = e.transaction(['meta', 'translations'], 'readonly'), a = r.objectStore('meta'), o = a.get('registry'); (o.onsuccess = function () { - var l = o.result; + const l = o.result; if (!l || !l.languages || l.languages.length === 0) { e.close(), n(null); return; } - var v = Y(), + const v = Y(), h = v || l.languages[0], s = r.objectStore('translations').get(h); (s.onsuccess = function () { e.close(); - var f = s.result; + const f = s.result; if (!f || !f.data) { n(null); return; } - var p = {}; + const p = {}; p[h] = f.data; - var g = r.objectStore('meta').get('aria'); + const g = r.objectStore('meta').get('aria'); (g.onsuccess = function () { n({ languages: l.languages, @@ -1602,28 +1604,28 @@ var Loco = (function () { }); } function ve(e, n, t) { - var r = n.languages || [], + const r = n.languages || [], a = n.languageNames || {}, o = n.translations || {}, l = Array.isArray(n.aria) ? n.aria : [], v = n.timestamp || 0; return ie().then(function (h) { return new Promise(function (s, f) { - var p = h.transaction(['meta', 'translations'], 'readwrite'), + const p = h.transaction(['meta', 'translations'], 'readwrite'), g = p.objectStore('meta'), c = p.objectStore('translations'), u = g.get('registry'); (u.onsuccess = function () { - var d = u.result, + let d = u.result, m, x; if (t && d) { - var b = d.languages || []; + const b = d.languages || []; m = b.slice(); - for (var w = 0; w < r.length; w++) + for (let w = 0; w < r.length; w++) m.indexOf(r[w]) === -1 && m.push(r[w]); x = {}; - var y = d.languageNames || {}, + let y = d.languageNames || {}, E; for (E in y) x[E] = y[E]; for (E in a) x[E] = a[E]; @@ -1635,25 +1637,25 @@ var Loco = (function () { timestamp: v, storedAt: Date.now(), }); - var T = g.get('aria'); + const T = g.get('aria'); (T.onsuccess = function () { - var C = l; + let C = l; if (t && T.result && Array.isArray(T.result.rules)) { for ( var O = '\0', F = {}, R = [], K = T.result.rules, J = 0; J < K.length; J++ ) { - var S = K[J]; + const S = K[J]; if (!(!S || typeof S.key != 'string')) { - var U = S.key + O + (S.context || ''); + const U = S.key + O + (S.context || ''); F.hasOwnProperty(U) || R.push(U), (F[U] = S); } } - for (var P = 0; P < l.length; P++) { - var D = l[P]; + for (let P = 0; P < l.length; P++) { + const D = l[P]; if (!(!D || typeof D.key != 'string')) { - var _ = D.key + O + (D.context || ''); + const _ = D.key + O + (D.context || ''); F.hasOwnProperty(_) || R.push(_), (F[_] = D); } } @@ -1666,13 +1668,13 @@ var Loco = (function () { (T.onerror = function () { g.put({ key: 'aria', rules: l }); }); - for (var N = 0; N < r.length; N++) + for (let N = 0; N < r.length; N++) o[r[N]] && (t && Array.isArray(o[r[N]]) ? (function (C) { - var O = c.get(C); + const O = c.get(C); O.onsuccess = function () { - var F = O.result, + const F = O.result, R = F && Array.isArray(F.data) ? F.data : null, K = o[C]; if (R) { @@ -1681,11 +1683,11 @@ var Loco = (function () { P < R.length; P++ ) { - var D = R[P].key + J + (R[P].context || ''); + const D = R[P].key + J + (R[P].context || ''); S.hasOwnProperty(D) || U.push(D), (S[D] = R[P]); } - for (var _ = 0; _ < K.length; _++) { - var oe = K[_].key + J + (K[_].context || ''); + for (let _ = 0; _ < K.length; _++) { + const oe = K[_].key + J + (K[_].context || ''); S.hasOwnProperty(oe) || U.push(oe), (S[oe] = K[_]); } for (var Ze = [], Fe = 0; Fe < U.length; Fe++) @@ -1707,7 +1709,7 @@ var Loco = (function () { } function yt() { return new Promise(function (e) { - var n = indexedDB.deleteDatabase(Xe); + const n = indexedDB.deleteDatabase(Xe); (n.onsuccess = function () { e(); }), @@ -1719,14 +1721,14 @@ var Loco = (function () { }); }); } - var xt = + let xt = '', Ce = null; function me(e, n) { Ce = n; - var t = document.getElementById('loco-lang-widget'); + const t = document.getElementById('loco-lang-widget'); t && t.remove(); - var r = {}, + const r = {}, a = e.map(function (y) { return typeof y == 'object' && y.code ? (y.name && (r[y.code] = y.name), y.code) @@ -1745,12 +1747,12 @@ var Loco = (function () { return y && y.charAt(0).toUpperCase() + y.slice(1).toLowerCase(); } function h(y) { - var E = l(y); + const E = l(y); if (!E) return ''; - var T = E.split('-').filter(Boolean); + const T = E.split('-').filter(Boolean); if (T.length === 0) return ''; T[0] = T[0].toLowerCase(); - for (var N = 1; N < T.length; N++) + for (let N = 1; N < T.length; N++) T[N].length === 2 ? (T[N] = T[N].toUpperCase()) : T[N].length === 4 @@ -1759,13 +1761,13 @@ var Loco = (function () { return T.join('-'); } function s(y) { - var E = h(y); + const E = h(y); if (!E) return ''; if (o[E]) return o[E]; if (E.indexOf('-') === -1) return ''; try { if (typeof Intl < 'u' && typeof Intl.DisplayNames == 'function') { - var T = new Intl.DisplayNames([navigator.language || 'en', 'en'], { + const T = new Intl.DisplayNames([navigator.language || 'en', 'en'], { type: 'language', }), N = T.of(E); @@ -1778,15 +1780,15 @@ var Loco = (function () { return y ? h(y) === h(E) : !0; } function p(y) { - var E = r[y] || r[h(y)] || ''; + const E = r[y] || r[h(y)] || ''; if (E && !f(E, y)) return E; - var T = h(y); + const T = h(y); return o[T] ? o[T] : s(y) || y; } - var g = Y() || null, + let g = Y() || null, c = document.createElement('div'); (c.id = 'loco-lang-widget'), c.setAttribute('data-notranslate', ''); - var u = { + const u = { 'bottom-right': 'bottom:20px;right:20px;', 'bottom-left': 'bottom:20px;left:20px;', 'top-right': 'top:20px;right:20px;', @@ -1795,7 +1797,7 @@ var Loco = (function () { c.style.cssText = 'position:fixed;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;' + (u[n] || u['bottom-right']); - var d = document.createElement('button'); + const d = document.createElement('button'); (d.innerHTML = xt), (d.style.cssText = 'width:52px;height:52px;border-radius:50%;border:none;background:#1a1f2e;color:#fff;font-size:22px;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,0.25);display:flex;align-items:center;justify-content:center;transition:transform 0.2s,box-shadow 0.2s;overflow:hidden;padding:0;'), @@ -1807,13 +1809,13 @@ var Loco = (function () { (d.style.transform = 'scale(1)'), (d.style.boxShadow = '0 4px 14px rgba(0,0,0,0.25)'); }); - var m = document.createElement('div'); + const m = document.createElement('div'); m.style.cssText = 'display:none;position:absolute;' + (n.indexOf('bottom') === 0 ? 'bottom:56px;' : 'top:56px;') + (n.indexOf('right') >= 0 ? 'right:0;' : 'left:0;') + 'background:#fff;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.18);min-width:200px;max-height:320px;overflow-y:auto;padding:6px 0;'; - var x = document.createElement('div'); + const x = document.createElement('div'); (x.textContent = 'Original'), (x.style.cssText = 'padding:10px 16px;cursor:pointer;color:#333;transition:background 0.15s;font-weight:600;border-bottom:1px solid #eee;'), @@ -1827,9 +1829,9 @@ var Loco = (function () { (g = null), window.Loco.restore(), w(), (m.style.display = 'none'); }), m.appendChild(x); - var b = []; + const b = []; a.forEach(function (y) { - var E = document.createElement('div'); + const E = document.createElement('div'); (E.textContent = p(y)), E.setAttribute('data-lang', y), (E.style.cssText = @@ -1869,12 +1871,12 @@ var Loco = (function () { function Et(e) { Ce && (!e || e.length === 0 || me(e, Ce)); } - var H = { __proto__: 1, constructor: 1, prototype: 1 }; + const H = { __proto__: 1, constructor: 1, prototype: 1 }; function ze(e) { if (!e || typeof e != 'string') return !1; if (e.charAt(0) === '/' || e.charAt(0) === '.') return !0; try { - var n = new URL(e, window.location.origin); + const n = new URL(e, window.location.origin); return n.protocol === 'http:' || n.protocol === 'https:'; } catch { return !1; @@ -1903,10 +1905,10 @@ var Loco = (function () { timestamp: 0, }; } - var o = {}; + const o = {}; if (!Array.isArray(e.languages)) return null; o.languages = []; - for (var l = 0; l < e.languages.length; l++) { + for (let l = 0; l < e.languages.length; l++) { if (typeof e.languages[l] != 'string' || e.languages[l].length > 20) return null; o.languages.push(e.languages[l]); @@ -1921,7 +1923,7 @@ var Loco = (function () { typeof e.languageNames == 'object' && !Array.isArray(e.languageNames)) ) - for (var v in e.languageNames) + for (const v in e.languageNames) !e.languageNames.hasOwnProperty(v) || H[v] || (typeof e.languageNames[v] == 'string' && @@ -1933,15 +1935,15 @@ var Loco = (function () { typeof e.translations == 'object' && !Array.isArray(e.translations)) ) { - for (var h in e.translations) + for (const h in e.translations) if ( !(!e.translations.hasOwnProperty(h) || H[h]) && o.languages.indexOf(h) !== -1 ) { - var s = e.translations[h]; + const s = e.translations[h]; if (Array.isArray(s)) { for (var f = [], p = 0; p < s.length; p++) { - var g = s[p]; + const g = s[p]; !g || typeof g != 'object' || typeof g.key != 'string' || @@ -1957,8 +1959,8 @@ var Loco = (function () { } o.translations[h] = f; } else if (typeof s == 'object') { - var c = {}; - for (var u in s) + const c = {}; + for (const u in s) !s.hasOwnProperty(u) || H[u] || (typeof s[u] == 'string' && @@ -1968,14 +1970,14 @@ var Loco = (function () { } } if (((o.aria = []), Array.isArray(e.aria))) - for (var d = 0; d < e.aria.length; d++) { + for (let d = 0; d < e.aria.length; d++) { var a = e.aria[d]; if ( !(!a || typeof a != 'object') && !(typeof a.key != 'string' || a.key.length > Z) && !H[a.key] ) { - var m = typeof a.context == 'string' ? a.context : '', + const m = typeof a.context == 'string' ? a.context : '', x = a.attributes; !x || typeof x != 'object' || @@ -1987,7 +1989,7 @@ var Loco = (function () { } async function Ge() { try { - var e = await fetch(i.apiBase + '/api/project/crawler-config', { + const e = await fetch(i.apiBase + '/api/project/crawler-config', { headers: { 'X-API-Key': i.apiKey }, }); e.ok && Ye(await e.json()); @@ -2004,7 +2006,7 @@ var Loco = (function () { We() .then(function (t) { i.ariaRules = t || {}; - var r = V(i.phrases, i.ariaRules); + const r = V(i.phrases, i.ariaRules); r.applied > 0 && console.log( '[loco] applied ARIA rules to ' + r.applied + ' element(s)' @@ -2017,7 +2019,7 @@ var Loco = (function () { Loco.apply("zh-Hans") — apply translations for a language Loco.restore() — revert to original text Loco.rescan() — re-scan DOM for new nodes`); - var n = Y(); + const n = Y(); n ? $.apply(n) : (i.observer = he( @@ -2029,12 +2031,12 @@ var Loco = (function () { } function bt(e, n) { for (var t = '\0', r = {}, a = [], o = 0; o < e.length; o++) { - var l = e[o], + const l = e[o], v = l.key + t + (l.context || ''); r.hasOwnProperty(v) || a.push(v), (r[v] = l); } - for (var h = 0; h < n.length; h++) { - var s = n[h], + for (let h = 0; h < n.length; h++) { + const s = n[h], f = s.key + t + (s.context || ''); r.hasOwnProperty(f) || a.push(f), (r[f] = s); } @@ -2043,11 +2045,11 @@ var Loco = (function () { } async function xe(e) { (i.screenshotsEnabled = !1), Le(), (i.phrases = await ae(document.body)); - var n = [], + let n = [], t = i.fileData.translations || {}, r = (i.fileData.languages || [])[0]; r && t[r] && ((t[r] = Ue(t[r])), (n = Object.keys(t[r]))); - var a = q(i.phrases, n), + const a = q(i.phrases, n), o = a ? Object.keys(a).length : 0, l = (i.fileData.languages || []).length; console.log( @@ -2062,7 +2064,7 @@ var Loco = (function () { ']' ), console.log(' Languages: ' + (i.fileData.languages || []).join(', ')); - var v = Y(); + const v = Y(); v ? $.apply(v) : (i.observer = he( @@ -2096,7 +2098,7 @@ var Loco = (function () { .catch(function () {}); } catch {} i.fileUrls = e; - var n = null; + let n = null; try { n = await mt(); } catch {} @@ -2120,7 +2122,7 @@ var Loco = (function () { } async function wt(e) { for ( - var n = e.map(function (h) { + let n = e.map(function (h) { return fetch(h) .then(function (s) { if (!s.ok) throw new Error('HTTP ' + s.status + ' for ' + h); @@ -2128,13 +2130,13 @@ var Loco = (function () { }) .then(function (s) { if (!s || !s.trim()) return null; - var f; + let f; try { f = JSON.parse(s); } catch { return null; } - var p = ye(f); + const p = ye(f); return p ? { url: h, data: p } : (console.warn('[loco] File rejected (invalid schema): ' + h), @@ -2151,7 +2153,7 @@ var Loco = (function () { a++ ) if (t[a]) { - var o = t[a].url, + const o = t[a].url, l = t[a].data, v = !r || (l.languages || []).length === 1; Ee(l, r), (r = !1); @@ -2171,23 +2173,23 @@ var Loco = (function () { timestamp: e.timestamp || 0, }), i.fileData.aria || (i.fileData.aria = []); - var t = e.aria || []; + const t = e.aria || []; if (t.length > 0) { for ( var r = '\0', a = {}, o = [], l = 0; l < i.fileData.aria.length; l++ ) { - var v = i.fileData.aria[l]; + const v = i.fileData.aria[l]; if (!(!v || typeof v.key != 'string')) { - var h = v.key + r + (v.context || ''); + const h = v.key + r + (v.context || ''); a.hasOwnProperty(h) || o.push(h), (a[h] = v); } } - for (var s = 0; s < t.length; s++) { - var f = t[s]; + for (let s = 0; s < t.length; s++) { + const f = t[s]; if (!(!f || typeof f.key != 'string')) { - var p = f.key + r + (f.context || ''); + const p = f.key + r + (f.context || ''); a.hasOwnProperty(p) || o.push(p), (a[p] = f); } } @@ -2197,18 +2199,18 @@ var Loco = (function () { } return; } - for (var g = e.languages || [], c = 0; c < g.length; c++) + for (let g = e.languages || [], c = 0; c < g.length; c++) i.fileData.languages.indexOf(g[c]) === -1 && i.fileData.languages.push(g[c]); - var u = e.languageNames || {}; + const u = e.languageNames || {}; i.fileData.languageNames || (i.fileData.languageNames = {}); - for (var d in u) + for (const d in u) !u.hasOwnProperty(d) || H[d] || (i.fileData.languageNames[d] = u[d]); - var m = e.translations || {}; + const m = e.translations || {}; i.fileData.translations || (i.fileData.translations = {}); - for (var x in m) + for (const x in m) if (!(!m.hasOwnProperty(x) || H[x])) { - var b = i.fileData.translations[x], + const b = i.fileData.translations[x], w = m[x]; !b || !Array.isArray(b) || !Array.isArray(w) ? (i.fileData.translations[x] = w) @@ -2219,7 +2221,7 @@ var Loco = (function () { (i.fileData.timestamp = e.timestamp); } function Nt(e) { - var n = e.map(function (t) { + const n = e.map(function (t) { return Promise.all([ Qe(t).catch(function () { return null; @@ -2232,20 +2234,20 @@ var Loco = (function () { return null; }), ]).then(function (r) { - var a = r[0], + const a = r[0], o = r[1]; if (!o || !o.trim()) return null; - var l; + let l; try { l = JSON.parse(o); } catch { return null; } if (((l = ye(l)), !l)) return null; - var v = l.timestamp || 0, + const v = l.timestamp || 0, h = (a && a.timestamp) || 0; if (v !== h) { - var s = e.length > 1 || (l.languages || []).length === 1; + const s = e.length > 1 || (l.languages || []).length === 1; return ve(t, l, s).catch(function () {}), l; } return null; @@ -2275,25 +2277,25 @@ var Loco = (function () { ge() .then(function (e) { if (e) { - var n = be(e.languages, e.languageNames); + const n = be(e.languages, e.languageNames); Et(n); } }) .catch(function () {}); } - var Ve = { + const Ve = { 'zh-Hans': 'Chinese (Simplified)', 'zh-Hant': 'Chinese (Traditional)', }; function Tt(e) { - var n = String(e || '') + const n = String(e || '') .trim() .replace(/_/g, '-'); if (!n) return ''; - var t = n.split('-').filter(Boolean); + const t = n.split('-').filter(Boolean); if (t.length === 0) return ''; t[0] = t[0].toLowerCase(); - for (var r = 1; r < t.length; r++) + for (let r = 1; r < t.length; r++) t[r].length === 2 ? (t[r] = t[r].toUpperCase()) : t[r].length === 4 @@ -2302,13 +2304,13 @@ var Loco = (function () { return t.join('-'); } function $e(e) { - var n = Tt(e); + const n = Tt(e); if (!n) return ''; if (Ve[n]) return Ve[n]; if (n.indexOf('-') === -1) return ''; try { if (typeof Intl < 'u' && typeof Intl.DisplayNames == 'function') { - var t = new Intl.DisplayNames( + const t = new Intl.DisplayNames( [(typeof navigator < 'u' && navigator.language) || 'en', 'en'], { type: 'language' } ), @@ -2321,13 +2323,13 @@ var Loco = (function () { function Oe(e, n) { if (!e) return ''; n = n || {}; - var t = n[e]; + const t = n[e]; if (t) return t; - var r = String(e).replace(/_/g, '-'); + const r = String(e).replace(/_/g, '-'); if (n[r]) return n[r]; - var a = r.toLowerCase(); + const a = r.toLowerCase(); if (n[a]) return n[a]; - var o = a + const o = a .split('-') .map(function (l, v) { return v === 0 @@ -2346,7 +2348,7 @@ var Loco = (function () { (e = e || []), (n = n || {}), e.map(function (t) { - var r = Oe(t, n); + const r = Oe(t, n); return r ? { code: t, name: r } : t; }) ); @@ -2360,7 +2362,7 @@ var Loco = (function () { } function Je() { if (!Array.isArray(i.phrases) || i.phrases.length === 0) return []; - var e = i.phrases.filter(At); + const e = i.phrases.filter(At); return e.length !== i.phrases.length && (i.phrases = e), i.phrases; } var $ = { @@ -2407,7 +2409,7 @@ var Loco = (function () { : Ge(); }, apply: async function (e) { - var n = typeof window < 'u' && window.__locoPerfMode; + const n = typeof window < 'u' && window.__locoPerfMode; if ( (n && (performance.clearMarks(), @@ -2437,7 +2439,7 @@ var Loco = (function () { } if (!i.fileData.translations || !i.fileData.translations[e]) try { - var t = await vt(e); + const t = await vt(e); if (!t || Object.keys(t).length === 0) { console.warn( '[loco] No translations for "' + e + '" in file data or cache' @@ -2463,7 +2465,7 @@ var Loco = (function () { (i.translations = Ue(i.fileData.translations[e])), (i.fileData.translations[e] = i.translations), n && performance.mark('loco-collect-start'); - var r = Je(); + const r = Je(); r.length === 0 && (i.phrases = await ae(document.body)), n && (performance.mark('loco-collect-end'), @@ -2472,7 +2474,7 @@ var Loco = (function () { 'loco-collect-start', 'loco-collect-end' )); - var a = Object.keys(i.translations); + const a = Object.keys(i.translations); q(i.phrases, a), n && performance.mark('loco-dom-start'); var o = await pe(i.phrases, i.translations); return ( @@ -2516,9 +2518,9 @@ var Loco = (function () { i.observer && (i.observer.disconnect(), (i.observer = null)), j(i.phrases); try { - var l = await ct(e); + const l = await ct(e); (i.translations = l || {}), n && performance.mark('loco-collect-start'); - var v = Je(); + const v = Je(); v.length === 0 && (i.phrases = await ae(document.body)), n && (performance.mark('loco-collect-end'), @@ -2581,7 +2583,7 @@ var Loco = (function () { le(null); }, rescan: async function () { - var e = Object.keys(i.translations).length > 0; + const e = Object.keys(i.translations).length > 0; e && j(i.phrases), (i.currentLang = null), (i.phrases = await ae(document.body)), @@ -2599,9 +2601,9 @@ var Loco = (function () { }); }, aria: async function () { - var e = await We(); + const e = await We(); i.ariaRules = e || {}; - var n = V(i.phrases, i.ariaRules); + const n = V(i.phrases, i.ariaRules); return ( console.log( '[loco] applied ARIA rules to ' + n.applied + ' element(s)' @@ -2624,8 +2626,8 @@ var Loco = (function () { }, languages: function () { if (i.fileMode) { - var e = function () { - var n = i.fileData ? i.fileData.languages || [] : [], + const e = function () { + const n = i.fileData ? i.fileData.languages || [] : [], t = (i.fileData && i.fileData.languageNames) || {}; return n.map(function (r) { return { code: r, name: Oe(r, t) || r }; @@ -2665,7 +2667,8 @@ var Loco = (function () { return Array.isArray(n) ? n.map(function (t) { if (t && typeof t == 'object' && t.code) { - var r = t.name && t.name !== t.code ? t.name : $e(t.code); + const r = + t.name && t.name !== t.code ? t.name : $e(t.code); return { code: t.code, name: r || t.code }; } return t; @@ -2678,12 +2681,12 @@ var Loco = (function () { }, widget: function (e) { e = e || {}; - var n = e.position || 'bottom-right'; + const n = e.position || 'bottom-right'; if (((i.widgetPosition = n), i.fileMode)) { - let t = function () { + const t = function () { ge() .then(function (a) { - var o; + let o; if ( (a && a.languages && a.languages.length > 0 ? (o = be(a.languages, a.languageNames)) @@ -2698,7 +2701,7 @@ var Loco = (function () { me(o, n); }) .catch(function () { - var a = r(); + const a = r(); if (a.length === 0) { console.warn('[loco] No languages found in translation file'); return; @@ -2707,10 +2710,10 @@ var Loco = (function () { }); }, r = function () { - var a = i.fileData ? i.fileData.languages || [] : [], + const a = i.fileData ? i.fileData.languages || [] : [], o = (i.fileData && i.fileData.languageNames) || {}; return a.map(function (l) { - var v = Oe(l, o); + const v = Oe(l, o); return v ? { code: l, name: v } : l; }); }; @@ -2777,14 +2780,14 @@ var Loco = (function () { }) .then(function (n) { if (!n || !n.trim()) throw new Error('Empty file'); - var t; + let t; try { t = JSON.parse(n); } catch { throw new Error('Invalid JSON'); } if (((t = ye(t)), !t)) throw new Error('Invalid file schema'); - var r = + const r = !i.fileData || !i.fileData.languages || i.fileData.languages.length === 0, @@ -2821,7 +2824,8 @@ var Loco = (function () { console.warn('[loco] pullLatest() is only available in file mode'), Promise.resolve({ status: 'skipped', reason: 'not in file mode' }) ); - var e = i.fileUrls.length > 0 ? i.fileUrls : i.fileUrl ? [i.fileUrl] : []; + const e = + i.fileUrls.length > 0 ? i.fileUrls : i.fileUrl ? [i.fileUrl] : []; if (e.length === 0) return ( console.warn( @@ -2829,7 +2833,7 @@ var Loco = (function () { ), Promise.resolve({ status: 'error', reason: 'no file URLs' }) ); - var n = e.map(function (t) { + const n = e.map(function (t) { return Promise.all([ Qe(t).catch(function () { return null; @@ -2840,11 +2844,11 @@ var Loco = (function () { }), ]) .then(function (r) { - var a = r[0], + const a = r[0], o = r[1]; if (!o || !o.trim()) return { url: t, status: 'current', reason: 'empty' }; - var l; + let l; try { l = JSON.parse(o); } catch { @@ -2852,10 +2856,10 @@ var Loco = (function () { } if (((l = ye(l)), !l)) throw new Error('Invalid file schema in ' + t); - var v = l.timestamp || 0, + const v = l.timestamp || 0, h = (a && a.timestamp) || 0; if (v === h && h !== 0) { - var s = i.fileData && i.fileData.translations, + const s = i.fileData && i.fileData.translations, f = l.languages || [], p = !s || @@ -2864,7 +2868,7 @@ var Loco = (function () { }); if (!p) return { url: t, status: 'current', timestamp: h }; } - var g = + const g = !i.fileData || !i.fileData.languages || i.fileData.languages.length === 0, @@ -2881,26 +2885,26 @@ var Loco = (function () { }); return Promise.all(n) .then(async function (t) { - var r = t.some(function (f) { + const r = t.some(function (f) { return f.status === 'updated'; }); if (r) { (i.loadedFromCache = !1), (i.currentLang = null); - var a = Y(); + const a = Y(); if (a) i.observer && (i.observer.disconnect(), (i.observer = null)), j(i.phrases), await xe('pullLatest'); else { i.phrases = Ke(document.body); - var o = i.fileData.translations || {}, + const o = i.fileData.translations || {}, l = (i.fileData.languages || [])[0]; l && o[l] && q(i.phrases, Object.keys(o[l])); } Se(); } if (t.length === 1) { - var v = t[0]; + const v = t[0]; return ( console.log( '[loco] pullLatest: ' + @@ -2910,7 +2914,7 @@ var Loco = (function () { v ); } - var h = t + const h = t .filter(function (f) { return f.status === 'updated'; }) diff --git a/eslint.config.mjs b/eslint.config.mjs index c26aa27..59833bd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -21,6 +21,7 @@ export default [ '**/vite.config.*.timestamp*', '**/vitest.config.*.timestamp*', 'apps/demo/public/**', + 'apps/docs/static/**', ], }, { diff --git a/index b/index new file mode 100644 index 0000000..e69de29 diff --git a/packages/adapters/src/fhir/fhir-adapter.spec.ts b/packages/adapters/src/fhir/fhir-adapter.spec.ts index 23d65a0..2eee7c5 100644 --- a/packages/adapters/src/fhir/fhir-adapter.spec.ts +++ b/packages/adapters/src/fhir/fhir-adapter.spec.ts @@ -194,7 +194,22 @@ describe('mapFhirTypeToEsheet', () => { it('maps attachment to diagram by default', () => { const result = mapFhirTypeToEsheet('attachment', makeItem('attachment')); - expect(result.fieldType).toBe('diagram'); + expect(result.fieldType).toBe('file'); + }); + + it('maps open-choice itemControl to openchoice field', () => { + const result = mapFhirTypeToEsheet( + 'choice', + makeItem('choice', { + extension: [ + { + url: FHIR_EXT.ITEM_CONTROL, + valueCodeableConcept: { coding: [{ code: 'open-choice' }] }, + }, + ], + }) + ); + expect(result.fieldType).toBe('openchoice'); }); it('maps attachment with signatureRequired to signature', () => { diff --git a/packages/adapters/src/fhir/fhir-adapter.ts b/packages/adapters/src/fhir/fhir-adapter.ts index 631d156..685383e 100644 --- a/packages/adapters/src/fhir/fhir-adapter.ts +++ b/packages/adapters/src/fhir/fhir-adapter.ts @@ -611,7 +611,13 @@ function extractSingleAnswerValue(answer: FhirResponseAnswer): unknown { if (answer.valueUri !== undefined) return answer.valueUri; if (answer.valueCoding) return answer.valueCoding.code; if (answer.valueQuantity) return answer.valueQuantity.value; - if (answer.valueAttachment) return answer.valueAttachment.data; + if (answer.valueAttachment) + return { + data: answer.valueAttachment.data, + contentType: answer.valueAttachment.contentType, + title: answer.valueAttachment.title, + url: answer.valueAttachment.url, + }; if (answer.valueReference) return answer.valueReference.reference; return undefined; @@ -731,6 +737,47 @@ function convertValueToAnswers( }, ]; + case 'file': { + // value may be a string (base64) or an object with data/dataUrl/contentType/title + if (typeof value === 'string') { + return [ + { + valueAttachment: { + contentType: 'application/octet-stream', + data: value, + }, + }, + ]; + } + + const v = value as { + data?: string; + dataUrl?: string; + contentType?: string; + title?: string; + }; + let dataBase64: string | undefined = undefined; + let contentType = v.contentType ?? 'application/octet-stream'; + if (v.data) dataBase64 = v.data; + else if (v.dataUrl) { + const comma = v.dataUrl.indexOf(','); + dataBase64 = comma >= 0 ? v.dataUrl.slice(comma + 1) : v.dataUrl; + const prefix = v.dataUrl.slice(0, comma); + const m = prefix.match(/data:([^;]+)/); + if (m && m[1]) contentType = m[1]; + } + + return [ + { + valueAttachment: { + contentType, + data: dataBase64, + title: v.title, + }, + }, + ]; + } + default: return [{ valueString: String(value) }]; } @@ -758,15 +805,27 @@ function convertTextAnswer( function createCodingAnswer( field: FieldDefinition, - selectedId: string + selectedIdOrObj: string | { id?: string; value?: string } ): FhirResponseAnswer { const options = 'options' in field ? field.options : undefined; + const selectedId = + typeof selectedIdOrObj === 'string' + ? selectedIdOrObj + : selectedIdOrObj.id ?? String(selectedIdOrObj.value ?? ''); const option = options?.find((o: FieldOption) => o.id === selectedId); return { valueCoding: { - code: option?.value ?? selectedId, - display: option?.text, + code: + option?.value ?? + (typeof selectedIdOrObj === 'string' + ? selectedIdOrObj + : selectedIdOrObj.value ?? selectedId), + display: + option?.text ?? + (typeof selectedIdOrObj === 'object' + ? selectedIdOrObj.value + : undefined), }, }; } diff --git a/packages/adapters/src/fhir/utils.ts b/packages/adapters/src/fhir/utils.ts index eca8b02..5736587 100644 --- a/packages/adapters/src/fhir/utils.ts +++ b/packages/adapters/src/fhir/utils.ts @@ -156,7 +156,7 @@ export function mapFhirTypeToEsheet( if (hasSignatureRequired(item.extension)) { return { fieldType: 'signature' }; } - return { fieldType: 'diagram' }; + return { fieldType: 'file' }; case 'reference': return { fieldType: 'text', subType: 'reference' }; @@ -181,6 +181,10 @@ function mapChoiceType( : { fieldType: 'dropdown' }; } + if (itemControl === 'open-choice') { + return { fieldType: 'openchoice' }; + } + if (itemControl === 'check-box') { return { fieldType: 'check' }; } @@ -251,6 +255,12 @@ export function mapEsheetTypeToFhir( case 'diagram': return { type: 'attachment' }; + case 'file': + return { type: 'attachment' }; + + case 'openchoice': + return { type: 'choice', itemControl: 'open-choice' }; + case 'singlematrix': case 'multimatrix': return { type: 'group' }; diff --git a/packages/builder/src/lib/components/edit-panel/LogicEditor.tsx b/packages/builder/src/lib/components/edit-panel/LogicEditor.tsx index 5b7810e..fd485bc 100644 --- a/packages/builder/src/lib/components/edit-panel/LogicEditor.tsx +++ b/packages/builder/src/lib/components/edit-panel/LogicEditor.tsx @@ -1075,6 +1075,34 @@ function ExpectedValueInput({ } } + // OpenChoice fields include predefined options plus a special "{fieldId}-other" option. + if (target?.fieldType === 'openchoice') { + if ( + operator === 'equals' || + operator === 'notEquals' || + operator === 'includes' + ) { + const otherOptionId = `${target.id}-other`; + return ( + + ); + } + } + // If target has options and we're using equals/notEquals/includes, // show a dropdown of option values if (target?.hasOptions && target.options && target.options.length > 0) { @@ -1169,6 +1197,11 @@ function getOperatorsForTarget(target: TargetField): ConditionOperator[] { return ['empty', 'notEmpty']; } + // Media answers (file, signature, diagram) — only presence checks are meaningful. + if (answerType === 'media') { + return ['empty', 'notEmpty']; + } + // Single-value selection fields (radio/dropdown/boolean etc.). if (answerType === 'selection') { const ops: ConditionOperator[] = [ diff --git a/packages/builder/src/lib/register-defaults.ts b/packages/builder/src/lib/register-defaults.ts index 0813df7..ad6c336 100644 --- a/packages/builder/src/lib/register-defaults.ts +++ b/packages/builder/src/lib/register-defaults.ts @@ -14,6 +14,7 @@ import { MultiTextField, RadioField, CheckField, + OpenChoiceField, BooleanField, DropdownField, MultiSelectDropdownField, @@ -28,6 +29,7 @@ import { ImageField, HtmlField, DisplayField, + FileField, } from '@esheet/fields'; let defaultFieldComponentsRegistered = false; @@ -41,6 +43,7 @@ export function ensureDefaultFieldComponentsRegistered(): void { multitext: MultiTextField, radio: RadioField, check: CheckField, + openchoice: OpenChoiceField, boolean: BooleanField, dropdown: DropdownField, multiselectdropdown: MultiSelectDropdownField, @@ -53,6 +56,7 @@ export function ensureDefaultFieldComponentsRegistered(): void { signature: SignatureField, diagram: DiagramField, image: ImageField, + file: FileField, html: HtmlField, display: DisplayField, }); diff --git a/packages/builder/tsconfig.json b/packages/builder/tsconfig.json index f29eb83..87df318 100644 --- a/packages/builder/tsconfig.json +++ b/packages/builder/tsconfig.json @@ -14,9 +14,6 @@ }, { "path": "./tsconfig.lib.json" - }, - { - "path": "./tsconfig.spec.json" } ] } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 528afa0..4e96f80 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -49,12 +49,14 @@ export { type BooleanFieldDefinition, type DropdownFieldDefinition, type MultiselectDropdownFieldDefinition, + type OpenChoiceFieldDefinition, type RatingFieldDefinition, type RankingFieldDefinition, type SliderFieldDefinition, type SingleMatrixFieldDefinition, type MultiMatrixFieldDefinition, type ImageFieldDefinition, + type FileFieldDefinition, type HtmlFieldDefinition, type SignatureFieldDefinition, type DiagramFieldDefinition, diff --git a/packages/core/src/lib/functions/hydrate-response.ts b/packages/core/src/lib/functions/hydrate-response.ts index c8a3fbd..6542b1e 100644 --- a/packages/core/src/lib/functions/hydrate-response.ts +++ b/packages/core/src/lib/functions/hydrate-response.ts @@ -148,9 +148,12 @@ function extractAnswer( response.signatureData ?? response.markupImage ?? response.markupData; - if (!dataUrl) return undefined; - const result: AttachmentAnswer = { contentType: 'image/png', dataUrl }; - return result; + if (dataUrl) { + const result: AttachmentAnswer = { contentType: 'image/png', dataUrl }; + return result; + } + // File fields store data in fileData with their actual contentType + return response.fileData; } default: return undefined; diff --git a/packages/core/src/lib/logic/comprehensive-conditions.spec.ts b/packages/core/src/lib/logic/comprehensive-conditions.spec.ts index 8fa6df2..fb590db 100644 --- a/packages/core/src/lib/logic/comprehensive-conditions.spec.ts +++ b/packages/core/src/lib/logic/comprehensive-conditions.spec.ts @@ -2206,4 +2206,167 @@ describe('comprehensive conditions integration', () => { ).toBe(false); }); }); + + // --------------------------------------------------------------------------- + // § New field types — openchoice and file + // --------------------------------------------------------------------------- + + describe('openchoice field conditions', () => { + const openChoiceDef: FieldDefinition = { + id: 'other-1', + fieldType: 'openchoice', + question: 'Pick one or other', + options: [ + { id: 'other-1', value: 'Alpha' }, + { id: 'other-2', value: 'Beta' }, + ], + }; + + it('equals — matches predefined option id', () => { + const driver: FieldDefinition = { ...openChoiceDef, id: 'other-3' }; + const target: FieldDefinition = { + id: 'other-4', + fieldType: 'text', + question: 'Target', + rules: [fieldRule('visible', 'other-3', 'equals', 'other-1')], + }; + const normalized = normalizeDefinition([driver, target]); + const respMatch: FieldResponse = { selected: sel('other-1', 'Alpha') }; + expect( + resolveEffect('visible', target, normalized, { 'other-3': respMatch }) + ).toBe(true); + + const respNoMatch: FieldResponse = { selected: sel('other-2', 'Beta') }; + expect( + resolveEffect('visible', target, normalized, { 'other-3': respNoMatch }) + ).toBe(false); + }); + + it('equals — matches __other__ when user types custom text', () => { + const { normalized, field } = setup(openChoiceDef); + field.rules = [fieldRule('visible', 'other-1', 'equals', '__other__')]; + const resp: FieldResponse = { + selected: { id: '__other__', value: 'custom text' }, + }; + expect( + resolveEffect('visible', field, normalized, { 'other-1': resp }) + ).toBe(true); + }); + + it('notEquals — does not match different option', () => { + const { normalized, field } = setup(openChoiceDef); + field.rules = [fieldRule('visible', 'other-1', 'notEquals', 'other-1')]; + const resp: FieldResponse = { selected: sel('other-2', 'Beta') }; + expect( + resolveEffect('visible', field, normalized, { 'other-1': resp }) + ).toBe(true); + }); + + it('empty — true when nothing selected', () => { + const { normalized, field } = setup(openChoiceDef); + field.rules = [fieldRule('visible', 'other-1', 'empty')]; + expect(resolveEffect('visible', field, normalized, {})).toBe(true); + }); + + it('notEmpty — true when option is selected', () => { + const { normalized, field } = setup(openChoiceDef); + field.rules = [fieldRule('visible', 'other-1', 'notEmpty')]; + const resp: FieldResponse = { selected: sel('other-1', 'Alpha') }; + expect( + resolveEffect('visible', field, normalized, { 'other-1': resp }) + ).toBe(true); + }); + + it('expression — returns user-typed text for __other__', () => { + const driver: FieldDefinition = { + id: 'other-5', + fieldType: 'openchoice', + question: 'Source', + options: [{ id: 'other-6', value: 'X' }], + }; + const target: FieldDefinition = { + id: 'other-7', + fieldType: 'text', + question: 'Target', + rules: [exprRule('visible', "{other-5} == 'custom text'")], + }; + const normalized = normalizeDefinition([driver, target]); + const resp: FieldResponse = { + selected: { id: '__other__', value: 'custom text' }, + }; + expect( + resolveEffect('visible', target, normalized, { 'other-5': resp }) + ).toBe(true); + }); + }); + + describe('file field conditions', () => { + const fileDef: FieldDefinition = { + id: 'other-8', + fieldType: 'file', + question: 'Upload a file', + }; + + it('empty — true when no fileData', () => { + const { normalized, field } = setup(fileDef); + field.rules = [fieldRule('visible', 'other-8', 'empty')]; + expect(resolveEffect('visible', field, normalized, {})).toBe(true); + }); + + it('notEmpty — true when fileData is present', () => { + const { normalized, field } = setup(fileDef); + field.rules = [fieldRule('visible', 'other-8', 'notEmpty')]; + const resp: FieldResponse = { + fileData: { contentType: 'application/pdf', title: 'doc.pdf' }, + }; + expect( + resolveEffect('visible', field, normalized, { 'other-8': resp }) + ).toBe(true); + }); + + it('notEmpty — true when fileData is an array with entries', () => { + const { normalized, field } = setup(fileDef); + field.rules = [fieldRule('visible', 'other-8', 'notEmpty')]; + const resp: FieldResponse = { + fileData: [ + { contentType: 'image/png', title: 'a.png' }, + { contentType: 'image/png', title: 'b.png' }, + ], + }; + expect( + resolveEffect('visible', field, normalized, { 'other-8': resp }) + ).toBe(true); + }); + + it('expression — file count used for numeric comparison', () => { + const driver: FieldDefinition = { + id: 'other-9', + fieldType: 'file', + question: 'Source files', + }; + const target: FieldDefinition = { + id: 'other-10', + fieldType: 'text', + question: 'Target', + rules: [exprRule('visible', '{other-9} >= 2')], + }; + const normalized = normalizeDefinition([driver, target]); + const resp: FieldResponse = { + fileData: [ + { contentType: 'image/png', title: 'a.png' }, + { contentType: 'image/png', title: 'b.png' }, + ], + }; + expect( + resolveEffect('visible', target, normalized, { 'other-9': resp }) + ).toBe(true); + + const singleResp: FieldResponse = { + fileData: { contentType: 'image/png', title: 'a.png' }, + }; + expect( + resolveEffect('visible', target, normalized, { 'other-9': singleResp }) + ).toBe(false); + }); + }); }); diff --git a/packages/core/src/lib/logic/conditions.ts b/packages/core/src/lib/logic/conditions.ts index 436b211..d04f0f5 100644 --- a/packages/core/src/lib/logic/conditions.ts +++ b/packages/core/src/lib/logic/conditions.ts @@ -936,7 +936,8 @@ function getExpressionFieldValue( definition.fieldType === 'dropdown' || definition.fieldType === 'boolean' || definition.fieldType === 'slider' || - definition.fieldType === 'rating' + definition.fieldType === 'rating' || + definition.fieldType === 'openchoice' ) { const selected = response.selected; if ( @@ -948,13 +949,23 @@ function getExpressionFieldValue( const selectedId = (selected as SelectedOption).id; const opt = definition.options?.find((o) => o.id === selectedId); if (opt?.score != null) return opt.score; - const raw = opt?.value ?? selectedId; + // For openchoice __other__, prefer the stored value (user-typed text) + // over the option lookup since the definition won't have that entry. + const raw = + opt?.value ?? (selected as SelectedOption).value ?? selectedId; const parsed = parseFloat(raw); return Number.isNaN(parsed) ? raw : parsed; } return definition.fieldType === 'rating' ? 0 : ''; } + if (definition.fieldType === 'file') { + const fd = response.fileData; + if (!fd) return 0; + const files = Array.isArray(fd) ? fd : [fd]; + return files.length; + } + if ( definition.fieldType === 'check' || definition.fieldType === 'multiselectdropdown' @@ -1010,7 +1021,8 @@ function getActualValue( case 'dropdown': case 'boolean': case 'slider': - case 'rating': { + case 'rating': + case 'openchoice': { const sel = response.selected; if ( sel != null && @@ -1041,6 +1053,15 @@ function getActualValue( } return {}; + case 'file': { + const fd = response.fileData; + if (!fd) return null; + const files = Array.isArray(fd) ? fd : [fd]; + return files.length === 0 + ? null + : files.map((f) => f.title ?? f.url ?? ''); + } + default: return null; } diff --git a/packages/core/src/lib/logic/validate.ts b/packages/core/src/lib/logic/validate.ts index 802c66c..6a27071 100644 --- a/packages/core/src/lib/logic/validate.ts +++ b/packages/core/src/lib/logic/validate.ts @@ -199,6 +199,14 @@ function isResponseEmpty(response: FieldResponse | undefined): boolean { if (values.some((v) => v.trim() !== '')) return false; } + // fileData — single or multiple file attachments + if (response.fileData != null) { + const files = Array.isArray(response.fileData) + ? response.fileData + : [response.fileData]; + if (files.length > 0) return false; + } + // signatureData if (response.signatureData && response.signatureData.trim() !== '') return false; diff --git a/packages/core/src/lib/registry.spec.ts b/packages/core/src/lib/registry.spec.ts index ec496e8..523c633 100644 --- a/packages/core/src/lib/registry.spec.ts +++ b/packages/core/src/lib/registry.spec.ts @@ -12,8 +12,8 @@ describe('field type registry', () => { resetFieldTypeRegistry(); }); - it('should have all 19 built-in types registered by default', () => { - expect(getRegisteredFieldTypes()).toHaveLength(19); + it('should have all 21 built-in types registered by default', () => { + expect(getRegisteredFieldTypes()).toHaveLength(21); for (const ft of FIELD_TYPES) { expect(getFieldTypeMeta(ft)).toBeDefined(); } @@ -35,7 +35,7 @@ describe('field type registry', () => { expect(getFieldTypeMeta('vitals')).toBeDefined(); expect(getFieldTypeMeta('vitals')!.label).toBe('Vitals Field'); - expect(getRegisteredFieldTypes()).toHaveLength(20); + expect(getRegisteredFieldTypes()).toHaveLength(22); }); it('should allow overriding a built-in field type', () => { @@ -49,7 +49,7 @@ describe('field type registry', () => { }); expect(getFieldTypeMeta('text')!.label).toBe('Custom Text'); - expect(getRegisteredFieldTypes()).toHaveLength(19); + expect(getRegisteredFieldTypes()).toHaveLength(21); }); it('should reset to defaults', () => { @@ -61,10 +61,10 @@ describe('field type registry', () => { hasMatrix: false, defaultProps: {}, }); - expect(getRegisteredFieldTypes()).toHaveLength(20); + expect(getRegisteredFieldTypes()).toHaveLength(22); resetFieldTypeRegistry(); - expect(getRegisteredFieldTypes()).toHaveLength(19); + expect(getRegisteredFieldTypes()).toHaveLength(21); expect(getFieldTypeMeta('custom')).toBeUndefined(); }); diff --git a/packages/core/src/lib/registry.ts b/packages/core/src/lib/registry.ts index 75bac80..a794a91 100644 --- a/packages/core/src/lib/registry.ts +++ b/packages/core/src/lib/registry.ts @@ -149,6 +149,19 @@ const BUILT_IN_FIELD_TYPES: Record = { }, defaultOptionCount: 3, }, + openchoice: { + label: 'Open Choice', + category: 'selection', + answerType: 'selection', + hasOptions: true, + hasMatrix: false, + defaultProps: {}, + placeholder: { + question: 'Enter your question...', + options: 'Enter option text...', + }, + defaultOptionCount: 3, + }, rating: { label: 'Rating', category: 'rating', @@ -255,6 +268,15 @@ const BUILT_IN_FIELD_TYPES: Record = { pad: 'Draw on the diagram', }, }, + file: { + label: 'File', + category: 'rich', + answerType: 'media', + hasOptions: false, + hasMatrix: false, + defaultProps: {}, + placeholder: { question: 'Upload a file' }, + }, display: { label: 'Display', category: 'rich', diff --git a/packages/core/src/lib/types.spec.ts b/packages/core/src/lib/types.spec.ts index f298277..c8612ab 100644 --- a/packages/core/src/lib/types.spec.ts +++ b/packages/core/src/lib/types.spec.ts @@ -15,7 +15,9 @@ describe('schema types', () => { expect(FIELD_TYPES).toContain('text'); expect(FIELD_TYPES).toContain('section'); expect(FIELD_TYPES).toContain('display'); - expect(FIELD_TYPES).toHaveLength(19); + expect(FIELD_TYPES).toContain('file'); + expect(FIELD_TYPES).toContain('openchoice'); + expect(FIELD_TYPES).toHaveLength(21); }); it('should allow constructing a valid FormDefinition', () => { diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index 5443065..2c11a18 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -23,6 +23,8 @@ export const FIELD_TYPES = [ 'html', 'signature', 'diagram', + 'file', + 'openchoice', 'display', 'section', ] as const; @@ -397,6 +399,26 @@ export interface DiagramFieldDefinition extends BaseFieldDefinition { padPlaceholder?: string; } +export interface FileFieldDefinition extends BaseFieldDefinition { + fieldType: 'file'; + /** Accept string for file input (MIME types or extensions), e.g. "image/*,.pdf" */ + accept?: string; + /** Maximum allowed file size in bytes (optional). */ + maxFileSize?: number; + /** Maximum number of files allowed to upload (optional). Default: 1. */ + maxFiles?: number; +} + +export interface OpenChoiceFieldDefinition extends BaseFieldDefinition { + fieldType: 'openchoice'; + /** Predefined selectable options. */ + options?: FieldOption[]; + /** Maximum number of custom user-added options allowed at runtime (optional). */ + maxCustomOptions?: number; + /** Label for the "Other, please specify" option (optional). */ + otherLabel?: string; +} + export interface DisplayFieldDefinition { id: string; fieldType: 'display'; @@ -440,6 +462,7 @@ export type FieldDefinition = | BooleanFieldDefinition | DropdownFieldDefinition | MultiselectDropdownFieldDefinition + | OpenChoiceFieldDefinition // Rating | RatingFieldDefinition | RankingFieldDefinition @@ -449,6 +472,7 @@ export type FieldDefinition = | MultiMatrixFieldDefinition // Rich | ImageFieldDefinition + | FileFieldDefinition | HtmlFieldDefinition | SignatureFieldDefinition | DiagramFieldDefinition @@ -464,6 +488,7 @@ export type OptionBearingFieldDefinition = | DropdownFieldDefinition | MultiselectDropdownFieldDefinition | RatingFieldDefinition + | OpenChoiceFieldDefinition | RankingFieldDefinition | SliderFieldDefinition; @@ -479,7 +504,8 @@ export function hasOptions( field.fieldType === 'multiselectdropdown' || field.fieldType === 'rating' || field.fieldType === 'ranking' || - field.fieldType === 'slider' + field.fieldType === 'slider' || + field.fieldType === 'openchoice' ); } @@ -511,6 +537,8 @@ const FIELD_TYPE_PROPERTIES: Record = { html: ['htmlContent', 'iframeHeight'], signature: ['padPlaceholder'], diagram: ['imageUri', 'padPlaceholder'], + file: ['accept', 'maxFileSize'], + openchoice: ['options', 'maxCustomOptions'], display: ['content'], // Organization category section: ['title', 'fields'], @@ -751,6 +779,20 @@ const diagramFieldSchema = z.strictObject({ padPlaceholder: z.optional(z.string()), }); +const fileFieldSchema = z.strictObject({ + ...baseFieldProps, + fieldType: z.literal('file'), + accept: z.optional(z.string()), + maxFileSize: z.optional(z.number()), +}); + +const openChoiceFieldSchema = z.strictObject({ + ...baseFieldProps, + fieldType: z.literal('openchoice'), + options: z.optional(z.array(fieldOptionSchema)), + maxCustomOptions: z.optional(z.number()), +}); + const displayBaseFieldProps = { id: z.string(), rules: z.optional(z.array(conditionalRuleSchema)), @@ -798,9 +840,11 @@ const _coreFieldSchema = z.discriminatedUnion('fieldType', [ multiMatrixFieldSchema, // Rich imageFieldSchema, + fileFieldSchema, htmlFieldSchema, signatureFieldSchema, diagramFieldSchema, + openChoiceFieldSchema, displayFieldSchema, // Organization sectionFieldSchema, @@ -901,6 +945,8 @@ export interface FieldResponse { markupData?: string; /** Base64 diagram image (diagram field). */ markupImage?: string; + /** File/attachment(s) uploaded by user (file field). Supports single or multiple files. */ + fileData?: AttachmentAnswer | AttachmentAnswer[]; /** Set to true when the response was filled programmatically by an AI agent. */ _ai?: boolean; } @@ -1052,6 +1098,8 @@ export type AttachmentAnswer = { dataUrl?: string; url?: string; title?: string; + /** File size in bytes (optional). */ + size?: number; }; /** All possible answer value shapes in a submission payload. */ diff --git a/packages/fields/src/fields/index.ts b/packages/fields/src/fields/index.ts index ec456eb..6e17d39 100644 --- a/packages/fields/src/fields/index.ts +++ b/packages/fields/src/fields/index.ts @@ -5,6 +5,7 @@ export { TextField, LongTextField, MultiTextField } from './text/index.js'; export { RadioField, CheckField, + OpenChoiceField, BooleanField, DropdownField, MultiSelectDropdownField, @@ -28,6 +29,9 @@ export { ImageField, SignatureField, } from './rich/index.js'; + +// File field (not part of rich content grouping) +export { FileField } from './rich/FileField.js'; export type { DrawingData, DrawingPadConfig, diff --git a/packages/fields/src/fields/matrix/MultiMatrixField.tsx b/packages/fields/src/fields/matrix/MultiMatrixField.tsx index dea2e8b..ea955c3 100644 --- a/packages/fields/src/fields/matrix/MultiMatrixField.tsx +++ b/packages/fields/src/fields/matrix/MultiMatrixField.tsx @@ -85,7 +85,7 @@ export const MultiMatrixField = React.memo(function MultiMatrixField({