Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 1 Rev 1441
Línea 28... Línea 28...
28
    'core/templates',
28
    'core/templates',
29
    'core/notification',
29
    'core/notification',
30
    'core/loadingicon',
30
    'core/loadingicon',
31
    'core/aria',
31
    'core/aria',
32
    'core_form/changechecker',
32
    'core_form/changechecker',
-
 
33
    'core/popper2',
-
 
34
    'theme_boost/bootstrap/dom/event-handler',
33
], function(
35
], function(
34
    $,
36
    $,
35
    log,
37
    log,
36
    str,
38
    str,
37
    templates,
39
    templates,
38
    notification,
40
    notification,
39
    LoadingIcon,
41
    LoadingIcon,
40
    Aria,
42
    Aria,
41
    FormChangeChecker
43
    FormChangeChecker,
-
 
44
    Popper,
-
 
45
    EventHandler,
42
) {
46
) {
43
    // Private functions and variables.
47
    // Private functions and variables.
44
    /** @var {Object} KEYS - List of keycode constants. */
48
    /** @var {Object} KEYS - List of keycode constants. */
45
    var KEYS = {
49
    var KEYS = {
46
        DOWN: 40,
50
        DOWN: 40,
Línea 215... Línea 219...
215
     * @param {Object} state State variables for this autocomplete element.
219
     * @param {Object} state State variables for this autocomplete element.
216
     * @param {Element} item The item to be deselected.
220
     * @param {Element} item The item to be deselected.
217
     * @param {Element} originalSelect The original select list.
221
     * @param {Element} originalSelect The original select list.
218
     * @return {Promise}
222
     * @return {Promise}
219
     */
223
     */
220
    var deselectItem = function(options, state, item, originalSelect) {
224
    const deselectItem = async(options, state, item, originalSelect) => {
221
        var selectedItemValue = $(item).attr('data-value');
225
        var selectedItemValue = $(item).attr('data-value');
Línea 222... Línea 226...
222
 
226
 
223
        // Preprend an empty option to the select list to avoid having a default selected option.
227
        // Preprend an empty option to the select list to avoid having a default selected option.
224
        if (originalSelect.find('option').first().attr('value') !== undefined) {
228
        if (originalSelect.find('option').first().attr('value') !== undefined) {
Línea 233... Línea 237...
233
                if ($(ele).attr('data-iscustom')) {
237
                if ($(ele).attr('data-iscustom')) {
234
                    $(ele).remove();
238
                    $(ele).remove();
235
                }
239
                }
236
            }
240
            }
237
        });
241
        });
-
 
242
 
-
 
243
        const selectedItemText = item[0].childNodes[2].textContent?.trim();
-
 
244
        await announceChanges(state.selectionId, selectedItemText, 'removed');
-
 
245
 
238
        // Rerender the selection list.
246
        // Rerender the selection list.
239
        return updateSelectionList(options, state, originalSelect)
247
        await updateSelectionList(options, state, originalSelect);
240
        .then(function() {
-
 
241
            // Notify that the selection changed.
-
 
242
            notifyChange(originalSelect);
-
 
Línea 243... Línea 248...
243
 
248
 
244
            return;
249
        // Notify that the selection changed.
245
        });
250
        notifyChange(originalSelect);
Línea 246... Línea 251...
246
    };
251
    };
247
 
252
 
