PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / split-bill-calculator / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at blocks/split-bill-calculator/frontend.js

283 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var _typoKeys = [
5 ['family','font-family'],['weight','font-weight'],['style','font-style'],
6 ['decoration','text-decoration'],['transform','text-transform'],
7 ['sizeDesktop','font-size-d'],['sizeTablet','font-size-t'],['sizeMobile','font-size-m'],
8 ['lineHeightDesktop','line-height-d'],['lineHeightTablet','line-height-t'],['lineHeightMobile','line-height-m'],
9 ['letterSpacingDesktop','letter-spacing-d'],['letterSpacingTablet','letter-spacing-t'],['letterSpacingMobile','letter-spacing-m'],
10 ['wordSpacingDesktop','word-spacing-d'],['wordSpacingTablet','word-spacing-t'],['wordSpacingMobile','word-spacing-m']
11 ];
12 function typoCssVarsForEl(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 _typoKeys.forEach(function (pair) {
15 var val = obj[pair[0]];
16 if (val !== undefined && val !== '') el.style.setProperty(prefix + pair[1], String(val));
17 });
18 if (obj.sizeUnit && obj.sizeUnit !== 'px') {
19 ['sizeDesktop','sizeTablet','sizeMobile'].forEach(function (k, i) {
20 var v = obj[k]; if (v !== undefined && v !== '') el.style.setProperty(prefix + ['font-size-d','font-size-t','font-size-m'][i], v + obj.sizeUnit);
21 });
22 }
23 }
24
25 function number(value, fallback, min, max) {
26 var n = parseFloat(value);
27 return Number.isFinite(n) ? Math.min(max === undefined ? Infinity : max, Math.max(min || 0, n)) : fallback;
28 }
29
30 function fmt(sym, n) {
31 return sym + n.toFixed(2);
32 }
33
34 function init(app) {
35 try {
36 var a = JSON.parse(app.getAttribute('data-opts'));
37 } catch(e) { return; }
38 if (!a || typeof a !== 'object' || Array.isArray(a)) return;
39
40 var labels = Object.assign({ bill:'Bill Amount', people:'Number of People', tip:'Tip Percentage', result:'Each person pays', subtotal:'Bill Subtotal', tipTotal:'Tip', total:'Total', split:'Split {n} ways', each:'each', validation:'Enter a valid bill, tip (0–100%), and whole number of people (at least 1).' }, a.labels || {});
41 var sym = a.currencySymbol || '$';
42 var accent = a.accentColor || '#6c3fb5';
43 var tipPresets = String(a.tipPresets || '10,15,20,25').split(',').map(function(s){ return number(s.trim(), 0, 0, 100); });
44
45 var billVal = number(a.defaultBill, 100);
46 var tipVal = number(a.defaultTip, 15, 0, 100);
47 var peopleVal = Math.floor(number(a.defaultPeople, 4, 1));
48
49 // Build DOM
50 app.innerHTML = '';
51 var wrap = document.createElement('div');
52 wrap.className = 'bkbg-sbc-wrap';
53 wrap.style.maxWidth = (a.maxWidth || 500) + 'px';
54 wrap.style.borderRadius = number(a.cardRadius, 16) + 'px';
55 wrap.style.background = a.cardBg || '#fff';
56 app.style.paddingTop = number(a.paddingTop, 60) + 'px';
57 app.style.paddingBottom = number(a.paddingBottom, 60) + 'px';
58 if (a.sectionBg) app.style.background = a.sectionBg;
59
60 typoCssVarsForEl(app, a.titleTypo, '--bksbc-tt-');
61 typoCssVarsForEl(app, a.subtitleTypo, '--bksbc-st-');
62 typoCssVarsForEl(app, a.resultTypo, '--bksbc-rs-');
63
64 app.appendChild(wrap);
65
66 // Header
67 if (a.showTitle || a.showSubtitle) {
68 var header = document.createElement('div');
69 header.className = 'bkbg-sbc-header';
70 if (a.showTitle && a.title) {
71 var title = document.createElement('h2');
72 title.className = 'bkbg-sbc-title';
73 title.textContent = a.title;
74 title.style.color = a.titleColor || '';
75 header.appendChild(title);
76 }
77 if (a.showSubtitle && a.subtitle) {
78 var sub = document.createElement('div');
79 sub.className = 'bkbg-sbc-subtitle';
80 sub.textContent = a.subtitle;
81 sub.style.color = a.subtitleColor || '';
82 header.appendChild(sub);
83 }
84 wrap.appendChild(header);
85 }
86
87 var inputRadius = number(a.inputRadius, 8) + 'px';
88 var inputBorder = '1.5px solid ' + (a.inputBorder || '#e5e7eb');
89
90 function makeField(labelText) {
91 var field = document.createElement('div');
92 field.className = 'bkbg-sbc-field';
93 var lbl = document.createElement('div');
94 lbl.className = 'bkbg-sbc-label';
95 lbl.style.color = a.labelColor || '#374151';
96 lbl.textContent = labelText;
97 field.appendChild(lbl);
98 return field;
99 }
100
101 function makeInput(type, val) {
102 var inp = document.createElement('input');
103 inp.type = type;
104 inp.className = 'bkbg-sbc-input';
105 inp.value = val;
106 inp.min = '0';
107 inp.style.borderRadius = inputRadius;
108 inp.style.border = inputBorder;
109 inp.style.fontSize = '16px';
110 return inp;
111 }
112
113 // Bill field
114 var billField = makeField(labels.bill);
115 var billInput = makeInput('number', billVal);
116 billInput.setAttribute('aria-label', labels.bill);
117 billInput.step = 'any';
118 billField.appendChild(billInput);
119 wrap.appendChild(billField);
120
121 // People field
122 var peopleField = makeField(labels.people);
123 var peopleInput = makeInput('number', peopleVal);
124 peopleInput.min = '1';
125 peopleInput.step = '1';
126 peopleInput.setAttribute('aria-label', labels.people);
127 peopleField.appendChild(peopleInput);
128 wrap.appendChild(peopleField);
129
130 // Tip field
131 var tipField = makeField(labels.tip);
132
133 var customTipInput;
134 if (a.showTipPresets) {
135 var presetsRow = document.createElement('div');
136 presetsRow.className = 'bkbg-sbc-presets';
137
138 tipPresets.forEach(function(p) {
139 var btn = document.createElement('button');
140 btn.className = 'bkbg-sbc-preset-btn';
141 btn.type = 'button';
142 btn.setAttribute('aria-pressed', p === tipVal ? 'true' : 'false');
143 btn.textContent = p + '%';
144 btn.style.background = p === tipVal ? (a.presetActiveBg || accent) : (a.presetBg || '#f3f4f6');
145 btn.style.borderColor = p === tipVal ? (a.presetActiveBg || accent) : '#e5e7eb';
146 btn.style.color = p === tipVal ? (a.presetActiveColor || '#fff') : (a.presetColor || '#374151');
147 btn.addEventListener('click', function() {
148 tipVal = p;
149 if (customTipInput) customTipInput.value = p;
150 updatePresetButtons();
151 recalculate();
152 });
153 presetsRow.appendChild(btn);
154 });
155
156 customTipInput = document.createElement('input');
157 customTipInput.type = 'number';
158 customTipInput.className = 'bkbg-sbc-tip-custom';
159 customTipInput.value = tipVal;
160 customTipInput.min = '0';
161 customTipInput.max = '100';
162 customTipInput.step = 'any';
163 customTipInput.setAttribute('aria-label', labels.tip);
164 customTipInput.style.borderRadius = inputRadius;
165 customTipInput.style.border = inputBorder;
166 customTipInput.addEventListener('input', function() {
167 tipVal = number(this.value, 0, 0, 100);
168 updatePresetButtons();
169 recalculate();
170 });
171 presetsRow.appendChild(customTipInput);
172 tipField.appendChild(presetsRow);
173 } else {
174 var tipInputDirect = makeInput('number', tipVal);
175 tipInputDirect.max = '100';
176 tipInputDirect.step = 'any';
177 tipInputDirect.setAttribute('aria-label', labels.tip);
178 tipInputDirect.addEventListener('input', function(){ tipVal = number(this.value, 0, 0, 100); recalculate(); });
179 tipField.appendChild(tipInputDirect);
180 }
181 wrap.appendChild(tipField);
182
183 function updatePresetButtons() {
184 var btns = presetsRow ? presetsRow.querySelectorAll('.bkbg-sbc-preset-btn') : [];
185 tipPresets.forEach(function(p, i) {
186 var active = p === tipVal;
187 btns[i].setAttribute('aria-pressed', active ? 'true' : 'false');
188 btns[i].style.background = active ? (a.presetActiveBg || accent) : (a.presetBg || '#f3f4f6');
189 btns[i].style.borderColor = active ? (a.presetActiveBg || accent) : '#e5e7eb';
190 btns[i].style.color = active ? (a.presetActiveColor || '#fff'): (a.presetColor || '#374151');
191 });
192 }
193
194 // Result card
195 var resultCard = document.createElement('div');
196 resultCard.className = 'bkbg-sbc-result';
197 resultCard.setAttribute('aria-live', 'polite');
198 resultCard.setAttribute('aria-atomic', 'true');
199 resultCard.style.background = a.resultBg || accent;
200 resultCard.style.borderRadius = inputRadius;
201 resultCard.style.marginBottom = a.showBreakdown ? '16px' : '0';
202
203 var resultLabel = document.createElement('div');
204 resultLabel.className = 'bkbg-sbc-result-label';
205 resultLabel.style.color = a.resultColor || '#fff';
206 resultLabel.textContent = labels.result;
207
208 var resultAmount = document.createElement('div');
209 resultAmount.className = 'bkbg-sbc-result-amount';
210 resultAmount.style.color = a.resultColor || '#fff';
211
212 resultCard.appendChild(resultLabel);
213 resultCard.appendChild(resultAmount);
214 wrap.appendChild(resultCard);
215
216 // Breakdown
217 var breakdownEl = null;
218 var rows = {};
219 if (a.showBreakdown) {
220 breakdownEl = document.createElement('div');
221 breakdownEl.className = 'bkbg-sbc-breakdown';
222 breakdownEl.style.background = a.breakdownBg || '#f5f3ff';
223 breakdownEl.style.borderColor = a.breakdownBorder || '#ede9fe';
224 breakdownEl.style.borderRadius = inputRadius;
225
226 ['Bill Subtotal', 'Tip', 'Total', 'Split'].forEach(function(key) {
227 var row = document.createElement('div');
228 row.className = 'bkbg-sbc-breakdown-row';
229 row.style.borderBottomColor = a.breakdownBorder || '#ede9fe';
230 row.style.color = a.labelColor || '#374151';
231 var lbl = document.createElement('span');
232 var val = document.createElement('strong');
233 row.appendChild(lbl);
234 row.appendChild(val);
235 breakdownEl.appendChild(row);
236 rows[key] = { lbl: lbl, val: val };
237 });
238 wrap.appendChild(breakdownEl);
239 }
240
241 function recalculate() {
242 billVal = number(billInput.value, 0);
243 peopleVal = Math.floor(number(peopleInput.value, 1, 1));
244
245 var currentTip = customTipInput || tipInputDirect;
246 var valid = billInput.value !== '' && billInput.validity.valid && peopleInput.value !== '' && peopleInput.validity.valid && currentTip.value !== '' && currentTip.validity.valid;
247 resultLabel.textContent = valid ? labels.result : labels.validation;
248 if (breakdownEl) breakdownEl.hidden = !valid;
249 [billInput, peopleInput, currentTip].forEach(function (input) { input.setAttribute('aria-invalid', input.value === '' || !input.validity.valid ? 'true' : 'false'); });
250 if (!valid) { resultAmount.textContent = '—'; return; }
251
252 var tipAmt = billVal * tipVal / 100;
253 var total = billVal + tipAmt;
254 var perPerson = a.roundUp
255 ? Math.ceil(total / peopleVal * 100) / 100
256 : total / peopleVal;
257
258 resultAmount.textContent = fmt(sym, perPerson);
259
260 if (a.showBreakdown && rows) {
261 rows['Bill Subtotal'].lbl.textContent = labels.subtotal;
262 rows['Bill Subtotal'].val.textContent = fmt(sym, billVal);
263 rows['Tip'].lbl.textContent = labels.tipTotal + ' (' + tipVal + '%)';
264 rows['Tip'].val.textContent = '+' + fmt(sym, tipAmt);
265 rows['Total'].lbl.textContent = labels.total;
266 rows['Total'].val.textContent = fmt(sym, total);
267 rows['Split'].lbl.textContent = labels.split.replace('{n}', peopleVal);
268 rows['Split'].val.textContent = fmt(sym, perPerson) + ' ' + labels.each;
269 }
270 }
271
272 billInput.addEventListener('input', recalculate);
273 peopleInput.addEventListener('input', recalculate);
274 if (!a.showTipPresets && typeof tipInputDirect !== 'undefined') {
275 // already attached above
276 }
277
278 recalculate();
279 }
280
281 document.querySelectorAll('.bkbg-sbc-app').forEach(init);
282 })();
283