| @@ -133,8 +133,9 @@ | ||
| 133 | 133 | var $fcSearch = $('#mxch-fc-search'); |
| 134 | 134 | var $fcModal = $('#mxch-fc-tool-modal'); |
| 135 | 135 | var fcCurrentCb = null; // tool being added/edited in the modal |
| 136 | 136 | var fcViewCb = null; // tool currently shown in the detail panel |
| 137 | + var fcGuardedClose = null; // dirty-checked close, re-armed on each open | |
| 137 | 138 | var fcSelected = new Set(); // callbacks ticked for bulk remove (plan 5f7409) |
| 138 | 139 | |
| 139 | 140 | var fcI18n = { |
| 140 | 141 | tools: $fcPanel.data('i18n-tools') || 'tools', |
| @@ -265,12 +266,25 @@ | ||
| 265 | 266 | function fcShowStep(n) { |
| 266 | 267 | $fcModal.find('.mxch-fc-modal-step').hide(); |
| 267 | 268 | $fcModal.find('.mxch-fc-modal-step[data-step="' + n + '"]').show(); |
| 268 | 269 | } |
| 269 | - function fcCloseModal() { $fcModal.hide(); fcCurrentCb = null; } | |
| 270 | + function fcCloseModal() { $fcModal.hide(); fcCurrentCb = null; fcGuardedClose = null; } | |
| 270 | 271 | |
| 272 | + // The modal holds an editable "when to use" hint, so it never closes on the | |
| 273 | + // backdrop and confirms before discarding. fcArmGuard must run AFTER a step | |
| 274 | + // populates its fields: mxchatGuardedClose snapshots at call time, so arming | |
| 275 | + // it earlier would measure "dirty" against empty and confirm on every close. | |
| 276 | + function fcArmGuard() { | |
| 277 | + if (typeof mxchatGuardedClose !== 'function' || !$fcModal.length) return; | |
| 278 | + fcGuardedClose = mxchatGuardedClose($fcModal[0], fcCloseModal); | |
| 279 | + } | |
| 280 | + function fcRequestClose(e) { (fcGuardedClose || fcCloseModal)(e); } | |
| 281 | + | |
| 271 | 282 | // Step 1: grid of tools not yet added. |
| 272 | 283 | function fcOpenAddModal() { |
| 284 | + // Back-to-step-1 keeps the step-2 baseline: re-arming here would treat a | |
| 285 | + // hint typed before Back as clean and discard it silently on close. | |
| 286 | + var fcWasOpen = $fcModal.is(':visible'); | |
| 273 | 287 | var $grid = $('#mxch-fc-modal-grid').empty(); |
| 274 | 288 | var addable = 0; |
| 275 | 289 | $fcStore.find('.mxch-fc-tool').each(function () { |
| 276 | 290 | var $t = $(this); |
| @@ -298,8 +312,9 @@ | ||
| 298 | 312 | $('#mxch-fc-modal-allset').toggle(addable === 0); |
| 299 | 313 | $grid.toggle(addable > 0); |
| 300 | 314 | fcShowStep(1); |
| 301 | 315 | $fcModal.css('display', 'flex'); |
| 316 | + if (!fcWasOpen) fcArmGuard(); | |
| 302 | 317 | } |
| 303 | 318 | |
| 304 | 319 | // Step 2: confirm + when-to-use (shared by Add and Edit). |
| 305 | 320 | function fcOpenConfigStep(cb) { |
| @@ -315,8 +330,9 @@ | ||
| 315 | 330 | $fcStep2Title.text(editing ? FC_TITLE_EDIT : FC_TITLE_ADD); |
| 316 | 331 | $fcSaveBtn.text(editing ? FC_BTN_SAVE : FC_BTN_ADD); |
| 317 | 332 | fcShowStep(2); |
| 318 | 333 | $fcModal.css('display', 'flex'); |
| 334 | + fcArmGuard(); // after the hint is loaded: that value is the clean baseline | |
| 319 | 335 | } |
| 320 | 336 | |
| 321 | 337 | // ---- wiring ---- |
| 322 | 338 | // "Add" buttons (panel header + empty state) — delegated on $fc. |
| @@ -322,11 +338,13 @@ | ||
| 322 | 338 | // "Add" buttons (panel header + empty state) — delegated on $fc. |
| 323 | 339 | $fc.on('click', '.js-mxch-fc-add', fcOpenAddModal); |
| 324 | 340 | $fcModal.on('click', '.mxch-fc-type-card', function () { fcOpenConfigStep($(this).data('fc-callback')); }); |
| 325 | 341 | $('#mxch-fc-modal-back').on('click', fcOpenAddModal); |
| 326 | - $fcModal.on('click', '[data-fc-modal-close]', fcCloseModal); | |
| 327 | - $fcModal.on('click', function (e) { if (e.target === this) fcCloseModal(); }); // backdrop | |
| 328 | - $(document).on('keydown', function (e) { if (e.key === 'Escape' && $fcModal.is(':visible')) fcCloseModal(); }); | |
| 342 | + $fcModal.on('click', '[data-fc-modal-close]', fcRequestClose); // X + Cancel | |
| 343 | + // No backdrop close: a click dispatches on the common ancestor of its mousedown | |
| 344 | + // and mouseup, so releasing a selection past the dialog edge targets the backdrop | |
| 345 | + // and would discard the hint — and a plain backdrop click discarded it outright. | |
| 346 | + $(document).on('keydown', function (e) { if (e.key === 'Escape' && $fcModal.is(':visible')) fcRequestClose(e); }); | |
| 329 | 347 | |
| 330 | 348 | // Modal Save (Add or Edit): write the store inputs, autosave, refresh |
| 331 | 349 | // the list, and show the tool's detail on the right. |
| 332 | 350 | $fcSaveBtn.on('click', function () { |