248
    /**
253
    /**
Línea 486... Línea 491...
486
        return $.Deferred().resolve();
491
        return $.Deferred().resolve();
487
    };
492
    };
Línea 488... Línea 493...
488
 
493
 
489
    /**
494
    /**
-
 
495
     * Rebuild the list of suggestions based on the current values in the select list, and the query.
-
 
496
     * Any options in the original select with [data-enabled=disabled] will not be included
490
     * Rebuild the list of suggestions based on the current values in the select list, and the query.
497
     * as a suggestion option in the enhanced field.
491
     *
498
     *
492
     * @method updateSuggestions
499
     * @method updateSuggestions
493
     * @private
500
     * @private
494
     * @param {Object} options The original options for this autocomplete.
501
     * @param {Object} options The original options for this autocomplete.
Línea 506... Línea 513...
506
        var suggestionsElement = $(document.getElementById(state.suggestionsId));
513
        var suggestionsElement = $(document.getElementById(state.suggestionsId));
Línea 507... Línea 514...
507
 
514
 
508
        // Used to track if we found any visible suggestions.
515
        // Used to track if we found any visible suggestions.
509
        var matchingElements = false;
516
        var matchingElements = false;
510
        // Options is used by the context when rendering the suggestions from a template.
517
        // Options is used by the context when rendering the suggestions from a template.
Línea 511... Línea 518...
511
        var suggestions = rebuildOptions(originalSelect.children('option:not(:selected)'), true);
518
        var suggestions = rebuildOptions(originalSelect.children('option:not(:selected, [data-enabled="disabled"])'), true);
512
 
519
 
513
        // Re-render the list of suggestions.
520
        // Re-render the list of suggestions.
514
        var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
521
        var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
Línea 524... Línea 531...
524
            // Get the element again.
531
            // Get the element again.
525
            suggestionsElement = $(document.getElementById(state.suggestionsId));
532
            suggestionsElement = $(document.getElementById(state.suggestionsId));
Línea 526... Línea 533...
526
 
533
 
527
            // Show it if it is hidden.
534
            // Show it if it is hidden.
-
 
535
            Aria.unhide(suggestionsElement.get());
528
            Aria.unhide(suggestionsElement.get());
536
            Popper.createPopper(inputElement[0], suggestionsElement[0], {
-
 
537
                placement: 'bottom-start',
-
 
538
                modifiers: [{name: 'flip', enabled: false}],
Línea 529... Línea 539...
529
            suggestionsElement.show();
539
            });
530
 
540
 
531
            // For each option in the list, hide it if it doesn't match the query.
541
            // For each option in the list, hide it if it doesn't match the query.
532
            suggestionsElement.children().each(function(index, node) {
542
            suggestionsElement.children().each(function(index, node) {
Línea 578... Línea 588...
578
     * @param {Object} options The original options for the autocomplete.
588
     * @param {Object} options The original options for the autocomplete.
579
     * @param {Object} state State variables for the autocomplete.
589
     * @param {Object} state State variables for the autocomplete.
580
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
590
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
581
     * @return {Promise}
591
     * @return {Promise}
582
     */
592
     */
583
    var createItem = function(options, state, originalSelect) {
593
    const createItem = async(options, state, originalSelect) => {
584
        // Find the element in the DOM.
594
        // Find the element in the DOM.
585
        var inputElement = $(document.getElementById(state.inputId));
595
        var inputElement = $(document.getElementById(state.inputId));
586
        // Get the current text in the input field.
596
        // Get the current text in the input field.
587
        var query = inputElement.val();
597
        var query = inputElement.val();
588
        var tags = query.split(',');
598
        var tags = query.split(',');
Línea 613... Línea 623...
613
                    option.attr('data-iscustom', true);
623
                    option.attr('data-iscustom', true);
614
                }
624
                }
615
            }
625
            }
616
        });
626
        });
Línea 617... Línea 627...
617
 
627
 
618
        return updateSelectionList(options, state, originalSelect)
-
 
619
        .then(function() {
628
        // Announce the changes to the assistive technology.
620
            // Notify that the selection changed.
-
 
621
            notifyChange(originalSelect);
-
 
622
 
-
 
623
            return;
-
 
624
        })
-
 
625
        .then(function() {
-
 
626
            // Clear the input field.
-
 
Línea -... Línea 629...
-
 
629
        await announceChanges(state.selectionId, query.trim(), 'added');
-
 
630
 
627
            inputElement.val('');
631
        await updateSelectionList(options, state, originalSelect);
-
 
632
        // Notify that the selection changed.
628
 
633
        notifyChange(originalSelect);
629
            return;
634
 
630
        })
635
        // Clear the input field.
631
        .then(function() {
636
        inputElement.val('');
632
            // Close the suggestions list.
-
 
633
            return closeSuggestions(state);
637
        // Close the suggestions list.
Línea 634... Línea 638...
634
        });
638
        await closeSuggestions(state);
635
    };
639
    };
636
 
640
 
637
    /**
641
    /**
638
     * Select the currently active item from the suggestions list.
642
     * Select the currently active item from the suggestions list.
639
     *
643
     *
640
     * @method selectCurrentItem
644
     * @method selectCurrentItem
641
     * @private
645
     * @private
-
 
646
     * @param {Object} options The original options for the autocomplete.
642
     * @param {Object} options The original options for the autocomplete.
647
     * @param {Object} state State variables for the autocomplete.
643
     * @param {Object} state State variables for the autocomplete.
648
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
644
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
649
     * @param {string|null} selectedItem The item to be selected.
645
     * @return {Promise}
650
     * @return {Promise}
646
     */
651
     */
647
    var selectCurrentItem = function(options, state, originalSelect) {
652
    const selectCurrentItem = async(options, state, originalSelect, selectedItem) => {
648
        // Find the elements in the page.
653
        // Find the elements in the page.
Línea 657... Línea 662...
657
        // If only one can be selected at a time, start by deselecting everything.
662
        // If only one can be selected at a time, start by deselecting everything.
658
        if (!options.multiple) {
663
        if (!options.multiple) {
659
            originalSelect.children('option').prop('selected', false);
664
            originalSelect.children('option').prop('selected', false);
660
        }
665
        }
661
        // Look for a match, and toggle the selected property if there is a match.
666
        // Look for a match, and toggle the selected property if there is a match.
662
        originalSelect.children('option').each(function(index, ele) {
667
        originalSelect.children('option').each(function (index, ele) {
663
            if ($(ele).attr('value') == selectedItemValue) {
668
            if ($(ele).attr('value') == selectedItemValue) {
664
                $(ele).prop('selected', true);
669
                $(ele).prop('selected', true);
665
            }
670
            }
666
        });
671
        });
Línea 667... Línea 672...
667
 
672
 
668
        return updateSelectionList(options, state, originalSelect)
-
 
669
        .then(function() {
-
 
670
            // Notify that the selection changed.
-
 
Línea 671... Línea 673...
671
            notifyChange(originalSelect);
673
        await announceChanges(state.selectionId, selectedItem, 'added');
-
 
674
 
672
 
675
        await updateSelectionList(options, state, originalSelect);
673
            return;
676
 
-
 
677
        // Notify that the selection changed.
674
        })
678
        notifyChange(originalSelect);
675
        .then(function() {
679
 
676
            if (options.closeSuggestionsOnSelect) {
680
        if (options.closeSuggestionsOnSelect) {
677
                // Clear the input element.
681
            // Clear the input element.
678
                inputElement.val('');
682
            inputElement.val('');
679
                // Close the list of suggestions.
683
            // Close the list of suggestions.
680
                return closeSuggestions(state);
684
            await closeSuggestions(state);
681
            } else {
685
        } else {
682
                // Focus on the input element so the suggestions does not auto-close.
686
            // Focus on the input element so the suggestions does not auto-close.
683
                inputElement.focus();
687
            inputElement.focus();
684
                // Remove the last selected item from the suggestions list.
-
 
685
                return updateSuggestions(options, state, inputElement.val(), originalSelect);
688
            // Remove the last selected item from the suggestions list.
686
            }
689
            await updateSuggestions(options, state, inputElement.val(), originalSelect);
Línea 687... Línea 690...
687
        });
690
        }
688
    };
691
    };
689
 
692
 
Línea 803... Línea 806...
803
                    return false;
806
                    return false;
804
                case KEYS.ENTER:
807
                case KEYS.ENTER:
805
                    var suggestionsElement = $(document.getElementById(state.suggestionsId));
808
                    var suggestionsElement = $(document.getElementById(state.suggestionsId));
806
                    if ((inputElement.attr('aria-expanded') === "true") &&
809
                    if ((inputElement.attr('aria-expanded') === "true") &&
807
                            (suggestionsElement.children('[aria-selected=true]').length > 0)) {
810
                            (suggestionsElement.children('[aria-selected=true]').length > 0)) {
-
 
811
                        const selectedItemText = suggestionsElement.children('[aria-selected=true]')[0].textContent.trim();
808
                        // If the suggestion list has an active item, select it.
812
                        // If the suggestion list has an active item, select it.
809
                        pendingJsPromise.resolve(selectCurrentItem(options, state, originalSelect));
813
                        pendingJsPromise.resolve(selectCurrentItem(options, state, originalSelect, selectedItemText));
810
                    } else if (options.tags) {
814
                    } else if (options.tags) {
811
                        // If tags are enabled, create a tag.
815
                        // If tags are enabled, create a tag.
812
                        pendingJsPromise.resolve(createItem(options, state, originalSelect));
816
                        pendingJsPromise.resolve(createItem(options, state, originalSelect));
813
                    } else {
817
                    } else {
814
                        pendingJsPromise.resolve();
818
                        pendingJsPromise.resolve();
Línea 923... Línea 927...
923
 
927
 
924
            // Activate it.
928
            // Activate it.
925
            activateItem(current, state)
929
            activateItem(current, state)
926
            .then(function() {
930
            .then(function() {
-
 
931
                // And select it.
927
                // And select it.
932
                const selectedItemText = element[0].textContent.trim();
928
                return selectCurrentItem(options, state, originalSelect);
933
                return selectCurrentItem(options, state, originalSelect, selectedItemText);
929
            })
934
            })
930
            .then(function() {
935
            .then(function() {
931
                return pendingPromise.resolve();
936
                return pendingPromise.resolve();
932
            })
937
            })
933
            .catch();
938
            .catch(notification.exception);
934
        });
939
        });
Línea 935... Línea 940...
935
        var selectionElement = $(document.getElementById(state.selectionId));
940
        var selectionElement = $(document.getElementById(state.selectionId));
936
 
941
 
Línea 1074... Línea 1079...
1074
                    }
1079
                    }
1075
                    $(e.currentTarget).data('last-value', query);
1080
                    $(e.currentTarget).data('last-value', query);
1076
                });
1081
                });
1077
            }
1082
            }
1078
        }
1083
        }
-
 
1084
 
-
 
1085
        // Add a Bootstrap keydown handler to close the suggestions list preventing the whole Dropdown close.
-
 
1086
        EventHandler.on(document, 'keydown.bs.dropdown.data-api', '.dropdown-menu', (event) => {
-
 
1087
            const pendingPromise = addPendingJSPromise('addNavigation-' + state.inputId + '-' + event.key);
-
 
1088
            if (event.key === "Escape" && inputElement.attr('aria-expanded') === "true") {
-
 
1089
                event.stopImmediatePropagation();
-
 
1090
                return pendingPromise.resolve(closeSuggestions(state));
-
 
1091
            }
-
 
1092
            return pendingPromise.resolve();
-
 
1093
        });
1079
    };
1094
    };
Línea 1080... Línea 1095...
1080
 
1095
 
1081
    /**
1096
    /**
1082
     * Create and return an unresolved Promise for some pending JS.
1097
     * Create and return an unresolved Promise for some pending JS.
Línea 1276... Línea 1291...
1276
                M.util.js_complete(pendingKey);
1291
                M.util.js_complete(pendingKey);
1277
                notification.exception(error);
1292
                notification.exception(error);
1278
            });
1293
            });
1279
    };
1294
    };
Línea -... Línea 1295...
-
 
1295
 
-
 
1296
    /**
-
 
1297
     * Announces changes to a tag in the autocomplete form.
-
 
1298
     *
-
 
1299
     * Updates the text content of a status element to inform users about the addition or removal
-
 
1300
     * of a tag. This is useful for accessibility purposes, ensuring screen readers can notify users
-
 
1301
     * of changes in the autocomplete component.
-
 
1302
     *
-
 
1303
     * @param {string} selectionId - The ID of the selection element used to locate the announcer element.
-
 
1304
     * @param {string|null|undefined} tagname - The name of the tag that was added or removed.
-
 
1305
     * @param {string} action - The action performed on the tag (e.g., "added" or "removed").
-
 
1306
     */
-
 
1307
    const announceChanges = async(selectionId, tagname, action) => {
-
 
1308
        if (!tagname) {
-
 
1309
            return;
-
 
1310
        }
-
 
1311
 
-
 
1312
        const status = document.getElementById(`${selectionId}-announcer`);
-
 
1313
        if (!status) {
-
 
1314
            return;
-
 
1315
        }
-
 
1316
 
-
 
1317
        status.textContent = await str.get_string(action, 'core', tagname);
-
 
1318
 
-
 
1319
        // Remove the status message after 4 seconds to prevent screen readers from announcing it.
-
 
1320
        setTimeout(() => {
-
 
1321
            status.textContent = '';
-
 
1322
        }, 4000);
-
 
1323
 
-
 
1324
    };
1280
 
1325
 
1281
    return {
1326
    return {
1282
        // Public variables and functions.
1327
        // Public variables and functions.
Línea 1283... Línea 1328...
1283
        enhanceField: enhanceField,
1328
        enhanceField: enhanceField,