first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

74
core/misc/active-link.js Executable file
View File

@@ -0,0 +1,74 @@
/**
* @file
* Attaches behaviors for Drupal's active link marking.
*/
(function (Drupal, drupalSettings) {
/**
* Append is-active class.
*
* The link is only active if its path corresponds to the current path, the
* language of the linked path is equal to the current language, and if the
* query parameters of the link equal those of the current request, since the
* same request with different query parameters may yield a different page
* (e.g. pagers, exposed View filters).
*
* Does not discriminate based on element type, so allows you to set the
* is-active class on any element: a, li…
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.activeLinks = {
attach(context) {
// Start by finding all potentially active links.
const path = drupalSettings.path;
const queryString = JSON.stringify(path.currentQuery);
const querySelector = path.currentQuery
? `[data-drupal-link-query='${queryString}']`
: ':not([data-drupal-link-query])';
const originalSelectors = [
`[data-drupal-link-system-path="${path.currentPath}"]`,
];
let selectors;
// If this is the front page, we have to check for the <front> path as
// well.
if (path.isFront) {
originalSelectors.push('[data-drupal-link-system-path="<front>"]');
}
// Add language filtering.
selectors = [].concat(
// Links without any hreflang attributes (most of them).
originalSelectors.map((selector) => `${selector}:not([hreflang])`),
// Links with hreflang equals to the current language.
originalSelectors.map(
(selector) => `${selector}[hreflang="${path.currentLanguage}"]`,
),
);
// Add query string selector for pagers, exposed filters.
selectors = selectors.map((current) => current + querySelector);
// Query the DOM.
const activeLinks = context.querySelectorAll(selectors.join(','));
const il = activeLinks.length;
for (let i = 0; i < il; i++) {
activeLinks[i].classList.add('is-active');
activeLinks[i].setAttribute('aria-current', 'page');
}
},
detach(context, settings, trigger) {
if (trigger === 'unload') {
const activeLinks = context.querySelectorAll(
'[data-drupal-link-system-path].is-active',
);
const il = activeLinks.length;
for (let i = 0; i < il; i++) {
activeLinks[i].classList.remove('is-active');
activeLinks[i].removeAttribute('aria-current');
}
}
},
};
})(Drupal, drupalSettings);

1921
core/misc/ajax.js Executable file

File diff suppressed because it is too large Load Diff

117
core/misc/announce.js Executable file
View File

@@ -0,0 +1,117 @@
/**
* @file
* Adds an HTML element and method to trigger audio UAs to read system messages.
*
* Use {@link Drupal.announce} to indicate to screen reader users that an
* element on the page has changed state. For instance, if clicking a link
* loads 10 more items into a list, one might announce the change like this.
*
* @example
* $('#search-list')
* .on('itemInsert', function (event, data) {
* // Insert the new items.
* $(data.container.el).append(data.items.el);
* // Announce the change to the page contents.
* Drupal.announce(Drupal.t('@count items added to @container',
* {'@count': data.items.length, '@container': data.container.title}
* ));
* });
*/
(function (Drupal, debounce) {
let liveElement;
const announcements = [];
/**
* Builds a div element with the aria-live attribute and add it to the DOM.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the behavior for drupalAnnounce.
*/
Drupal.behaviors.drupalAnnounce = {
attach(context) {
// Create only one aria-live element.
if (!liveElement) {
liveElement = document.createElement('div');
liveElement.id = 'drupal-live-announce';
liveElement.className = 'visually-hidden';
liveElement.setAttribute('aria-live', 'polite');
liveElement.setAttribute('aria-busy', 'false');
document.body.appendChild(liveElement);
}
},
};
/**
* Concatenates announcements to a single string; appends to the live region.
*/
function announce() {
const text = [];
let priority = 'polite';
let announcement;
// Create an array of announcement strings to be joined and appended to the
// aria live region.
const il = announcements.length;
for (let i = 0; i < il; i++) {
announcement = announcements.pop();
text.unshift(announcement.text);
// If any of the announcements has a priority of assertive then the group
// of joined announcements will have this priority.
if (announcement.priority === 'assertive') {
priority = 'assertive';
}
}
if (text.length) {
// Clear the liveElement so that repeated strings will be read.
liveElement.innerHTML = '';
// Set the busy state to true until the node changes are complete.
liveElement.setAttribute('aria-busy', 'true');
// Set the priority to assertive, or default to polite.
liveElement.setAttribute('aria-live', priority);
// Print the text to the live region. Text should be run through
// Drupal.t() before being passed to Drupal.announce().
liveElement.innerHTML = text.join('\n');
// The live text area is updated. Allow the AT to announce the text.
liveElement.setAttribute('aria-busy', 'false');
}
}
/**
* Triggers audio UAs to read the supplied text.
*
* The aria-live region will only read the text that currently populates its
* text node. Replacing text quickly in rapid calls to announce results in
* only the text from the most recent call to {@link Drupal.announce} being
* read. By wrapping the call to announce in a debounce function, we allow for
* time for multiple calls to {@link Drupal.announce} to queue up their
* messages. These messages are then joined and append to the aria-live region
* as one text node.
*
* @param {string} text
* A string to be read by the UA.
* @param {string} [priority='polite']
* A string to indicate the priority of the message. Can be either
* 'polite' or 'assertive'.
*
* @return {function}
* The return of the call to debounce.
*
* @see http://www.w3.org/WAI/PF/aria-practices/#liveprops
*/
Drupal.announce = function (text, priority) {
// Save the text and priority into a closure variable. Multiple simultaneous
// announcements will be concatenated and read in sequence.
announcements.push({
text,
priority,
});
// Immediately invoke the function that debounce returns. 200 ms is right at
// the cusp where humans notice a pause, so we will wait
// at most this much time before the set of queued announcements is read.
return debounce(announce, 200)();
};
})(Drupal, Drupal.debounce);

283
core/misc/autocomplete.js Executable file
View File

@@ -0,0 +1,283 @@
/**
* @file
* Autocomplete based on jQuery UI.
*/
(function ($, Drupal) {
let autocomplete;
/**
* Helper splitting terms from the autocomplete value.
*
* @function Drupal.autocomplete.splitValues
*
* @param {string} value
* The value being entered by the user.
*
* @return {Array}
* Array of values, split by comma.
*/
function autocompleteSplitValues(value) {
// We will match the value against comma-separated terms.
const result = [];
let quote = false;
let current = '';
const valueLength = value.length;
let character;
for (let i = 0; i < valueLength; i++) {
character = value.charAt(i);
if (character === '"') {
current += character;
quote = !quote;
} else if (character === ',' && !quote) {
result.push(current.trim());
current = '';
} else {
current += character;
}
}
if (value.length > 0) {
result.push(current.trim());
}
return result;
}
/**
* Returns the last value of a multi-value textfield.
*
* @function Drupal.autocomplete.extractLastTerm
*
* @param {string} terms
* The value of the field.
*
* @return {string}
* The last value of the input field.
*/
function extractLastTerm(terms) {
return autocomplete.splitValues(terms).pop();
}
/**
* The search handler is called before a search is performed.
*
* @function Drupal.autocomplete.options.search
*
* @param {object} event
* The event triggered.
*
* @return {boolean}
* Whether to perform a search or not.
*/
function searchHandler(event) {
const options = autocomplete.options;
if (options.isComposing) {
return false;
}
const term = autocomplete.extractLastTerm(event.target.value);
// Abort search if the first character is in firstCharacterBlacklist.
if (term.length > 0 && options.firstCharacterBlacklist.includes(term[0])) {
return false;
}
// Only search when the term is at least the minimum length.
return term.length >= options.minLength;
}
/**
* JQuery UI autocomplete source callback.
*
* @param {object} request
* The request object.
* @param {function} response
* The function to call with the response.
*/
function sourceData(request, response) {
const elementId = this.element.attr('id');
if (!(elementId in autocomplete.cache)) {
autocomplete.cache[elementId] = {};
}
/**
* Filter through the suggestions removing all terms already tagged and
* display the available terms to the user.
*
* @param {object} suggestions
* Suggestions returned by the server.
*/
function showSuggestions(suggestions) {
const tagged = autocomplete.splitValues(request.term);
const il = tagged.length;
for (let i = 0; i < il; i++) {
const index = suggestions.indexOf(tagged[i]);
if (index >= 0) {
suggestions.splice(index, 1);
}
}
response(suggestions);
}
// Get the desired term and construct the autocomplete URL for it.
const term = autocomplete.extractLastTerm(request.term);
/**
* Transforms the data object into an array and update autocomplete results.
*
* @param {object} data
* The data sent back from the server.
*/
function sourceCallbackHandler(data) {
autocomplete.cache[elementId][term] = data;
// Send the new string array of terms to the jQuery UI list.
showSuggestions(data);
}
// Check if the term is already cached.
if (autocomplete.cache[elementId].hasOwnProperty(term)) {
showSuggestions(autocomplete.cache[elementId][term]);
} else {
const options = $.extend(
{ success: sourceCallbackHandler, data: { q: term } },
autocomplete.ajax,
);
$.ajax(this.element.attr('data-autocomplete-path'), options);
}
}
/**
* Handles an autocomplete focus event.
*
* @return {boolean}
* Always returns false.
*/
function focusHandler() {
return false;
}
/**
* Handles an autocomplete select event.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} ui
* The jQuery UI settings object.
*
* @return {boolean}
* Returns false to indicate the event status.
*/
function selectHandler(event, ui) {
const terms = autocomplete.splitValues(event.target.value);
// Remove the current input.
terms.pop();
// Add the selected item.
terms.push(ui.item.value);
event.target.value = terms.join(', ');
// Return false to tell jQuery UI that we've filled in the value already.
return false;
}
/**
* Override jQuery UI _renderItem function to output HTML by default.
*
* @param {jQuery} ul
* jQuery collection of the ul element.
* @param {object} item
* The list item to append.
*
* @return {jQuery}
* jQuery collection of the ul element.
*/
function renderItem(ul, item) {
return $('<li>').append($('<a>').html(item.label)).appendTo(ul);
}
/**
* Attaches the autocomplete behavior to all required fields.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the autocomplete behaviors.
* @prop {Drupal~behaviorDetach} detach
* Detaches the autocomplete behaviors.
*/
Drupal.behaviors.autocomplete = {
attach(context) {
// Act on textfields with the "form-autocomplete" class.
once('autocomplete', 'input.form-autocomplete', context).forEach(
(element) => {
const $autocomplete = $(element);
// Allow options to be overridden per instance.
const blacklist = $autocomplete.attr(
'data-autocomplete-first-character-blacklist',
);
$.extend(autocomplete.options, {
firstCharacterBlacklist: blacklist || '',
});
// Use jQuery UI Autocomplete on the textfield.
$autocomplete.autocomplete(autocomplete.options).each(function () {
$(this).data('ui-autocomplete')._renderItem =
autocomplete.options.renderItem;
});
// Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
$autocomplete.on('compositionstart.autocomplete', () => {
autocomplete.options.isComposing = true;
});
$autocomplete.on('compositionend.autocomplete', () => {
autocomplete.options.isComposing = false;
});
},
);
},
detach(context, settings, trigger) {
if (trigger === 'unload') {
$(
once.remove('autocomplete', 'input.form-autocomplete', context),
).autocomplete('destroy');
}
},
};
/**
* Autocomplete object implementation.
*
* @namespace Drupal.autocomplete
*/
autocomplete = {
cache: {},
// Exposes options to allow overriding by contrib.
splitValues: autocompleteSplitValues,
extractLastTerm,
// jQuery UI autocomplete options.
/**
* JQuery UI option object.
*
* @name Drupal.autocomplete.options
*/
options: {
source: sourceData,
focus: focusHandler,
search: searchHandler,
select: selectHandler,
renderItem,
minLength: 1,
// Custom options, used by Drupal.autocomplete.
firstCharacterBlacklist: '',
// Custom options, indicate IME usage status.
isComposing: false,
},
ajax: {
dataType: 'json',
jsonp: false,
},
};
Drupal.autocomplete = autocomplete;
})(jQuery, Drupal);

47
core/misc/batch.js Executable file
View File

@@ -0,0 +1,47 @@
/**
* @file
* Drupal's batch API.
*/
(function ($, Drupal) {
/**
* Attaches the batch behavior to progress bars.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.batch = {
attach(context, settings) {
const batch = settings.batch;
const $progress = $(once('batch', '[data-drupal-progress]'));
let progressBar;
// Success: redirect to the summary.
function updateCallback(progress, status, pb) {
if (progress === '100') {
pb.stopMonitoring();
window.location = `${batch.uri}&op=finished`;
}
}
function errorCallback(pb) {
$progress.prepend($('<p class="error"></p>').html(batch.errorMessage));
$('#wait').hide();
}
if ($progress.length) {
progressBar = new Drupal.ProgressBar(
'updateprogress',
updateCallback,
'POST',
errorCallback,
);
progressBar.setProgress(-1, batch.initMessage);
progressBar.startMonitoring(`${batch.uri}&op=do`, 10);
// Remove HTML from no-js progress bar.
$progress.empty();
// Append the JS progressbar element.
$progress.append(progressBar.element);
}
},
};
})(jQuery, Drupal);

15
core/misc/checkbox.js Executable file
View File

@@ -0,0 +1,15 @@
/**
* @file
* Defines checkbox theme functions.
*/
((Drupal) => {
/**
* Theme function for a checkbox.
*
* @return {string}
* The HTML markup for the checkbox.
*/
Drupal.theme.checkbox = () =>
`<input type="checkbox" class="form-checkbox"/>`;
})(Drupal);

View File

@@ -0,0 +1,49 @@
/**
* @file
* Throbber.
*/
.ajax-progress {
display: inline-block;
padding: 1px 5px 2px 5px;
}
[dir="rtl"] .ajax-progress {
float: right;
}
.ajax-progress-throbber .throbber {
display: inline;
padding: 1px 6px 2px;
background: transparent url(../throbber-active.gif) no-repeat 0 center;
}
.ajax-progress-throbber .message {
display: inline;
padding: 1px 5px 2px;
}
tr .ajax-progress-throbber .throbber {
margin: 0 2px;
}
.ajax-progress-bar {
width: 16em;
}
/* Full screen throbber */
.ajax-progress-fullscreen {
position: fixed;
z-index: 1261;
top: 48.5%;
/* Can't do center:50% middle: 50%, so approximate it for a typical window size. */
left: 49%; /* LTR */
width: 24px;
height: 24px;
padding: 4px;
opacity: 0.9;
border-radius: 7px;
background-color: #232323;
background-image: url(../loading-small.gif);
background-repeat: no-repeat;
background-position: center center;
}
[dir="rtl"] .ajax-progress-fullscreen {
right: 49%;
left: auto;
}

View File

@@ -0,0 +1,22 @@
/**
* @file
* Visual styles for animated throbber.
*
* @see autocomplete.js
*/
.js input.form-autocomplete {
background-image: url(../throbber-inactive.png);
background-repeat: no-repeat;
background-position: 100% center; /* LTR */
}
.js[dir="rtl"] input.form-autocomplete {
background-position: 0% center;
}
.js input.form-autocomplete.ui-autocomplete-loading {
background-image: url(../throbber-active.gif);
background-position: 100% center; /* LTR */
}
.js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
background-position: 0% center;
}

View File

@@ -0,0 +1,51 @@
/**
* @file
* Progress behavior.
*
* @see progress.js
*/
.progress {
position: relative;
}
.progress__track {
min-width: 100px;
max-width: 100%;
height: 16px;
margin-top: 5px;
border: 1px solid;
background-color: #fff;
}
.progress__bar {
width: 3%;
min-width: 3%;
max-width: 100%;
height: 16px;
background-color: #000;
}
.progress__description,
.progress__percentage {
overflow: hidden;
margin-top: 0.2em;
color: #555;
font-size: 0.875em;
}
.progress__description {
float: left; /* LTR */
}
[dir="rtl"] .progress__description {
float: right;
}
.progress__percentage {
float: right; /* LTR */
}
[dir="rtl"] .progress__percentage {
float: left;
}
.progress--small .progress__track {
height: 7px;
}
.progress--small .progress__bar {
height: 7px;
background-size: 20px 20px;
}

View File

@@ -0,0 +1,5 @@
table.sticky-header thead {
position: sticky;
z-index: 500;
top: var(--drupal-displace-offset-top, 0);
}

View File

@@ -0,0 +1,103 @@
/**
* @file
* Table drag behavior.
*
* @see tabledrag.js
*/
body.drag {
cursor: move;
}
tr.region-title {
font-weight: bold;
}
tr.region-message {
color: #999;
}
tr.region-populated {
display: none;
}
tr.add-new .tabledrag-changed {
display: none;
}
.draggable a.tabledrag-handle {
float: left; /* LTR */
overflow: hidden;
height: 1.7em;
margin-left: -1em; /* LTR */
cursor: move;
text-decoration: none;
}
[dir="rtl"] .draggable a.tabledrag-handle {
float: right;
margin-right: -1em;
margin-left: 0;
}
a.tabledrag-handle:hover {
text-decoration: none;
}
a.tabledrag-handle .handle {
width: 14px;
height: 14px;
margin: -0.4em 0.5em 0;
padding: 0.42em 0.5em;
background: url(../icons/787878/move.svg) no-repeat 6px 7px;
}
a.tabledrag-handle:hover .handle,
a.tabledrag-handle:focus .handle {
background-image: url(../icons/000000/move.svg);
}
@media (forced-colors: active) {
a.tabledrag-handle .handle,
a.tabledrag-handle:hover .handle,
a.tabledrag-handle:focus .handle {
background: linktext;
-webkit-mask: url(../icons/787878/move.svg) no-repeat 6px 7px;
mask: url(../icons/787878/move.svg) no-repeat 6px 7px;
}
a.tabledrag-handle:focus {
outline: solid 1px transparent;
}
}
.touchevents .draggable td {
padding: 0 10px;
}
.touchevents .draggable .menu-item__link {
display: inline-block;
padding: 10px 0;
}
.touchevents a.tabledrag-handle {
width: 40px;
height: 44px;
}
.touchevents a.tabledrag-handle .handle {
height: 21px;
background-position: 40% 19px; /* LTR */
}
[dir="rtl"] .touch a.tabledrag-handle .handle {
background-position: right 40% top 19px;
}
.touchevents .draggable.drag a.tabledrag-handle .handle {
background-position: 50% -32px;
}
.tabledrag-toggle-weight-wrapper {
text-align: right; /* LTR */
}
[dir="rtl"] .tabledrag-toggle-weight-wrapper {
text-align: left;
}
.indentation {
float: left; /* LTR */
width: 20px;
height: 1.7em;
margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */
padding: 0.42em 0 0.42em 0.6em; /* LTR */
}
[dir="rtl"] .indentation {
float: right;
margin: -0.4em -0.4em -0.4em 0.2em;
padding: 0.42em 0.6em 0.42em 0;
}

View File

@@ -0,0 +1,18 @@
/**
* @file
* Visual styles for a nested tree child.
*/
div.tree-child {
background: url(../tree.png) no-repeat 11px center; /* LTR */
}
div.tree-child-last {
background: url(../tree-bottom.png) no-repeat 11px center; /* LTR */
}
[dir="rtl"] div.tree-child,
[dir="rtl"] div.tree-child-last {
background-position: -65px center;
}
div.tree-child-horizontal {
background: url(../tree.png) no-repeat -11px center;
}

820
core/misc/cspell/dictionary.txt Executable file
View File

@@ -0,0 +1,820 @@
absolutezero
adamson
addedline
addtogroup
adminforms
afterclose
aftercreate
ahah
ajaxing
allwords
alphadecimal
ampm
anyall
archiver
archivers
arrowrefresh
arrowreturn
arrowreturnthick
arrowstop
arrowthick
arrowthickstop
assertable
autoconfiguration
autoconfigure
autoconfigured
autocreate
autocreated
autocreation
autoescape
autoescaped
autoescaping
autogenerator
autoloadable
autoloaded
autoloader
autoloaders
autoloading
autop
autoplace
autoplay
autoreply
autosubmit
autowire
autowired
autowiring
backlink
backlinks
bakeware
barbar
barchart
basefield
basepath
basetheme
beatle
bebebe
beforeclose
beforecreate
behat
bergmann
berne
bgblue
bgred
bidi
bigpipe
bitmask
bkmgtpezy
bkslshv
blockarticles
blockbasic
blockcontent
blockfooter
blocknodebundle
blockpromoted
blockquotes
blockrecipe
blockrelated
bodyless
boing
bovigo
brotli
browserkit
browsertest
browsertestbase
brûlée
bubbleable
bundleable
bundleless
bundlenode
buttonpane
buttonset
buttontext
buytaert
bytea
cachetag
cachetags
callables
callout
camelid
camelids
canvastext
castable
catalana
catchable
ccyy
ccyymm
ccyymmdd
chainedfast
changelist
checkboxified
checkboxifier
checkboxify
checkboxradio
chocolatiers
chromedriver
chtext
chumillas
chvotes
cidhash
cids
circlesmall
ckeditor
classloader
classmap
classmaps
classname
classtype
cldr
clearfix
closethick
codesniffer
colgroups
colinodell
colour
colspans
columnschange
comida
commenters
compositionend
compositionstart
configentity
configurator
contentblock
contextuals
controlgroup
conv
createrole
createuser
crema
crossout
crème
csrftoken
csslintrc
csstools
curle
curlopt
customevent
customrequest
cweagans
datelist
daterange
datestamp
datetimeiso
datetimeplus
dbtng
dburl
dealerdirect
defaultable
defgroup
delayable
deletedline
delsp
denormalizable
denormalizer
denormalizers
derivedfrom
desaturate
desaturated
desaturates
desaturating
destructable
devel
deviantart
dflt
dialogsave
diffchange
differring
divs
dnumber
dockerhub
docroot
docroots
dotenv
downcasting
doxygen
dragtable
drillable
drivertest
dropbutton
dropbuttons
drupaldevdays
drupalelementstyle
drupalelementstylecommand
drupalelementstyleediting
drupalelementstyleui
drupalhtmlbuilder
drupalimage
drupalism
drupalisms
drupallink
drupalmedia
drupalmediaediting
drupalmediatoolbar
drupalorg
editables
egulias
elbonia
elems
encapsed
endapply
endautoescape
endcode
endembed
endlink
endmacro
endset
endtrans
enoki
entityviewedit
errmode
etag
eurozone
evenodd
eventhandler
exampleurl
exitcode
expirable
extlink
extrasmall
extraspace
failonerror
fakepath
falsey
falsish
fapi
fastcgi
favourite
favstar
fcgi
fdiv
fieldable
fieldapi
fieldblock
fieldgroup
fieldgroups
fieldlayout
fieldnames
fieldsets
filemime
filesystems
filetransfer
findwith
flickr
flipfit
foat
fodg
fodp
fods
fodt
formattable
fouc
fourcol
fraîche
frontpage
fudgy
fulldata
fulldate
fulltext
funic
gabilondo
gids
gloop
googleapis
greeking
gripsmall
groupable
groupby
groupname
groupwise
guzzlehttp
hande
hateoas
hexcode
hilited
hmac
hookname
hosters
hrefs
htmlcorrector
httpheader
httplug
httponly
hwldf
icann
iconwrap
idekey
iframes
iframeupload
imagecache
imagetextalternative
indexname
inited
inno
instantiator
interactable
interruptible
introspectable
invalidators
invokable
isam
isinstallable
itok
ized
javascripts
jessebeach
jqueryui
jsonlint
jssdk
justinrainbow
kangarookitten
kernighan
keyevent
keypresses
keyvalue
kinberg
kolkata
lamoids
languageswitcher
libc
licious
lified
lightninggreen
linebreak
linebreaks
linkability
linkback
linkgenerator
linkification
linkset
linktext
litespeed
llamaids
lnumber
loadjs
localemark
localetranslatedirty
logicalcpu
lolspeak
longblob
longerthan
longtext
loquesea
lrdd
lstitle
ltitle
lzma
mainpage
mank
maryjane
matchout
maximumred
maxlifetime
maynot
mbytes
mdhash
mediaimagetextalternative
mediaimagetextalternativeediting
mediaimagetextalternativeui
mediumint
mediumtext
messagekey
metapackage
metapackages
metatag
metatags
mglaman
micheh
mikey
mimetypes
minifyzombies
minusthick
mlfr
mocktail
mocktails
mocky
moderatable
modernizr
modulenarrow
mojito
montag
msgctxt
msgid
msgstr
mucho
mulrev
mulrevpub
multibuys
multibyte
multicardinality
multilanguage
multipass
multisite
multistar
multistep
multivalue
multivalued
muuuh
myclabs
mysqladmin
mysqldump
mystarterkit
namespacing
navs
ndocs
necolas
newcol
newfieldinitial
newwin
nids
nightwatch
nightwatchjs
nikic
nmsgid
nmsgstr
nntp
noadd
nocache
nocase
nodelist
nodir
nofields
nojs
nolink
nomask
nonconfigurable
noquotes
nosniff
notexisting
notnull
notsimpletest
nourriture
nplurals
nproc
ntfs
nyan
nyancat
nyans
oembed
omittable
onecol
oneplusfourgrid
onetwo
onewidgetfield
optgroups
optin
orgchart
ossp
otel
otlp
outdent
outro
overridable
overrider
overrider's
overriders
owasp
pagecache
pagetop
paramconverter
parseable
parsererror
pathauto
pcre
pcss
peast
percona
permissionless
persistable
phpass
phpcbf
phpcodesniffer
phpcs
phpdocumentor
phpserialize
phpspec
pickable
picturefill
pingback
pjpeg
placeholdered
placeholdering
plusthick
porterstemmer
postcondition
postcss
postfields
postorder
postprocess
postupdate
poweredby
precache
preconfigured
predelete
prefixsqlite
preinstall
preloadable
preorder
prepopulate
prepopulated
prepopulates
prepopulating
prepopulation
preprocess
preprocesses
preprocessors
prerender
prerendered
presave
pretransaction
preuninstall
processlist
proname
prophesize
prophesized
prophesizing
protobuf
protossl
proxied
proxying
pseudotag
ptablenode
puzzlepiece
pwprompt
querystring
questionmark
quicklinks
quickstart
quinlan
qvalue
qvalues
qwer
ralouphie
rasterizable
rasterizes
readmore
realpaths
realword
rebuilder
refback
referenceables
referencers
referer
refinable
reindexing
relname
relpersistence
renderable
renderables
renderered
reparenting
replyto
resave
resaved
resaving
restrictor
restripe
restriped
restui
rethrown
returntransfer
revisionable
revisioned
revisionid
revisioning
revlog
revpub
ribisi
ritchie
rowspans
rsyslog
rtsp
ruleset
sameline
samename
sameorigin
samesite
savepoints
sayre
schemaapi
schemeless
scorewords
scriptable
scrollbars
sebastianbergmann
seld
selectbox
sess
settingstray
shorterthan
shortlink
silverlight
singlebyte
sirbrillig
sisko
sitename
sitewide
skiptags
slatkin
slevomat
smacss
smalldatetime
somecompany
sortablejs
specialchars
spiffiness
splitbutton
splitbuttons
squaresmall
squiz
squizlabs
srcset
ssess
starterkit
statuscode
stddev
stickied
stitle
streamwrapper
streamwrappers
strikethrough
striptags
strs
sttid
styleguide
stylelint
stylescombo
subarrays
subchild
subclassing
subcompile
subcondition
subdir
subelements
subform
subform's
subforms
subkey
subkeys
suboption
subparse
subplugins
subproject
subprojects
subproperties
subqueries
subquery
subrequest
subrequest's
subrequests
subresults
subselect
substrategies
subsubtheme
subtheme
subtheme's
subthemes
subtoken
subvalue
subvalues
subview
sulaco
supercede
svgz
synchronizable
syrop
tabbingmanager
tabledrag
tableheader
tableinfo
tableresponsive
tableselect
tablesort
tablesort's
tablesorts
tabset
tabwidth
tappable
taskless
tcomment
templating
testbot
testbots
testcase
testgroups
testlogger
testsuite
testsuites
textareas
textboxes
textfields
themename
themer
themers
themey
theseer
threecol
tids
timegate
timemap
timespan
tinyint
tinytext
titlealert
titlebar
tlds
tmpfs
toggleable
tongatapu
toolkits
toolongdomainexampledomainexampledomainexampledomainexampledomain
toplevel
torder
touchevents
trackback
trailingslash
transferthick
translatables
trayblock
trgm
truecolor
twistie
twocol
tzid
uids
unban
uncacheable
uncategorized
undoable
unfieldable
ungroup
ungroupable
unindexed
uninstallation
uninstallations
unmanaged
unminified
unmoderated
unpromote
unpublish
unpublishes
unpublishing
unrevisionable
unrouted
unsanitized
unserialization
unserialized
unserializes
unserializing
unsets
unsetting
unsticky
untabbable
upcasted
upcasting
updateprogress
uploaders
uploadprogress
upscaling
urandom
userinfo
userref
vampirize
varchar
versionable
versionless
vfsstream
viewports
vocabs
wcag
wcprops
webassert
webcal
webflo
webhosting
webmention
webmozart
webroot
webservers
widthx
wordsafe
writeln
wwwrun
xbap
xbitmap
xfbml
xmlhttp
xmlhttprequest
xpsdocument
xtmpl
yamls
yearless
yokotsoko
yottabyte
yottabytes
zettabyte
zettabytes
zipf's
zoneinfo
zoomin
zoomout
zxvf

View File

@@ -0,0 +1,17 @@
bartik
dblog
dependee
dependee's
dependees
drupalci
druplicon
drush
langcode
langcodes
lullabot
olivero
olivero's
simpletest
tempstore
umami
validatable

48
core/misc/debounce.js Executable file
View File

@@ -0,0 +1,48 @@
/**
* @file
* Adapted from underscore.js with the addition Drupal namespace.
*/
/**
* Limits the invocations of a function in a given time frame.
*
* The debounce function wrapper should be used sparingly. One clear use case
* is limiting the invocation of a callback attached to the window resize event.
*
* Before using the debounce function wrapper, consider first whether the
* callback could be attached to an event that fires less frequently or if the
* function can be written in such a way that it is only invoked under specific
* conditions.
*
* @param {function} func
* The function to be invoked.
* @param {number} wait
* The time period within which the callback function should only be
* invoked once. For example if the wait period is 250ms, then the callback
* will only be called at most 4 times per second.
* @param {boolean} immediate
* Whether we wait at the beginning or end to execute the function.
*
* @return {function}
* The debounced function.
*/
Drupal.debounce = function (func, wait, immediate) {
let timeout;
let result;
return function (...args) {
const context = this;
const later = function () {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
}
return result;
};
};

31
core/misc/details-aria.js Executable file
View File

@@ -0,0 +1,31 @@
/**
* @file
* Add aria attribute handling for details and summary elements.
*/
(function ($, Drupal) {
/**
* Handles `aria-expanded` and `aria-pressed` attributes on details elements.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.detailsAria = {
attach() {
$(once('detailsAria', 'body')).on(
'click.detailsAria',
'summary',
(event) => {
const $summary = $(event.currentTarget);
const open =
$(event.currentTarget.parentNode).attr('open') === 'open'
? 'false'
: 'true';
$summary.attr({
'aria-expanded': open,
});
},
);
},
};
})(jQuery, Drupal);

View File

@@ -0,0 +1,105 @@
/**
* @file
* Adds a summary of a details element's contents to its summary element.
*/
(($, Drupal) => {
/**
* The DetailsSummarizedContent object represents a single details element.
*
* @constructor Drupal.DetailsSummarizedContent
*
* @param {HTMLElement} node
* A details element, the summary of which may have supplemental text.
* The supplemental text summarizes the details element's contents.
*/
function DetailsSummarizedContent(node) {
this.$node = $(node);
this.setupSummary();
}
$.extend(
DetailsSummarizedContent,
/** @lends Drupal.DetailsSummarizedContent */ {
/**
* Holds references to instantiated DetailsSummarizedContent objects.
*
* @type {Array.<Drupal.DetailsSummarizedContent>}
*/
instances: [],
},
);
$.extend(
DetailsSummarizedContent.prototype,
/** @lends Drupal.DetailsSummarizedContent# */ {
/**
* Initialize and setup summary events and markup.
*
* @fires event:summaryUpdated
*
* @listens event:summaryUpdated
*/
setupSummary() {
this.$detailsSummarizedContentWrapper = $(
Drupal.theme('detailsSummarizedContentWrapper'),
);
this.$node
.on('summaryUpdated', this.onSummaryUpdated.bind(this))
.trigger('summaryUpdated')
.find('> summary')
.append(this.$detailsSummarizedContentWrapper);
},
/**
* Update summary.
*/
onSummaryUpdated() {
const text = this.$node.drupalGetSummary();
this.$detailsSummarizedContentWrapper.html(
Drupal.theme('detailsSummarizedContentText', text),
);
},
},
);
/**
* Adds a summary of a details element's contents to its summary element.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches behavior for the details element.
*/
Drupal.behaviors.detailsSummary = {
attach(context) {
DetailsSummarizedContent.instances =
DetailsSummarizedContent.instances.concat(
once('details', 'details', context).map(
(details) => new DetailsSummarizedContent(details),
),
);
},
};
Drupal.DetailsSummarizedContent = DetailsSummarizedContent;
/**
* The element containing a wrapper for summarized details content.
*
* @return {string}
* The markup for the element that will contain the summarized content.
*/
Drupal.theme.detailsSummarizedContentWrapper = () =>
`<span class="summary"></span>`;
/**
* Formats the summarized details content text.
*
* @param {string|null} [text]
* (optional) The summarized content text displayed in the summary.
* @return {string}
* The formatted summarized content text.
*/
Drupal.theme.detailsSummarizedContentText = (text) =>
text ? ` (${text})` : '';
})(jQuery, Drupal);

44
core/misc/details.js Executable file
View File

@@ -0,0 +1,44 @@
/**
* @file
* Additional functionality for HTML5 details elements.
*/
(function ($) {
/**
* Open parent details elements of a targeted page fragment.
*
* Opens all (nested) details element on a hash change or fragment link click
* when the target is a child element, in order to make sure the targeted
* element is visible. Aria attributes on the summary
* are set by triggering the click event listener in details-aria.js.
*
* @param {jQuery.Event} e
* The event triggered.
* @param {jQuery} $target
* The targeted node as a jQuery object.
*/
const handleFragmentLinkClickOrHashChange = (e, $target) => {
$target.parents('details').not('[open]').find('> summary').trigger('click');
};
/**
* Binds a listener to handle fragment link clicks and URL hash changes.
*/
$('body').on(
'formFragmentLinkClickOrHashChange.details',
handleFragmentLinkClickOrHashChange,
);
/**
* Binds a listener to handle required fields in details elements.
*/
window.addEventListener(
'invalid',
(event) => {
if (event.target.matches('details input[required]')) {
handleFragmentLinkClickOrHashChange(event, $(event.target));
}
},
{ capture: true },
);
})(jQuery);

View File

@@ -0,0 +1,40 @@
/**
* @file
* Maintains and deprecates Dialog jQuery events.
*/
(function ($, Drupal, once) {
if (once('drupal-dialog-deprecation-listener', 'html').length) {
const eventSpecial = {
handle($event) {
const $element = $($event.target);
const event = $event.originalEvent;
const dialog = event.dialog;
const dialogArguments = [$event, dialog, $element, event?.settings];
$event.handleObj.handler.apply(this, dialogArguments);
},
};
$.event.special['dialog:beforecreate'] = eventSpecial;
$.event.special['dialog:aftercreate'] = eventSpecial;
$.event.special['dialog:beforeclose'] = eventSpecial;
$.event.special['dialog:afterclose'] = eventSpecial;
const listenDialogEvent = (event) => {
const windowEvents = $._data(window, 'events');
const isWindowHasDialogListener = windowEvents[event.type];
if (isWindowHasDialogListener) {
Drupal.deprecationError({
message: `jQuery event ${event.type} is deprecated in 10.3.0 and is removed from Drupal:12.0.0. See https://www.drupal.org/node/3422670`,
});
}
};
[
'dialog:beforecreate',
'dialog:aftercreate',
'dialog:beforeclose',
'dialog:afterclose',
].forEach((e) => window.addEventListener(e, listenDialogEvent));
}
})(jQuery, Drupal, once);

320
core/misc/dialog/dialog.ajax.js Executable file
View File

@@ -0,0 +1,320 @@
/**
* @file
* Extends the Drupal AJAX functionality to integrate the dialog API.
*/
(function ($, Drupal, { focusable }) {
/**
* Initialize dialogs for Ajax purposes.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the behaviors for dialog ajax functionality.
*/
Drupal.behaviors.dialog = {
attach(context, settings) {
const $context = $(context);
// Provide a known 'drupal-modal' DOM element for Drupal-based modal
// dialogs. Non-modal dialogs are responsible for creating their own
// elements, since there can be multiple non-modal dialogs at a time.
if (!$('#drupal-modal').length) {
// Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete
// sit on top of dialogs. For more information see
// http://api.jqueryui.com/theming/stacking-elements/.
$('<div id="drupal-modal" class="ui-front"></div>')
.hide()
.appendTo('body');
}
// Special behaviors specific when attaching content within a dialog.
// These behaviors usually fire after a validation error inside a dialog.
const $dialog = $context.closest('.ui-dialog-content');
if ($dialog.length) {
// Remove and replace the dialog buttons with those from the new form.
if ($dialog.dialog('option', 'drupalAutoButtons')) {
// Trigger an event to detect/sync changes to buttons.
$dialog.trigger('dialogButtonsChange');
}
setTimeout(function () {
// Account for pre-existing focus handling that may have already moved
// the focus inside the dialog.
if (!$dialog[0].contains(document.activeElement)) {
// Move focus to the first focusable element in the next event loop
// to allow dialog buttons to be changed first.
$dialog.dialog('instance')._focusedElement = null;
$dialog.dialog('instance')._focusTabbable();
}
}, 0);
}
const originalClose = settings.dialog.close;
// Overwrite the close method to remove the dialog on closing.
settings.dialog.close = function (event, ...args) {
originalClose.apply(settings.dialog, [event, ...args]);
// Check if the opener element is inside an AJAX container.
const $element = $(event.target);
const ajaxContainer = $element.data('uiDialog')
? $element
.data('uiDialog')
.opener.closest('[data-drupal-ajax-container]')
: [];
// If the opener element was in an ajax container, and focus is on the
// body element, we can assume focus was lost. To recover, focus is
// moved to the first focusable element in the container.
if (
ajaxContainer.length &&
(document.activeElement === document.body ||
$(document.activeElement).not(':visible'))
) {
const focusableChildren = focusable(ajaxContainer[0]);
if (focusableChildren.length > 0) {
setTimeout(() => {
focusableChildren[0].focus();
}, 0);
}
}
$(event.target).remove();
};
},
/**
* Scan a dialog for any primary buttons and move them to the button area.
*
* @param {jQuery} $dialog
* A jQuery object containing the element that is the dialog target.
*
* @return {Array}
* An array of buttons that need to be added to the button area.
*/
prepareDialogButtons($dialog) {
const buttons = [];
const $buttons = $dialog.find(
'.form-actions input[type=submit], .form-actions a.button, .form-actions a.action-link',
);
$buttons.each(function () {
const $originalButton = $(this);
this.style.display = 'none';
buttons.push({
text: $originalButton.html() || $originalButton.attr('value'),
class: $originalButton.attr('class'),
'data-once': $originalButton.data('once'),
click(e) {
// If the original button is an anchor tag, triggering the "click"
// event will not simulate a click. Use the click method instead.
if ($originalButton[0].tagName === 'A') {
$originalButton[0].click();
} else {
$originalButton
.trigger('mousedown')
.trigger('mouseup')
.trigger('click');
}
e.preventDefault();
},
});
});
return buttons;
},
};
/**
* Command to open a dialog.
*
* @param {Drupal.Ajax} ajax
* The Drupal Ajax object.
* @param {object} response
* Object holding the server response.
* @param {number} [status]
* The HTTP status code.
*
* @return {boolean|undefined}
* Returns false if there was no selector property in the response object.
*/
Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) {
if (!response.selector) {
return false;
}
let $dialog = $(response.selector);
if (!$dialog.length) {
// Create the element if needed.
$dialog = $(
`<div id="${response.selector.replace(
/^#/,
'',
)}" class="ui-front"></div>`,
).appendTo('body');
}
// Set up the wrapper, if there isn't one.
if (!ajax.wrapper) {
ajax.wrapper = $dialog.attr('id');
}
// Use the ajax.js insert command to populate the dialog contents.
response.command = 'insert';
response.method = 'html';
ajax.commands.insert(ajax, response, status);
// Move the buttons to the jQuery UI dialog buttons area.
response.dialogOptions = response.dialogOptions || {};
if (typeof response.dialogOptions.drupalAutoButtons === 'undefined') {
response.dialogOptions.drupalAutoButtons = true;
} else if (response.dialogOptions.drupalAutoButtons === 'false') {
response.dialogOptions.drupalAutoButtons = false;
} else {
response.dialogOptions.drupalAutoButtons =
!!response.dialogOptions.drupalAutoButtons;
}
if (
!response.dialogOptions.buttons &&
response.dialogOptions.drupalAutoButtons
) {
response.dialogOptions.buttons =
Drupal.behaviors.dialog.prepareDialogButtons($dialog);
}
// Bind dialogButtonsChange.
$dialog.on('dialogButtonsChange', () => {
const buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);
$dialog.dialog('option', 'buttons', buttons);
});
// Open the dialog itself.
response.dialogOptions = response.dialogOptions || {};
const dialog = Drupal.dialog($dialog.get(0), response.dialogOptions);
if (response.dialogOptions.modal) {
dialog.showModal();
} else {
dialog.show();
}
// Add the standard Drupal class for buttons for style consistency.
$dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions');
};
/**
* Command to close a dialog.
*
* If no selector is given, it defaults to trying to close the modal.
*
* @param {Drupal.Ajax} [ajax]
* The ajax object.
* @param {object} response
* Object holding the server response.
* @param {string} response.selector
* The selector of the dialog.
* @param {boolean} response.persist
* Whether to persist the dialog element or not.
* @param {number} [status]
* The HTTP status code.
*/
Drupal.AjaxCommands.prototype.closeDialog = function (
ajax,
response,
status,
) {
const $dialog = $(response.selector);
if ($dialog.length) {
Drupal.dialog($dialog.get(0)).close();
if (!response.persist) {
$dialog.remove();
}
}
// Unbind dialogButtonsChange.
$dialog.off('dialogButtonsChange');
};
/**
* Command to set a dialog property.
*
* JQuery UI specific way of setting dialog options.
*
* @param {Drupal.Ajax} [ajax]
* The Drupal Ajax object.
* @param {object} response
* Object holding the server response.
* @param {string} response.selector
* Selector for the dialog element.
* @param {string} response.optionsName
* Name of a key to set.
* @param {string} response.optionValue
* Value to set.
* @param {number} [status]
* The HTTP status code.
*/
Drupal.AjaxCommands.prototype.setDialogOption = function (
ajax,
response,
status,
) {
const $dialog = $(response.selector);
if ($dialog.length) {
$dialog.dialog('option', response.optionName, response.optionValue);
}
};
/**
* Binds a listener on dialog creation to handle the cancel link.
*
* @param {jQuery.Event} e
* The event triggered.
* @param {Drupal.dialog~dialogDefinition} dialog
* The dialog instance.
* @param {jQuery} $element
* The jQuery collection of the dialog element.
* @param {object} [settings]
* Dialog settings.
*/
window.addEventListener('dialog:aftercreate', (event) => {
const $element = $(event.target);
const dialog = event.dialog;
$element.on('click.dialog', '.dialog-cancel', (e) => {
dialog.close('cancel');
e.preventDefault();
e.stopPropagation();
});
});
/**
* Removes all 'dialog' listeners.
*
* @param {jQuery.Event} e
* The event triggered.
* @param {Drupal.dialog~dialogDefinition} dialog
* The dialog instance.
* @param {jQuery} $element
* jQuery collection of the dialog element.
*/
window.addEventListener('dialog:beforeclose', (e) => {
const $element = $(e.target);
$element.off('.dialog');
});
/**
* Ajax command to open URL in a modal dialog.
*
* @param {Drupal.Ajax} [ajax]
* An Ajax object.
* @param {object} response
* The Ajax response.
*/
Drupal.AjaxCommands.prototype.openModalDialogWithUrl = function (
ajax,
response,
) {
const dialogOptions = response.dialogOptions || {};
const elementSettings = {
progress: { type: 'throbber' },
dialogType: 'modal',
dialog: dialogOptions,
url: response.url,
httpMethod: 'GET',
};
Drupal.ajax(elementSettings).execute();
};
})(jQuery, Drupal, window.tabbable);

View File

@@ -0,0 +1,75 @@
/**
* @file
* Adds default classes to buttons for styling purposes.
*/
(function ($, { tabbable, isTabbable }) {
$.widget('ui.dialog', $.ui.dialog, {
options: {
buttonClass: 'button',
buttonPrimaryClass: 'button--primary',
},
_createButtons() {
const opts = this.options;
let primaryIndex;
let index;
const il = opts.buttons.length;
for (index = 0; index < il; index++) {
if (
opts.buttons[index].primary &&
opts.buttons[index].primary === true
) {
primaryIndex = index;
delete opts.buttons[index].primary;
break;
}
}
this._super();
const $buttons = this.uiButtonSet.children().addClass(opts.buttonClass);
if (typeof primaryIndex !== 'undefined') {
$buttons.eq(index).addClass(opts.buttonPrimaryClass);
}
},
// Override jQuery UI's `_focusTabbable()` so finding tabbable elements uses
// the core/tabbable library instead of jQuery UI's `:tabbable` selector.
_focusTabbable() {
// Set focus to the first match:
// 1. An element that was focused previously.
let hasFocus = this._focusedElement ? this._focusedElement.get(0) : null;
// 2. First element inside the dialog matching [autofocus].
if (!hasFocus) {
hasFocus = this.element.find('[autofocus]').get(0);
}
// 3. Tabbable element inside the content element.
// 4. Tabbable element inside the buttonpane.
if (!hasFocus) {
const $elements = [this.element, this.uiDialogButtonPane];
for (let i = 0; i < $elements.length; i++) {
const element = $elements[i].get(0);
if (element) {
const elementTabbable = tabbable(element);
hasFocus = elementTabbable.length ? elementTabbable[0] : null;
}
if (hasFocus) {
break;
}
}
}
// 5. The close button.
if (!hasFocus) {
const closeBtn = this.uiDialogTitlebarClose.get(0);
hasFocus = closeBtn && isTabbable(closeBtn) ? closeBtn : null;
}
// 6. The dialog itself.
if (!hasFocus) {
hasFocus = this.uiDialog.get(0);
}
$(hasFocus).eq(0).trigger('focus');
},
});
})(jQuery, window.tabbable);

123
core/misc/dialog/dialog.js Executable file
View File

@@ -0,0 +1,123 @@
/**
* @file
* Dialog API inspired by HTML5 dialog element.
*
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element
*/
class DrupalDialogEvent extends Event {
constructor(type, dialog, settings = null) {
super(`dialog:${type}`, { bubbles: true });
this.dialog = dialog;
this.settings = settings;
}
}
(function ($, Drupal, drupalSettings, bodyScrollLock) {
/**
* Default dialog options.
*
* @type {object}
*
* @prop {boolean} [autoOpen=true]
* @prop {string} [dialogClass='']
* @prop {string} [buttonClass='button']
* @prop {string} [buttonPrimaryClass='button--primary']
* @prop {function} close
*/
drupalSettings.dialog = {
autoOpen: true,
dialogClass: '',
// Drupal-specific extensions: see dialog.jquery-ui.js.
buttonClass: 'button',
buttonPrimaryClass: 'button--primary',
// When using this API directly (when generating dialogs on the client
// side), you may want to override this method and do
// `jQuery(event.target).remove()` as well, to remove the dialog on
// closing.
close(event) {
Drupal.dialog(event.target).close();
Drupal.detachBehaviors(event.target, null, 'unload');
},
};
/**
* @typedef {object} Drupal.dialog~dialogDefinition
*
* @prop {boolean} open
* Is the dialog open or not.
* @prop {*} returnValue
* Return value of the dialog.
* @prop {function} show
* Method to display the dialog on the page.
* @prop {function} showModal
* Method to display the dialog as a modal on the page.
* @prop {function} close
* Method to hide the dialog from the page.
*/
/**
* Polyfill HTML5 dialog element with jQueryUI.
*
* @param {HTMLElement} element
* The element that holds the dialog.
* @param {object} options
* jQuery UI options to be passed to the dialog.
*
* @return {Drupal.dialog~dialogDefinition}
* The dialog instance.
*/
Drupal.dialog = function (element, options) {
let undef;
const $element = $(element);
const domElement = $element.get(0);
const dialog = {
open: false,
returnValue: undef,
};
function openDialog(settings) {
settings = $.extend({}, drupalSettings.dialog, options, settings);
// Trigger a global event to allow scripts to bind events to the dialog.
const event = new DrupalDialogEvent('beforecreate', dialog, settings);
domElement.dispatchEvent(event);
$element.dialog(event.settings);
dialog.open = true;
// Locks the body scroll only when it opens in modal.
if (settings.modal) {
// Locks the body when the dialog opens.
bodyScrollLock.lock(domElement);
}
domElement.dispatchEvent(
new DrupalDialogEvent('aftercreate', dialog, settings),
);
}
function closeDialog(value) {
domElement.dispatchEvent(new DrupalDialogEvent('beforeclose', dialog));
// Unlocks the body when the dialog closes.
bodyScrollLock.clearBodyLocks();
$element.dialog('close');
dialog.returnValue = value;
dialog.open = false;
domElement.dispatchEvent(new DrupalDialogEvent('afterclose', dialog));
}
dialog.show = () => {
openDialog({ modal: false });
};
dialog.showModal = () => {
openDialog({ modal: true });
};
dialog.close = closeDialog;
return dialog;
};
})(jQuery, Drupal, drupalSettings, bodyScrollLock);

View File

@@ -0,0 +1,146 @@
/**
* @file
* Positioning extensions for dialogs.
*/
/**
* Triggers when content inside a dialog changes.
*
* @event dialogContentResize
*/
(function ($, Drupal, drupalSettings, debounce, displace) {
// autoResize option will turn off resizable and draggable.
drupalSettings.dialog = $.extend(
{ autoResize: true, maxHeight: '95%' },
drupalSettings.dialog,
);
/**
* Position the dialog's center at the center of displace.offsets boundaries.
*
* @function Drupal.dialog~resetPosition
*
* @param {object} options
* Options object.
*
* @return {object}
* Altered options object.
*/
function resetPosition(options) {
const offsets = displace.offsets;
const left = offsets.left - offsets.right;
const top = offsets.top - offsets.bottom;
const leftString = `${
(left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2))
}px`;
const topString = `${
(top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2))
}px`;
options.position = {
my: `center${left !== 0 ? leftString : ''} center${
top !== 0 ? topString : ''
}`,
of: window,
};
return options;
}
/**
* Resets the current options for positioning.
*
* This is used as a window resize and scroll callback to reposition the
* jQuery UI dialog. Although not a built-in jQuery UI option, this can
* be disabled by setting autoResize: false in the options array when creating
* a new {@link Drupal.dialog}.
*
* @function Drupal.dialog~resetSize
*
* @param {jQuery.Event} event
* The event triggered.
*
* @fires event:dialogContentResize
*/
function resetSize(event) {
const positionOptions = [
'width',
'height',
'minWidth',
'minHeight',
'maxHeight',
'maxWidth',
'position',
];
let adjustedOptions = {};
let windowHeight = $(window).height();
let option;
let optionValue;
let adjustedValue;
for (let n = 0; n < positionOptions.length; n++) {
option = positionOptions[n];
optionValue = event.data.settings[option];
if (optionValue) {
// jQuery UI does not support percentages on heights, convert to pixels.
if (
typeof optionValue === 'string' &&
optionValue.endsWith('%') &&
/height/i.test(option)
) {
// Take offsets in account.
windowHeight -= displace.offsets.top + displace.offsets.bottom;
adjustedValue = parseInt(
0.01 * parseInt(optionValue, 10) * windowHeight,
10,
);
// Don't force the dialog to be bigger vertically than needed.
if (
option === 'height' &&
event.data.$element.parent().outerHeight() < adjustedValue
) {
adjustedValue = 'auto';
}
adjustedOptions[option] = adjustedValue;
}
}
}
// Offset the dialog center to be at the center of Drupal.displace.offsets.
if (!event.data.settings.modal) {
adjustedOptions = resetPosition(adjustedOptions);
}
event.data.$element.dialog('option', adjustedOptions);
event.data.$element
?.get(0)
?.dispatchEvent(
new CustomEvent('dialogContentResize', { bubbles: true }),
);
}
window.addEventListener('dialog:aftercreate', (e) => {
const autoResize = debounce(resetSize, 20);
const $element = $(e.target);
const { settings } = e;
const eventData = { settings, $element };
if (settings.autoResize === true || settings.autoResize === 'true') {
const uiDialog = $element
.dialog('option', { resizable: false, draggable: false })
.dialog('widget');
uiDialog[0].style.position = 'fixed';
$(window)
.on('resize.dialogResize scroll.dialogResize', eventData, autoResize)
.trigger('resize.dialogResize');
$(document).on(
'drupalViewportOffsetChange.dialogResize',
eventData,
autoResize,
);
}
});
window.addEventListener('dialog:beforeclose', () => {
$(window).off('.dialogResize');
$(document).off('.dialogResize');
});
})(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace);

View File

@@ -0,0 +1,101 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Set base styles for the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-background-color-light: #666;
--off-canvas-background-color-medium: #444;
--off-canvas-background-color-dark: #333;
--off-canvas-background-color: var(--off-canvas-background-color-medium);
--off-canvas-padding: 1.25rem;
--off-canvas-text-color: #e5e5e5;
--off-canvas-link-color: #85bef4;
--off-canvas-border-color: #666;
--off-canvas-font-family: "Lucida Grande", "Lucida Sans Unicode", "liberation sans", sans-serif;
--off-canvas-vertical-spacing-unit: 0.5rem;
--off-canvas-focus-outline-width: 2px;
--off-canvas-focus-outline-color: #fff;
padding: 0 var(--off-canvas-padding) var(--off-canvas-padding);
color: var(--off-canvas-text-color);
background-color: var(--off-canvas-background-color);
font-family: var(--off-canvas-font-family);
}
#drupal-off-canvas-wrapper *:focus {
outline: solid var(--off-canvas-focus-outline-width) var(--off-canvas-focus-outline-color);
outline-offset: 2px;
}
#drupal-off-canvas-wrapper a,
#drupal-off-canvas-wrapper .link {
-webkit-text-decoration: none;
text-decoration: none;
color: var(--off-canvas-link-color);
}
#drupal-off-canvas-wrapper hr {
height: 1px;
background: var(--off-canvas-border-color);
}
#drupal-off-canvas-wrapper h1,
#drupal-off-canvas-wrapper .heading-a {
font-size: 1.4375rem;
line-height: 1.2;
}
#drupal-off-canvas-wrapper h2,
#drupal-off-canvas-wrapper .heading-b {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 1.1875rem;
}
#drupal-off-canvas-wrapper h3,
#drupal-off-canvas-wrapper .heading-c {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 1.0625rem;
}
#drupal-off-canvas-wrapper h4,
#drupal-off-canvas-wrapper .heading-d {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 1rem;
}
#drupal-off-canvas-wrapper h5,
#drupal-off-canvas-wrapper .heading-e,
#drupal-off-canvas-wrapper h6,
#drupal-off-canvas-wrapper .heading-f {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 0.9375rem;
}
#drupal-off-canvas-wrapper p {
margin: var(--off-canvas-vertical-spacing-unit) 0;
}
#drupal-off-canvas-wrapper img {
max-width: 100%;
height: auto;
}
#drupal-off-canvas-wrapper .links {
margin: 0;
padding: 0;
list-style: none;
}
#drupal-off-canvas-wrapper .links li {
margin: calc(var(--off-canvas-vertical-spacing-unit) / 2) 0;
}

View File

@@ -0,0 +1,93 @@
/**
* @file
* Set base styles for the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-background-color-light: #666;
--off-canvas-background-color-medium: #444;
--off-canvas-background-color-dark: #333;
--off-canvas-background-color: var(--off-canvas-background-color-medium);
--off-canvas-padding: 20px;
--off-canvas-text-color: #e5e5e5;
--off-canvas-link-color: #85bef4;
--off-canvas-border-color: #666;
--off-canvas-font-family: "Lucida Grande", "Lucida Sans Unicode", "liberation sans", sans-serif;
--off-canvas-vertical-spacing-unit: 8px;
--off-canvas-focus-outline-width: 2px;
--off-canvas-focus-outline-color: #fff;
padding: 0 var(--off-canvas-padding) var(--off-canvas-padding);
color: var(--off-canvas-text-color);
background-color: var(--off-canvas-background-color);
font-family: var(--off-canvas-font-family);
& *:focus {
outline: solid var(--off-canvas-focus-outline-width) var(--off-canvas-focus-outline-color);
outline-offset: 2px;
}
& a,
& .link {
text-decoration: none;
color: var(--off-canvas-link-color);
}
& hr {
height: 1px;
background: var(--off-canvas-border-color);
}
& h1,
& .heading-a {
font-size: 23px;
line-height: 1.2;
}
& h2,
& .heading-b {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 19px;
}
& h3,
& .heading-c {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 17px;
}
& h4,
& .heading-d {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 16px;
}
& h5,
& .heading-e,
& h6,
& .heading-f {
margin: var(--off-canvas-vertical-spacing-unit) 0;
font-size: 15px;
}
& p {
margin: var(--off-canvas-vertical-spacing-unit) 0;
}
& img {
max-width: 100%;
height: auto;
}
& .links {
margin: 0;
padding: 0;
list-style: none;
& li {
margin: calc(var(--off-canvas-vertical-spacing-unit) / 2) 0;
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Visual styling for buttons in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-button-background-color: #777;
--off-canvas-button-background-color-hover: #999;
--off-canvas-button-text-color: #f5f5f5;
--off-canvas-button-text-color-hover: #fff;
--off-canvas-button-font-size: 0.875rem;
--off-canvas-button-padding: calc(var(--off-canvas-vertical-spacing-unit) / 2) 1.25rem;
--off-canvas-button-border-color: transparent;
--off-canvas-button-border-radius: 20em;
--off-canvas-button-font-weight: 600;
--off-canvas-primary-button-background-color: #277abd;
--off-canvas-primary-button-background-color-hover: #236aaf;
--off-canvas-primary-button-text-color: #fff;
--off-canvas-primary-button-text-color-hover: #fff;
}
#drupal-off-canvas-wrapper .button {
display: inline-block;
width: 100%;
height: auto;
margin: 0 0 0.625rem;
padding: var(--off-canvas-button-padding);
cursor: pointer;
transition: background 0.5s ease;
text-align: center;
color: var(--off-canvas-button-text-color);
border: solid 1px var(--off-canvas-button-border-color);
border-radius: var(--off-canvas-button-border-radius);
background: var(--off-canvas-button-background-color);
font-family: inherit;
font-size: var(--off-canvas-button-font-size);
font-weight: var(--off-canvas-button-font-weight);
line-height: normal;
appearance: none;
}
#drupal-off-canvas-wrapper .button:hover,
#drupal-off-canvas-wrapper .button:active {
z-index: 10;
-webkit-text-decoration: none;
text-decoration: none;
color: var(--off-canvas-button-text-color-hover);
background-color: var(--off-canvas-button-background-color-hover);
}
#drupal-off-canvas-wrapper .button:disabled,
#drupal-off-canvas-wrapper .button:disabled:active,
#drupal-off-canvas-wrapper .button.is-disabled,
#drupal-off-canvas-wrapper .button.is-disabled:active {
cursor: default;
color: #5c5c5c;
background: #555;
font-weight: normal;
}
#drupal-off-canvas-wrapper .button--primary {
margin-top: 0.9375rem;
color: var(--off-canvas-primary-button-text-color);
background: var(--off-canvas-primary-button-background-color);
}
#drupal-off-canvas-wrapper .button--primary:hover,
#drupal-off-canvas-wrapper .button--primary:active {
color: var(--off-canvas-primary-button-text-color-hover);
background: var(--off-canvas-primary-button-background-color-hover);
}
#drupal-off-canvas-wrapper button.link {
display: inline;
transition: color 0.5s ease;
color: var(--off-canvas-link-color);
border: 0;
background: transparent;
font-size: var(--off-canvas-button-font-size);
}
#drupal-off-canvas-wrapper button.link:hover,
#drupal-off-canvas-wrapper button.link:focus {
-webkit-text-decoration: none;
text-decoration: none;
color: var(--off-canvas-link-color);
}
#drupal-off-canvas-wrapper .button--danger {
-webkit-text-decoration: none;
text-decoration: none;
color: #c72100;
border-radius: 0;
font-weight: 400;
}
#drupal-off-canvas-wrapper .button--danger:hover,
#drupal-off-canvas-wrapper .button--danger:focus,
#drupal-off-canvas-wrapper .button--danger:active {
-webkit-text-decoration: none;
text-decoration: none;
color: #ff2a00;
text-shadow: none;
}
#drupal-off-canvas-wrapper .button--danger:disabled,
#drupal-off-canvas-wrapper .button--danger.is-disabled {
cursor: default;
color: #737373;
}
.no-touchevents #drupal-off-canvas-wrapper .button--small {
padding: 2px 1em;
font-size: 0.8125rem;
}

View File

@@ -0,0 +1,112 @@
/**
* @file
* Visual styling for buttons in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-button-background-color: #777;
--off-canvas-button-background-color-hover: #999;
--off-canvas-button-text-color: #f5f5f5;
--off-canvas-button-text-color-hover: #fff;
--off-canvas-button-font-size: 14px;
--off-canvas-button-padding: calc(var(--off-canvas-vertical-spacing-unit) / 2) 20px;
--off-canvas-button-border-color: transparent;
--off-canvas-button-border-radius: 20em;
--off-canvas-button-font-weight: 600;
--off-canvas-primary-button-background-color: #277abd;
--off-canvas-primary-button-background-color-hover: #236aaf;
--off-canvas-primary-button-text-color: #fff;
--off-canvas-primary-button-text-color-hover: #fff;
& .button {
display: inline-block;
width: 100%;
height: auto;
margin: 0 0 10px;
padding: var(--off-canvas-button-padding);
cursor: pointer;
transition: background 0.5s ease;
text-align: center;
color: var(--off-canvas-button-text-color);
border: solid 1px var(--off-canvas-button-border-color);
border-radius: var(--off-canvas-button-border-radius);
background: var(--off-canvas-button-background-color);
font-family: inherit;
font-size: var(--off-canvas-button-font-size);
font-weight: var(--off-canvas-button-font-weight);
line-height: normal;
appearance: none;
&:hover,
&:active {
z-index: 10;
text-decoration: none;
color: var(--off-canvas-button-text-color-hover);
background-color: var(--off-canvas-button-background-color-hover);
}
&:disabled,
&:disabled:active,
&.is-disabled,
&.is-disabled:active {
cursor: default;
color: #5c5c5c;
background: #555;
font-weight: normal;
}
}
& .button--primary {
margin-top: 15px;
color: var(--off-canvas-primary-button-text-color);
background: var(--off-canvas-primary-button-background-color);
&:hover,
&:active {
color: var(--off-canvas-primary-button-text-color-hover);
background: var(--off-canvas-primary-button-background-color-hover);
}
}
& button.link {
display: inline;
transition: color 0.5s ease;
color: var(--off-canvas-link-color);
border: 0;
background: transparent;
font-size: var(--off-canvas-button-font-size);
&:hover,
&:focus {
text-decoration: none;
color: var(--off-canvas-link-color);
}
}
& .button--danger {
text-decoration: none;
color: #c72100;
border-radius: 0;
font-weight: 400;
&:hover,
&:focus,
&:active {
text-decoration: none;
color: #ff2a00;
text-shadow: none;
}
&:disabled,
&.is-disabled {
cursor: default;
color: #737373;
}
}
@nest .no-touchevents & .button--small {
padding: 2px 1em;
font-size: 13px;
}
}

View File

@@ -0,0 +1,69 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Visual styling for summary and details in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-details-border-width: 0;
--off-canvas-details-border-color: none;
--off-canvas-details-background-color: #474747;
--off-canvas-details-text-color: #ddd;
--off-canvas-details-summary-border: none;
--off-canvas-details-summary-padding: 0.625rem 1.25rem;
--off-canvas-details-summary-font-size: 0.875rem;
--off-canvas-details-summary-background-color: #333;
--off-canvas-details-summary-background-color-hover: #222;
--off-canvas-details-summary-text-color: #eee;
--off-canvas-details-summary-text-color-hover: #fff;
}
#drupal-off-canvas-wrapper details {
margin: var(--off-canvas-vertical-spacing-unit) calc(-1 * var(--off-canvas-padding)); /* Cancel out the padding of the parent. */
padding: 0 var(--off-canvas-padding);
color: var(--off-canvas-details-text-color);
border: solid var(--off-canvas-details-border-color) var(--off-canvas-details-border-width);
background: var(--off-canvas-details-background-color);
}
:is(#drupal-off-canvas-wrapper details) + details {
margin-top: calc(-1 * var(--off-canvas-details-border-width));
}
#drupal-off-canvas-wrapper summary {
margin: 0 calc(-1 * var(--off-canvas-padding));
padding: var(--off-canvas-details-summary-padding);
color: var(--off-canvas-details-summary-text-color);
border: var(--off-canvas-details-summary-border);
background-color: var(--off-canvas-details-summary-background-color);
font-size: var(--off-canvas-details-summary-font-size);
}
#drupal-off-canvas-wrapper summary:hover {
color: var(--off-canvas-details-summary-text-color-hover);
background-color: var(--off-canvas-details-summary-background-color-hover);
}
#drupal-off-canvas-wrapper summary:focus {
outline-offset: -4px; /* Ensure focus doesn't get cut off. */
}
#drupal-off-canvas-wrapper summary a {
color: var(--off-canvas-details-text-color);
}
#drupal-off-canvas-wrapper summary a:hover {
color: var(--off-canvas-details-summary-text-color-hover);
}
#drupal-off-canvas-wrapper .details-wrapper {
padding: var(--off-canvas-vertical-spacing-unit) 0;
}

View File

@@ -0,0 +1,62 @@
/**
* @file
* Visual styling for summary and details in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-details-border-width: 0;
--off-canvas-details-border-color: none;
--off-canvas-details-background-color: #474747;
--off-canvas-details-text-color: #ddd;
--off-canvas-details-summary-border: none;
--off-canvas-details-summary-padding: 10px 20px;
--off-canvas-details-summary-font-size: 14px;
--off-canvas-details-summary-background-color: #333;
--off-canvas-details-summary-background-color-hover: #222;
--off-canvas-details-summary-text-color: #eee;
--off-canvas-details-summary-text-color-hover: #fff;
& details {
margin: var(--off-canvas-vertical-spacing-unit) calc(-1 * var(--off-canvas-padding)); /* Cancel out the padding of the parent. */
padding: 0 var(--off-canvas-padding);
color: var(--off-canvas-details-text-color);
border: solid var(--off-canvas-details-border-color) var(--off-canvas-details-border-width);
background: var(--off-canvas-details-background-color);
& + details {
margin-top: calc(-1 * var(--off-canvas-details-border-width));
}
}
& summary {
margin: 0 calc(-1 * var(--off-canvas-padding));
padding: var(--off-canvas-details-summary-padding);
color: var(--off-canvas-details-summary-text-color);
border: var(--off-canvas-details-summary-border);
background-color: var(--off-canvas-details-summary-background-color);
font-size: var(--off-canvas-details-summary-font-size);
&:hover {
color: var(--off-canvas-details-summary-text-color-hover);
background-color: var(--off-canvas-details-summary-background-color-hover);
}
&:focus {
outline-offset: -4px; /* Ensure focus doesn't get cut off. */
}
& a {
color: var(--off-canvas-details-text-color);
&:hover {
color: var(--off-canvas-details-summary-text-color-hover);
}
}
}
& .details-wrapper {
padding: var(--off-canvas-vertical-spacing-unit) 0;
}
}

View File

@@ -0,0 +1,157 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Styles for dropbuttons in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-dropbutton-height: 1.5rem;
--off-canvas-dropbutton-primary-background-color: var(--off-canvas-button-background-color);
--off-canvas-dropbutton-secondary-background-color: var(--off-canvas-button-hover-background-color);
--off-canvas-dropbutton-border-color: transparent;
--off-canvas-dropbutton-border-width: 1px;
--off-canvas-dropbutton-border-radius: 2px;
--off-canvas-dropbutton-focus-outline-color: var(--off-canvas-focus-outline-color);
--off-canvas-dropbutton-font-size: 0.75rem;
--off-canvas-dropbutton-text-color: var(--off-canvas-button-text-color); /* Minimum 4.5:1 contrast ratio against --off-canvas-dropbutton-primary-background-color and --off-canvas-dropbutton-secondary-background-color. */
--off-canvas-dropbutton-text-color-hover: var(--off-canvas-button-text-color-hover); /* Minimum 4.5:1 contrast ratio against --off-canvas-dropbutton-primary-background-color and --off-canvas-dropbutton-secondary-background-color. */
}
#drupal-off-canvas-wrapper .dropbutton-wrapper {
margin-block: var(--off-canvas-vertical-spacing-unit);
/*
* Styles for when the dropbutton is expanded.
*/
}
#drupal-off-canvas-wrapper .dropbutton-wrapper.open {
position: relative;
z-index: 100;
}
#drupal-off-canvas-wrapper .dropbutton-wrapper.open .secondary-action {
visibility: visible;
}
#drupal-off-canvas-wrapper .dropbutton-wrapper.open .dropbutton-widget {
border-radius: var(--off-canvas-dropbutton-border-radius) var(--off-canvas-dropbutton-border-radius) 0 0;
}
#drupal-off-canvas-wrapper .dropbutton-wrapper.open .dropbutton-toggle button::before {
transform: translateY(25%) rotate(225deg);
}
/*
* Styles for single link variant of dropbutton.
*/
#drupal-off-canvas-wrapper .dropbutton-wrapper.dropbutton-single .dropbutton-widget {
padding-inline-end: 0;
}
#drupal-off-canvas-wrapper .dropbutton-wrapper.dropbutton-single .dropbutton-action:first-child {
border-right: solid 1px var(--off-canvas-dropbutton-border-color); /* LTR */
border-radius: var(--off-canvas-dropbutton-border-radius);
}
[dir="rtl"] #drupal-off-canvas-wrapper .dropbutton-wrapper.dropbutton-single .dropbutton-action:first-child {
border: solid 1px var(--off-canvas-dropbutton-border-color);
}
#drupal-off-canvas-wrapper .dropbutton-wrapper.dropbutton-single .dropbutton-action a {
justify-content: center;
}
#drupal-off-canvas-wrapper .dropbutton-widget {
position: relative;
width: max-content;
max-width: 100%;
height: var(--off-canvas-dropbutton-height);
padding-inline-end: var(--off-canvas-dropbutton-height);
border-radius: var(--off-canvas-dropbutton-border-radius);
}
#drupal-off-canvas-wrapper .dropbutton {
height: var(--off-canvas-dropbutton-height);
margin-block: 0;
margin-inline-start: 0;
padding-inline-start: 0;
list-style: none;
font-size: var(--off-canvas-dropbutton-font-size);
}
/* This is the button that expands/collapses the secondary options. */
#drupal-off-canvas-wrapper .dropbutton-toggle {
padding: 0;
border: 0;
}
#drupal-off-canvas-wrapper .dropbutton-toggle button {
position: absolute;
top: 0;
display: flex;
align-items: center;
justify-content: center;
width: var(--off-canvas-dropbutton-height);
height: var(--off-canvas-dropbutton-height);
padding: 0;
cursor: pointer;
border-color: var(--off-canvas-dropbutton-border-color);
border-radius: 0 var(--border-radius) var(--border-radius) 0; /* LTR */
background: var(--off-canvas-dropbutton-primary-background-color);
inset-inline-end: 0;
}
#drupal-off-canvas-wrapper .dropbutton-toggle button:focus {
outline: solid 2px var(--off-canvas-dropbutton-focus-outline-color);
outline-offset: -2px;
}
#drupal-off-canvas-wrapper .dropbutton-toggle button::before {
display: block;
width: 0.375rem;
height: 0.375rem;
content: "";
transform: translateY(-25%) rotate(45deg);
border-right: solid 2px var(--off-canvas-dropbutton-text-color);
border-bottom: solid 2px var(--off-canvas-dropbutton-text-color);
}
[dir="rtl"] #drupal-off-canvas-wrapper .dropbutton-toggle button {
border-radius: var(--off-canvas-dropbutton-border-radius) 0 0 var(--off-canvas-dropbutton-border-radius);
}
/* This is the first <li> element in the list of actions. */
#drupal-off-canvas-wrapper .dropbutton-action:first-child {
margin-inline-end: 2px;
border: solid var(--off-canvas-dropbutton-border-width) var(--off-canvas-dropbutton-border-color);
border-radius: var(--off-canvas-dropbutton-border-radius) 0 0 var(--off-canvas-dropbutton-border-radius); /* LTR */
background: var(--off-canvas-dropbutton-primary-background-color);
}
[dir="rtl"] #drupal-off-canvas-wrapper .dropbutton-action:first-child {
border: solid var(--off-canvas-dropbutton-border-width) var(--off-canvas-dropbutton-border-color);
border-radius: 0 var(--off-canvas-dropbutton-border-radius) var(--off-canvas-dropbutton-border-radius) 0;
}
#drupal-off-canvas-wrapper .dropbutton-action a {
display: flex;
align-items: center;
min-height: var(--off-canvas-dropbutton-height);
margin-bottom: -2px;
padding: 0 0.5625rem;
-webkit-text-decoration: none;
text-decoration: none;
color: var(--off-canvas-dropbutton-text-color);
font-weight: 600;
}
#drupal-off-canvas-wrapper .dropbutton-action a:hover {
color: var(--off-canvas-dropbutton-text-color);
}
#drupal-off-canvas-wrapper .dropbutton-action a:focus {
outline: solid 2px var(--off-canvas-dropbutton-focus-outline-color);
outline-offset: -1px; /* Overlap parent container by 1px. */
}
/* These are the <li> elements other than the first. */
#drupal-off-canvas-wrapper .secondary-action {
visibility: hidden;
width: calc(100% + var(--off-canvas-dropbutton-height));
margin-top: var(--off-canvas-dropbutton-border-width);
border-right: var(--off-canvas-dropbutton-border-width) solid var(--off-canvas-dropbutton-border-color);
border-left: var(--off-canvas-dropbutton-border-width) solid var(--off-canvas-dropbutton-border-color);
background-color: var(--off-canvas-dropbutton-primary-background-color);
}
#drupal-off-canvas-wrapper .secondary-action:last-child {
border-bottom: var(--off-canvas-dropbutton-border-width) solid var(--off-canvas-dropbutton-border-color);
}
#drupal-off-canvas-wrapper .secondary-action a:hover {
color: var(--off-canvas-dropbutton-text-color-hover);
background-color: var(--off-canvas-dropbutton-secondary-background-color);
}

View File

@@ -0,0 +1,179 @@
/**
* @file
* Styles for dropbuttons in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-dropbutton-height: 24px;
--off-canvas-dropbutton-primary-background-color: var(--off-canvas-button-background-color);
--off-canvas-dropbutton-secondary-background-color: var(--off-canvas-button-hover-background-color);
--off-canvas-dropbutton-border-color: transparent;
--off-canvas-dropbutton-border-width: 1px;
--off-canvas-dropbutton-border-radius: 2px;
--off-canvas-dropbutton-focus-outline-color: var(--off-canvas-focus-outline-color);
--off-canvas-dropbutton-font-size: 12px;
--off-canvas-dropbutton-text-color: var(--off-canvas-button-text-color); /* Minimum 4.5:1 contrast ratio against --off-canvas-dropbutton-primary-background-color and --off-canvas-dropbutton-secondary-background-color. */
--off-canvas-dropbutton-text-color-hover: var(--off-canvas-button-text-color-hover); /* Minimum 4.5:1 contrast ratio against --off-canvas-dropbutton-primary-background-color and --off-canvas-dropbutton-secondary-background-color. */
& .dropbutton-wrapper {
margin-block: var(--off-canvas-vertical-spacing-unit);
/*
* Styles for when the dropbutton is expanded.
*/
&.open {
position: relative;
z-index: 100;
& .secondary-action {
visibility: visible;
}
& .dropbutton-widget {
border-radius: var(--off-canvas-dropbutton-border-radius) var(--off-canvas-dropbutton-border-radius) 0 0;
}
& .dropbutton-toggle button::before {
transform: translateY(25%) rotate(225deg);
}
}
/*
* Styles for single link variant of dropbutton.
*/
&.dropbutton-single {
& .dropbutton-widget {
padding-inline-end: 0;
}
& .dropbutton-action {
&:first-child {
border-right: solid 1px var(--off-canvas-dropbutton-border-color); /* LTR */
border-radius: var(--off-canvas-dropbutton-border-radius);
&:dir(rtl) {
border: solid 1px var(--off-canvas-dropbutton-border-color);
}
}
& a {
justify-content: center;
}
}
}
}
& .dropbutton-widget {
position: relative;
width: max-content;
max-width: 100%;
height: var(--off-canvas-dropbutton-height);
padding-inline-end: var(--off-canvas-dropbutton-height);
border-radius: var(--off-canvas-dropbutton-border-radius);
}
& .dropbutton {
height: var(--off-canvas-dropbutton-height);
margin-block: 0;
margin-inline-start: 0;
padding-inline-start: 0;
list-style: none;
font-size: var(--off-canvas-dropbutton-font-size);
}
/* This is the button that expands/collapses the secondary options. */
& .dropbutton-toggle {
padding: 0;
border: 0;
& button {
position: absolute;
top: 0;
display: flex;
align-items: center;
justify-content: center;
width: var(--off-canvas-dropbutton-height);
height: var(--off-canvas-dropbutton-height);
padding: 0;
cursor: pointer;
border-color: var(--off-canvas-dropbutton-border-color);
border-radius: 0 var(--border-radius) var(--border-radius) 0; /* LTR */
background: var(--off-canvas-dropbutton-primary-background-color);
inset-inline-end: 0;
&:focus {
outline: solid 2px var(--off-canvas-dropbutton-focus-outline-color);
outline-offset: -2px;
}
&::before {
display: block;
width: 6px;
height: 6px;
content: "";
transform: translateY(-25%) rotate(45deg);
border-right: solid 2px var(--off-canvas-dropbutton-text-color);
border-bottom: solid 2px var(--off-canvas-dropbutton-text-color);
}
&:dir(rtl) {
border-radius: var(--off-canvas-dropbutton-border-radius) 0 0 var(--off-canvas-dropbutton-border-radius);
}
}
}
/* This is the first <li> element in the list of actions. */
& .dropbutton-action {
&:first-child {
margin-inline-end: 2px;
border: solid var(--off-canvas-dropbutton-border-width) var(--off-canvas-dropbutton-border-color);
border-radius: var(--off-canvas-dropbutton-border-radius) 0 0 var(--off-canvas-dropbutton-border-radius); /* LTR */
background: var(--off-canvas-dropbutton-primary-background-color);
&:dir(rtl) {
border: solid var(--off-canvas-dropbutton-border-width) var(--off-canvas-dropbutton-border-color);
border-radius: 0 var(--off-canvas-dropbutton-border-radius) var(--off-canvas-dropbutton-border-radius) 0;
}
}
& a {
display: flex;
align-items: center;
min-height: var(--off-canvas-dropbutton-height);
margin-bottom: -2px;
padding: 0 9px;
text-decoration: none;
color: var(--off-canvas-dropbutton-text-color);
font-weight: 600;
&:hover {
color: var(--off-canvas-dropbutton-text-color);
}
&:focus {
outline: solid 2px var(--off-canvas-dropbutton-focus-outline-color);
outline-offset: -1px; /* Overlap parent container by 1px. */
}
}
}
/* These are the <li> elements other than the first. */
& .secondary-action {
visibility: hidden;
width: calc(100% + var(--off-canvas-dropbutton-height));
margin-top: var(--off-canvas-dropbutton-border-width);
border-right: var(--off-canvas-dropbutton-border-width) solid var(--off-canvas-dropbutton-border-color);
border-left: var(--off-canvas-dropbutton-border-width) solid var(--off-canvas-dropbutton-border-color);
background-color: var(--off-canvas-dropbutton-primary-background-color);
&:last-child {
border-bottom: var(--off-canvas-dropbutton-border-width) solid var(--off-canvas-dropbutton-border-color);
}
& a:hover {
color: var(--off-canvas-dropbutton-text-color-hover);
background-color: var(--off-canvas-dropbutton-secondary-background-color);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Recreate Drupal admin styling that was removed with reset.
* @see system.admin.css
*
* @internal
*/
#drupal-off-canvas-wrapper .panel {
padding: 0.3125rem 0.3125rem 0.9375rem;
}
#drupal-off-canvas-wrapper .panel__description {
margin: 0 0 0.1875rem;
padding: 2px 0 0.1875rem 0;
}
#drupal-off-canvas-wrapper .compact-link {
margin: 0 0 0.625rem 0;
}
#drupal-off-canvas-wrapper small .admin-link::before {
content: " [";
}
#drupal-off-canvas-wrapper small .admin-link::after {
content: "]";
}

View File

@@ -0,0 +1,30 @@
/**
* @file
* Recreate Drupal admin styling that was removed with reset.
* @see system.admin.css
*
* @internal
*/
#drupal-off-canvas-wrapper {
& .panel {
padding: 5px 5px 15px;
}
& .panel__description {
margin: 0 0 3px;
padding: 2px 0 3px 0;
}
& .compact-link {
margin: 0 0 10px 0;
}
& small .admin-link::before {
content: " [";
}
& small .admin-link::after {
content: "]";
}
}

View File

@@ -0,0 +1,192 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Visual styling for forms in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--drupal-off-canvas-input-padding: var(--off-canvas-vertical-spacing-unit);
--drupal-off-canvas-input-background-color: #fff;
--drupal-off-canvas-input-border: solid 1px transparent;
--drupal-off-canvas-input-border-radius: 2px;
--drupal-off-canvas-input-font-size: 0.875rem;
--drupal-off-canvas-input-text-color: #333;
--drupal-off-canvas-fieldset-background-color: transparent;
--drupal-off-canvas-fieldset-border-width: 1px;
--drupal-off-canvas-fieldset-border-color: var(--off-canvas-border-color);
}
#drupal-off-canvas-wrapper form {
padding-block: var(--off-canvas-padding);
}
#drupal-off-canvas-wrapper form > *:first-child {
margin-top: 0;
padding-top: 0;
}
#drupal-off-canvas-wrapper .ck-content {
color: var(--drupal-off-canvas-input-text-color);
}
#drupal-off-canvas-wrapper .form-item:where(:not(fieldset)) {
padding: var(--off-canvas-vertical-spacing-unit) 0;
}
#drupal-off-canvas-wrapper .form-items-inline > * {
display: inline-block;
}
#drupal-off-canvas-wrapper label {
display: block;
}
#drupal-off-canvas-wrapper .form-type-boolean {
padding: calc(0.5 * var(--off-canvas-vertical-spacing-unit)) 0;
}
#drupal-off-canvas-wrapper .description,
#drupal-off-canvas-wrapper .form-item__description {
margin: calc(0.5 * var(--off-canvas-vertical-spacing-unit)) 0;
font-size: 0.75rem;
}
#drupal-off-canvas-wrapper .form-required::after {
content: "*";
}
#drupal-off-canvas-wrapper .fieldset,
#drupal-off-canvas-wrapper fieldset {
margin: calc(2 * var(--off-canvas-vertical-spacing-unit)) 0;
padding: var(--off-canvas-vertical-spacing-unit);
border: solid var(--drupal-off-canvas-fieldset-border-width) var(--drupal-off-canvas-fieldset-border-color);
background-color: var(--drupal-off-canvas-fieldset-background-color);
}
#drupal-off-canvas-wrapper legend,
#drupal-off-canvas-wrapper .fieldset__legend {
display: contents;
font-weight: bold;
}
#drupal-off-canvas-wrapper :is(.fieldset, fieldset, .draggable-table) input:where(:not([type="submit"], [type="checkbox"], [type="radio"])) {
width: 100%; /* Prevent text fields from breaking out of tables and fieldsets at narrow widths. */
}
#drupal-off-canvas-wrapper input,
#drupal-off-canvas-wrapper textarea {
font-family: inherit;
}
#drupal-off-canvas-wrapper input:where(:not([type="submit"], [type="checkbox"], [type="radio"], [type="file"])),
#drupal-off-canvas-wrapper select,
#drupal-off-canvas-wrapper textarea {
max-width: 100%;
padding: var(--drupal-off-canvas-input-padding);
color: var(--drupal-off-canvas-input-text-color);
border: var(--drupal-off-canvas-input-border);
border-radius: var(--drupal-off-canvas-input-border-radius);
background-color: var(--drupal-off-canvas-input-background-color);
font-size: var(--drupal-off-canvas-input-font-size);
}
:is(#drupal-off-canvas-wrapper input[type="checkbox"]) + label,
:is(#drupal-off-canvas-wrapper input[type="radio"]) + label {
display: inline;
}
#drupal-off-canvas-wrapper input[type="file"] {
margin-bottom: var(--off-canvas-vertical-spacing-unit);
}
#drupal-off-canvas-wrapper input[type="search"] {
appearance: none; /* Necessary for Safari. */
}
#drupal-off-canvas-wrapper select {
appearance: none;
padding-inline-end: 1.25rem;
border: var(--drupal-off-canvas-input-border);
border-radius: var(--drupal-off-canvas-input-border-radius);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 9'%3e%3cpath fill='none' stroke-width='1.5' d='M1 1l6 6 6-6' stroke='%23545560'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center right 5px; /* LTR */
background-size: 0.75rem;
}
[dir="rtl"] #drupal-off-canvas-wrapper select {
background-position: center left 5px;
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper select {
appearance: revert;
padding-inline-end: 0;
background: revert;
}
}
/*
* Autocomplete.
*/
#drupal-off-canvas-wrapper .form-autocomplete {
padding-inline-end: 2.5rem; /* Room for icon. */
background-image: url("data:image/svg+xml,%3csvg width='40' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M8 1C3.46.827-.188 5.787 1.313 10.068c1.176 4.384 6.993 6.417 10.637 3.7.326-.39.565.276.846.442l3.74 3.739 1.413-1.414-4.35-4.35c2.811-3.468 1.15-9.247-3.062-10.71A7.003 7.003 0 008 1zm0 2c3.242-.123 5.849 3.42 4.777 6.477-.842 3.132-4.994 4.58-7.6 2.65-2.745-1.73-2.9-6.125-.285-8.044A5.006 5.006 0 018 3z' fill='%23868686'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center right 1px; /* LTR */
}
#drupal-off-canvas-wrapper .form-autocomplete.ui-autocomplete-loading {
background-image: url(../../../icons/spinner.gif);
}
[dir="rtl"] #drupal-off-canvas-wrapper .form-autocomplete {
background-position: center left 1px;
}
/* This is the background <ul> for the autocomplete dropdown. */
#drupal-off-canvas-wrapper .ui-autocomplete {
margin: 0;
padding: 0;
list-style: none;
border: var(--drupal-off-canvas-input-border);
background-color: var(--drupal-off-canvas-input-background-color);
box-shadow: 0 1px 1px 0 var(--off-canvas-background-color); /* Ensure edge is visible if appearing over another form element. */
}
#drupal-off-canvas-wrapper .ui-autocomplete a {
display: block;
padding: var(--drupal-off-canvas-input-padding);
cursor: pointer;
color: var(--drupal-off-canvas-input-text-color);
font-size: var(--drupal-off-canvas-input-font-size);
}
#drupal-off-canvas-wrapper .ui-autocomplete a:hover {
background-color: #eee;
}
#drupal-off-canvas-wrapper .ui-autocomplete a:focus,
#drupal-off-canvas-wrapper .ui-autocomplete a.ui-state-active {
outline: solid 2px currentColor;
outline-offset: -2px;
}
/*
* Claro injects a "Loading" autocomplete message that affects the positioning
* of the ui-autocomplete dropdown. We remove this to normalize the markup.
*/
#drupal-off-canvas-wrapper .claro-autocomplete__message {
display: none;
}

View File

@@ -0,0 +1,182 @@
/**
* @file
* Visual styling for forms in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--drupal-off-canvas-input-padding: var(--off-canvas-vertical-spacing-unit);
--drupal-off-canvas-input-background-color: #fff;
--drupal-off-canvas-input-border: solid 1px transparent;
--drupal-off-canvas-input-border-radius: 2px;
--drupal-off-canvas-input-font-size: 14px;
--drupal-off-canvas-input-text-color: #333;
--drupal-off-canvas-fieldset-background-color: transparent;
--drupal-off-canvas-fieldset-border-width: 1px;
--drupal-off-canvas-fieldset-border-color: var(--off-canvas-border-color);
& form {
padding-block: var(--off-canvas-padding);
& > *:first-child {
margin-top: 0;
padding-top: 0;
}
}
& .ck-content {
color: var(--drupal-off-canvas-input-text-color);
}
& .form-item:where(:not(fieldset)) {
padding: var(--off-canvas-vertical-spacing-unit) 0;
}
& .form-items-inline > * {
display: inline-block;
}
& label {
display: block;
}
& .form-type-boolean {
padding: calc(0.5 * var(--off-canvas-vertical-spacing-unit)) 0;
}
& .description,
& .form-item__description {
margin: calc(0.5 * var(--off-canvas-vertical-spacing-unit)) 0;
font-size: 12px;
}
& .form-required::after {
content: "*";
}
& .fieldset,
& fieldset {
margin: calc(2 * var(--off-canvas-vertical-spacing-unit)) 0;
padding: var(--off-canvas-vertical-spacing-unit);
border: solid var(--drupal-off-canvas-fieldset-border-width) var(--drupal-off-canvas-fieldset-border-color);
background-color: var(--drupal-off-canvas-fieldset-background-color);
}
& legend, /* Bartik doesn't apply BEM classes, so we use the element. */
& .fieldset__legend {
display: contents;
font-weight: bold;
}
& :is(.fieldset, fieldset, .draggable-table) input:where(:not([type="submit"], [type="checkbox"], [type="radio"])) {
width: 100%; /* Prevent text fields from breaking out of tables and fieldsets at narrow widths. */
}
& input,
& textarea {
font-family: inherit;
}
& input:where(:not([type="submit"], [type="checkbox"], [type="radio"], [type="file"])),
& select,
& textarea {
max-width: 100%;
padding: var(--drupal-off-canvas-input-padding);
color: var(--drupal-off-canvas-input-text-color);
border: var(--drupal-off-canvas-input-border);
border-radius: var(--drupal-off-canvas-input-border-radius);
background-color: var(--drupal-off-canvas-input-background-color);
font-size: var(--drupal-off-canvas-input-font-size);
}
& input[type="checkbox"],
& input[type="radio"] {
& + label {
display: inline;
}
}
& input[type="file"] {
margin-bottom: var(--off-canvas-vertical-spacing-unit);
}
& input[type="search"] {
appearance: none; /* Necessary for Safari. */
}
& select {
appearance: none;
padding-inline-end: 20px;
border: var(--drupal-off-canvas-input-border);
border-radius: var(--drupal-off-canvas-input-border-radius);
background-image: url(../../../icons/545560/chevron-down.svg);
background-repeat: no-repeat;
background-position: center right 5px; /* LTR */
background-size: 12px;
&:dir(rtl) {
background-position: center left 5px;
}
@media (forced-colors: active) {
appearance: revert;
padding-inline-end: 0;
background: revert;
}
}
/*
* Autocomplete.
*/
& .form-autocomplete {
padding-inline-end: 40px; /* Room for icon. */
background-image: url(../../../icons/868686/magnifier.svg);
background-repeat: no-repeat;
background-position: center right 1px; /* LTR */
&.ui-autocomplete-loading {
background-image: url(../../../icons/spinner.gif);
}
&:dir(rtl) {
background-position: center left 1px;
}
}
/* This is the background <ul> for the autocomplete dropdown. */
& .ui-autocomplete {
margin: 0;
padding: 0;
list-style: none;
border: var(--drupal-off-canvas-input-border);
background-color: var(--drupal-off-canvas-input-background-color);
box-shadow: 0 1px 1px 0 var(--off-canvas-background-color); /* Ensure edge is visible if appearing over another form element. */
& a {
display: block;
padding: var(--drupal-off-canvas-input-padding);
cursor: pointer;
color: var(--drupal-off-canvas-input-text-color);
font-size: var(--drupal-off-canvas-input-font-size);
&:hover {
background-color: #eee;
}
&:focus,
&.ui-state-active {
outline: solid 2px currentColor;
outline-offset: -2px;
}
}
}
/*
* Claro injects a "Loading" autocomplete message that affects the positioning
* of the ui-autocomplete dropdown. We remove this to normalize the markup.
*/
& .claro-autocomplete__message {
display: none;
}
}

View File

@@ -0,0 +1,130 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Styling for messages in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-messages-icon-size: 1.25rem;
--off-canvas-messages-background-color: #f3faef;
--off-canvas-messages-text-color-status: #325e1c;
--off-canvas-messages-text-color-warning: #734c00;
--off-canvas-messages-text-color-error: #a51b00;
--off-canvas-messages-icon-status: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2373b355'%3e%3cpath d='M6.464 13.676c-.194.194-.513.194-.707 0l-4.96-4.955c-.194-.193-.194-.513 0-.707l1.405-1.407c.194-.195.512-.195.707 0l2.849 2.848c.194.193.513.193.707 0l6.629-6.626c.195-.194.514-.194.707 0l1.404 1.404c.193.194.193.513 0 .707l-8.741 8.736z'/%3e%3c/svg%3e");
--off-canvas-messages-icon-warning: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23e29700'%3e%3cpath d='M14.66 12.316l-5.316-10.633c-.738-1.476-1.946-1.476-2.685 0l-5.317 10.633c-.738 1.477.008 2.684 1.658 2.684h10.002c1.65 0 2.396-1.207 1.658-2.684zm-7.66-8.316h2.002v5h-2.002v-5zm2.252 8.615c0 .344-.281.625-.625.625h-1.25c-.345 0-.626-.281-.626-.625v-1.239c0-.344.281-.625.626-.625h1.25c.344 0 .625.281.625.625v1.239z'/%3e%3c/svg%3e");
--off-canvas-messages-icon-error: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23e32700'%3e%3cpath d='M8.002 1c-3.868 0-7.002 3.134-7.002 7s3.134 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm4.025 9.284c.062.063.1.149.1.239 0 .091-.037.177-.1.24l-1.262 1.262c-.064.062-.15.1-.24.1s-.176-.036-.24-.1l-2.283-2.283-2.286 2.283c-.064.062-.15.1-.24.1s-.176-.036-.24-.1l-1.261-1.262c-.063-.062-.1-.148-.1-.24 0-.088.036-.176.1-.238l2.283-2.285-2.283-2.284c-.063-.064-.1-.15-.1-.24s.036-.176.1-.24l1.262-1.262c.063-.063.149-.1.24-.1.089 0 .176.036.24.1l2.285 2.284 2.283-2.284c.064-.063.15-.1.24-.1s.176.036.24.1l1.262 1.262c.062.063.1.149.1.24 0 .089-.037.176-.1.24l-2.283 2.284 2.283 2.284z'/%3e%3c/svg%3e");
}
#drupal-off-canvas-wrapper .messages {
position: relative; /* Anchor ::before pseudo-element. */
margin-top: calc(2 * var(--off-canvas-vertical-spacing-unit));
padding: calc(2 * var(--off-canvas-vertical-spacing-unit));
padding-inline-start: calc(2 * var(--off-canvas-messages-icon-size)); /* Room for icon. */
border: solid 1px transparent;
background-color: var(--off-canvas-messages-background-color);
/* Icon. */
}
#drupal-off-canvas-wrapper .messages::before {
position: absolute;
top: 50%;
display: block;
width: var(--off-canvas-messages-icon-size);
height: var(--off-canvas-messages-icon-size);
content: "";
transform: translateY(-50%);
background-repeat: no-repeat;
background-size: contain;
inset-inline-start: 0.625rem;
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .messages::before {
background: canvastext;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
}
}
#drupal-off-canvas-wrapper h2 {
margin-top: 0;
}
/*
* Some themes (Olivero) insert SVG icon. We use a background icon, so we
* need to remove this.
*/
#drupal-off-canvas-wrapper .messages__icon,
#drupal-off-canvas-wrapper .messages__close {
display: none;
}
#drupal-off-canvas-wrapper .messages__list {
margin: 0;
padding-inline-start: 1.25rem;
}
#drupal-off-canvas-wrapper .messages abbr {
-webkit-text-decoration: none;
text-decoration: none;
}
#drupal-off-canvas-wrapper .messages--status {
color: var(--off-canvas-messages-text-color-status);
}
#drupal-off-canvas-wrapper .messages--status::before {
background-image: var(--off-canvas-messages-icon-status);
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .messages--status::before {
background: canvastext;
-webkit-mask-image: var(--off-canvas-messages-icon-status);
mask-image: var(--off-canvas-messages-icon-status);
}
}
#drupal-off-canvas-wrapper .messages--warning {
color: var(--off-canvas-messages-text-color-warning);
}
#drupal-off-canvas-wrapper .messages--warning::before {
background-image: var(--off-canvas-messages-icon-warning);
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .messages--warning::before {
background: canvastext;
-webkit-mask-image: var(--off-canvas-messages-icon-warning);
mask-image: var(--off-canvas-messages-icon-warning);
}
}
#drupal-off-canvas-wrapper .messages--error {
color: var(--off-canvas-messages-text-color-error);
}
#drupal-off-canvas-wrapper .messages--error::before {
background-image: var(--off-canvas-messages-icon-error);
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .messages--error::before {
background: canvastext;
-webkit-mask-image: var(--off-canvas-messages-icon-error);
mask-image: var(--off-canvas-messages-icon-error);
}
}

View File

@@ -0,0 +1,107 @@
/**
* @file
* Styling for messages in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-messages-icon-size: 20px;
--off-canvas-messages-background-color: #f3faef;
--off-canvas-messages-text-color-status: #325e1c;
--off-canvas-messages-text-color-warning: #734c00;
--off-canvas-messages-text-color-error: #a51b00;
--off-canvas-messages-icon-status: url(../../../icons/73b355/check.svg);
--off-canvas-messages-icon-warning: url(../../../icons/e29700/warning.svg);
--off-canvas-messages-icon-error: url(../../../icons/e32700/error.svg);
& .messages {
position: relative; /* Anchor ::before pseudo-element. */
margin-top: calc(2 * var(--off-canvas-vertical-spacing-unit));
padding: calc(2 * var(--off-canvas-vertical-spacing-unit));
padding-inline-start: calc(2 * var(--off-canvas-messages-icon-size)); /* Room for icon. */
border: solid 1px transparent;
background-color: var(--off-canvas-messages-background-color);
/* Icon. */
&::before {
position: absolute;
top: 50%;
display: block;
width: var(--off-canvas-messages-icon-size);
height: var(--off-canvas-messages-icon-size);
content: "";
transform: translateY(-50%);
background-repeat: no-repeat;
background-size: contain;
inset-inline-start: 10px;
@media (forced-colors: active) {
background: canvastext;
mask-repeat: no-repeat;
mask-size: contain;
}
}
}
& h2 {
margin-top: 0;
}
/*
* Some themes (Olivero) insert SVG icon. We use a background icon, so we
* need to remove this.
*/
& .messages__icon,
& .messages__close {
display: none;
}
& .messages__list {
margin: 0;
padding-inline-start: 20px;
}
& .messages abbr {
text-decoration: none;
}
& .messages--status {
color: var(--off-canvas-messages-text-color-status);
&::before {
background-image: var(--off-canvas-messages-icon-status);
@media (forced-colors: active) {
background: canvastext;
mask-image: var(--off-canvas-messages-icon-status);
}
}
}
& .messages--warning {
color: var(--off-canvas-messages-text-color-warning);
&::before {
background-image: var(--off-canvas-messages-icon-warning);
@media (forced-colors: active) {
background: canvastext;
mask-image: var(--off-canvas-messages-icon-warning);
}
}
}
& .messages--error {
color: var(--off-canvas-messages-text-color-error);
&::before {
background-image: var(--off-canvas-messages-icon-error);
@media (forced-colors: active) {
background: canvastext;
mask-image: var(--off-canvas-messages-icon-error);
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Reset HTML elements styles for the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper *:where(:not(svg, svg *, .ck-reset *, [data-drupal-ck-style-fence] *, .ui-resizable-handle)) {
all: revert;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
line-height: 1.4;
}
#drupal-off-canvas-wrapper *:where(:not(svg, svg *, .ck-reset *, [data-drupal-ck-style-fence] *, .ui-resizable-handle))::after,
#drupal-off-canvas-wrapper *:where(:not(svg, svg *, .ck-reset *, [data-drupal-ck-style-fence] *, .ui-resizable-handle))::before {
all: revert;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}

View File

@@ -0,0 +1,20 @@
/**
* @file
* Reset HTML elements styles for the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper *:where(:not(svg, svg *, .ck-reset *, [data-drupal-ck-style-fence] *, .ui-resizable-handle)) {
all: revert;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
line-height: 1.4;
&::after,
&::before {
all: revert;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
}

View File

@@ -0,0 +1,44 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Styling for tables in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-table-cell-padding: 2px;
--off-canvas-first-cell-padding-start: calc(var(--off-canvas-padding) / 2);
}
#drupal-off-canvas-wrapper table {
width: calc(100% + 2 * var(--off-canvas-padding));
margin: var(--off-canvas-vertical-spacing-unit) calc(-1 * var(--off-canvas-padding));
}
#drupal-off-canvas-wrapper tr {
border-bottom: 1px solid var(--off-canvas-border-color);
}
#drupal-off-canvas-wrapper td,
#drupal-off-canvas-wrapper th {
padding: var(--off-canvas-table-cell-padding);
text-align: start;
vertical-align: middle;
}
#drupal-off-canvas-wrapper td:first-child,
#drupal-off-canvas-wrapper th:first-child {
padding-inline-start: var(--off-canvas-first-cell-padding-start);
}
#drupal-off-canvas-wrapper td:not(:last-child) td,
#drupal-off-canvas-wrapper th:not(:last-child) td {
border-bottom: solid 1px var(--off-canvas-border-color);
}

View File

@@ -0,0 +1,35 @@
/**
* @file
* Styling for tables in the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-table-cell-padding: 2px;
--off-canvas-first-cell-padding-start: calc(var(--off-canvas-padding) / 2);
& table {
width: calc(100% + 2 * var(--off-canvas-padding));
margin: var(--off-canvas-vertical-spacing-unit) calc(-1 * var(--off-canvas-padding));
}
& tr {
border-bottom: 1px solid var(--off-canvas-border-color);
}
& td,
& th {
padding: var(--off-canvas-table-cell-padding);
text-align: start;
vertical-align: middle;
&:first-child {
padding-inline-start: var(--off-canvas-first-cell-padding-start);
}
&:not(:last-child) td {
border-bottom: solid 1px var(--off-canvas-border-color);
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Table drag styling for off-canvas dialog.
*
* @see tabledrag.js
*
* @internal
*/
/* The draggable <tr> element. */
#drupal-off-canvas-wrapper .draggable:hover,
#drupal-off-canvas-wrapper .draggable:focus-within {
background-color: var(--off-canvas-background-color-light);
}
/* Appears when the row is being dragged. */
#drupal-off-canvas-wrapper .draggable.drag {
cursor: move;
background-color: var(--off-canvas-background-color-dark);
}
#drupal-off-canvas-wrapper td {
transition: background-color 0.3s ease;
/* We have to horizontally align all descendent nodes including text nodes
* that do not have wrapper elements. Since we use flex to do this, we need
* try to keep it vertically centered, so we have to give it a minimum height
* to match the rest of the table cells. */
}
#drupal-off-canvas-wrapper td:first-child {
display: flex;
align-items: center;
min-height: 3.125rem;
gap: var(--off-canvas-table-cell-padding);
}
#drupal-off-canvas-wrapper td abbr {
margin-inline: 0 0.3125rem;
-webkit-text-decoration: none;
text-decoration: none;
}
#drupal-off-canvas-wrapper .tabledrag-handle {
flex-shrink: 0;
}
#drupal-off-canvas-wrapper .tabledrag-handle::after {
display: block;
width: 1.25rem;
height: 1.25rem;
margin: 0;
padding: 0;
content: "";
cursor: move;
background-color: transparent;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cpath fill='%23bebebe' d='M14.904 7.753l-2.373-2.372c-.291-.292-.529-.193-.529.22v1.399h-3v-3h1.398c.414 0 .512-.239.221-.53l-2.371-2.372c-.137-.136-.36-.136-.497 0l-2.372 2.372c-.292.292-.193.53.22.53h1.399v3h-3v-1.369c0-.413-.239-.511-.53-.22l-2.372 2.372c-.136.136-.136.359 0 .494l2.372 2.372c.291.292.53.192.53-.219v-1.43h3v3h-1.4c-.413 0-.511.238-.22.529l2.374 2.373c.137.137.36.137.495 0l2.373-2.373c.29-.291.19-.529-.222-.529h-1.398v-3h3v1.4c0 .412.238.511.529.219l2.373-2.371c.137-.137.137-.359 0-.495z'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center;
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .tabledrag-handle::after {
background: linktext;
-webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cpath fill='%23bebebe' d='M14.904 7.753l-2.373-2.372c-.291-.292-.529-.193-.529.22v1.399h-3v-3h1.398c.414 0 .512-.239.221-.53l-2.371-2.372c-.137-.136-.36-.136-.497 0l-2.372 2.372c-.292.292-.193.53.22.53h1.399v3h-3v-1.369c0-.413-.239-.511-.53-.22l-2.372 2.372c-.136.136-.136.359 0 .494l2.372 2.372c.291.292.53.192.53-.219v-1.43h3v3h-1.4c-.413 0-.511.238-.22.529l2.374 2.373c.137.137.36.137.495 0l2.373-2.373c.29-.291.19-.529-.222-.529h-1.398v-3h3v1.4c0 .412.238.511.529.219l2.373-2.371c.137-.137.137-.359 0-.495z'/%3e%3c/svg%3e");
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cpath fill='%23bebebe' d='M14.904 7.753l-2.373-2.372c-.291-.292-.529-.193-.529.22v1.399h-3v-3h1.398c.414 0 .512-.239.221-.53l-2.371-2.372c-.137-.136-.36-.136-.497 0l-2.372 2.372c-.292.292-.193.53.22.53h1.399v3h-3v-1.369c0-.413-.239-.511-.53-.22l-2.372 2.372c-.136.136-.136.359 0 .494l2.372 2.372c.291.292.53.192.53-.219v-1.43h3v3h-1.4c-.413 0-.511.238-.22.529l2.374 2.373c.137.137.36.137.495 0l2.373-2.373c.29-.291.19-.529-.222-.529h-1.398v-3h3v1.4c0 .412.238.511.529.219l2.373-2.371c.137-.137.137-.359 0-.495z'/%3e%3c/svg%3e");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
}
}
/* Make the "row weight" <select> as small as possible. */
#drupal-off-canvas-wrapper .tabledrag-hide select {
all: revert;
}
#drupal-off-canvas-wrapper .tabledrag-changed-warning {
margin-bottom: var(--off-canvas-vertical-spacing-unit);
font-size: 0.875rem;
}
#drupal-off-canvas-wrapper .tabledrag-toggle-weight-wrapper {
padding-top: 0.625rem;
text-align: end;
}
#drupal-off-canvas-wrapper .indentation {
width: 0.3125rem;
}
.touchevents #drupal-off-canvas-wrapper .draggable td {
padding: 0 0.625rem;
}
.touchevents #drupal-off-canvas-wrapper .draggable .menu-item__link {
display: inline-block;
padding: 0.625rem 0;
}
.touchevents #drupal-off-canvas-wrapper a.tabledrag-handle {
width: 2.5rem;
height: 2.75rem;
}

View File

@@ -0,0 +1,102 @@
/**
* @file
* Table drag styling for off-canvas dialog.
*
* @see tabledrag.js
*
* @internal
*/
#drupal-off-canvas-wrapper {
/* The draggable <tr> element. */
& .draggable {
&:hover,
&:focus-within {
background-color: var(--off-canvas-background-color-light);
}
/* Appears when the row is being dragged. */
&.drag {
cursor: move;
background-color: var(--off-canvas-background-color-dark);
}
}
& td {
transition: background-color 0.3s ease;
/* We have to horizontally align all descendent nodes including text nodes
* that do not have wrapper elements. Since we use flex to do this, we need
* try to keep it vertically centered, so we have to give it a minimum height
* to match the rest of the table cells. */
&:first-child {
display: flex;
align-items: center;
min-height: 50px;
gap: var(--off-canvas-table-cell-padding);
}
& abbr {
margin-inline: 0 5px;
text-decoration: none;
}
}
& .tabledrag-handle {
flex-shrink: 0;
&::after {
display: block;
width: 20px;
height: 20px;
margin: 0;
padding: 0;
content: "";
cursor: move;
background-color: transparent;
background-image: url(../../../icons/bebebe/move.svg);
background-repeat: no-repeat;
background-position: center;
@media (forced-colors: active) {
background: linktext;
mask-image: url(../../../icons/bebebe/move.svg);
mask-repeat: no-repeat;
mask-position: center;
}
}
}
/* Make the "row weight" <select> as small as possible. */
& .tabledrag-hide select {
all: revert;
}
& .tabledrag-changed-warning {
margin-bottom: var(--off-canvas-vertical-spacing-unit);
font-size: 14px;
}
& .tabledrag-toggle-weight-wrapper {
padding-top: 10px;
text-align: end;
}
& .indentation {
width: 5px;
}
@nest .touchevents & .draggable td {
padding: 0 10px;
}
@nest .touchevents & .draggable .menu-item__link {
display: inline-block;
padding: 10px 0;
}
@nest .touchevents & a.tabledrag-handle {
width: 40px;
height: 44px;
}
}

View File

@@ -0,0 +1,51 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Styling of AJAX actions throbber in off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper .ajax-progress,
#drupal-off-canvas-wrapper .ajax-progress-throbber {
display: inline-block;
overflow: hidden;
width: 0.9375rem;
height: 0.9375rem;
margin: 0 0.625rem;
animation: off-canvas-throbber-spin 1s linear infinite;
vertical-align: middle;
border: 2px solid var(--off-canvas-text-color);
border-top-color: transparent;
border-radius: 50%;
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .ajax-progress,
#drupal-off-canvas-wrapper .ajax-progress-throbber {
border-top-color: transparent;
}
}
#drupal-off-canvas-wrapper .layout-selection .ajax-progress,
#drupal-off-canvas-wrapper .inline-block-list .ajax-progress,
#drupal-off-canvas-wrapper .layout-selection .ajax-progress-throbber,
#drupal-off-canvas-wrapper .inline-block-list .ajax-progress-throbber {
position: absolute;
inset-block-start: 0;
inset-block-end: 0;
inset-inline-end: 0;
margin-block: auto;
}
@keyframes off-canvas-throbber-spin {
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,44 @@
/**
* @file
* Styling of AJAX actions throbber in off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
& .ajax-progress, /* This is the CSS class used in Claro. */
& .ajax-progress-throbber {
display: inline-block;
overflow: hidden;
width: 15px;
height: 15px;
margin: 0 10px;
animation: off-canvas-throbber-spin 1s linear infinite;
vertical-align: middle;
border: 2px solid var(--off-canvas-text-color);
border-top-color: transparent;
border-radius: 50%;
@media (forced-colors: active) {
border-top-color: transparent;
}
}
& .layout-selection,
& .inline-block-list {
& .ajax-progress,
& .ajax-progress-throbber {
position: absolute;
inset-block-start: 0;
inset-block-end: 0;
inset-inline-end: 0;
margin-block: auto;
}
}
}
@keyframes off-canvas-throbber-spin {
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,108 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Styling for the titlebar within the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-title-padding: calc(3 * var(--off-canvas-vertical-spacing-unit));
--off-canvas-title-background-color: #2d2d2d;
--off-canvas-title-text-color: #fff;
--off-canvas-title-font-size: 1rem;
}
#drupal-off-canvas-wrapper .ui-dialog-titlebar {
position: relative;
margin: 0 calc(-1 * var(--off-canvas-padding));
padding: var(--off-canvas-title-padding) 3.125rem;
color: var(--off-canvas-title-text-color);
background-color: var(--off-canvas-title-background-color);
font-family: var(--off-canvas-title-font-family);
font-size: var(--off-canvas-title-font-size);
font-weight: bold;
/* The pencil icon. */
}
#drupal-off-canvas-wrapper .ui-dialog-titlebar::before {
position: absolute;
top: 0;
inset-inline-start: 1em;
display: block;
width: 1.25rem;
height: 100%;
content: "";
background-color: currentColor;
-webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cg%3e%3cpath fill='%23ffffff' d='M14.545 3.042l-1.586-1.585c-.389-.389-1.025-.389-1.414 0l-1.293 1.293 3 3 1.293-1.293c.389-.389.389-1.026 0-1.415z'/%3e%3crect fill='%23ffffff' x='5.129' y='3.8' transform='matrix(-.707 -.707 .707 -.707 6.189 20.064)' width='4.243' height='9.899'/%3e%3cpath fill='%23ffffff' d='M.908 14.775c-.087.262.055.397.316.312l2.001-.667-1.65-1.646-.667 2.001z'/%3e%3c/g%3e%3c/svg%3e");
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cg%3e%3cpath fill='%23ffffff' d='M14.545 3.042l-1.586-1.585c-.389-.389-1.025-.389-1.414 0l-1.293 1.293 3 3 1.293-1.293c.389-.389.389-1.026 0-1.415z'/%3e%3crect fill='%23ffffff' x='5.129' y='3.8' transform='matrix(-.707 -.707 .707 -.707 6.189 20.064)' width='4.243' height='9.899'/%3e%3cpath fill='%23ffffff' d='M.908 14.775c-.087.262.055.397.316.312l2.001-.667-1.65-1.646-.667 2.001z'/%3e%3c/g%3e%3c/svg%3e");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-position: center;
mask-position: center;
}
@media (forced-colors: active) {
#drupal-off-canvas-wrapper .ui-dialog-titlebar::before {
background-color: canvastext;
}
}
/* Close button. */
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close {
position: absolute;
top: 50%;
inset-inline: auto 0.625rem;
overflow: hidden;
width: 1.875rem;
height: 1.875rem;
cursor: pointer;
transform: translateY(-50%);
text-indent: -624.9375rem;
color: inherit;
border: 1px solid transparent;
background-color: transparent;
appearance: none;
}
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close:focus {
outline: solid 2px currentColor;
outline-offset: 2px;
}
/* The plus icon. */
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close::before,
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close::after {
position: absolute;
top: calc(50% - 1px);
left: 50%;
width: 50%;
height: 0;
content: "";
border-top: solid 2px currentColor;
}
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close::before {
transform: translate(-50%, 50%) rotate(-45deg);
}
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close::after {
transform: translate(-50%, 50%) rotate(45deg);
}
/* Hide the default jQuery UI dialog close button. */
#drupal-off-canvas-wrapper .ui-dialog-titlebar-close .ui-icon {
display: none;
}

View File

@@ -0,0 +1,91 @@
/**
* @file
* Styling for the titlebar within the off-canvas dialog.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-title-padding: calc(3 * var(--off-canvas-vertical-spacing-unit));
--off-canvas-title-background-color: #2d2d2d;
--off-canvas-title-text-color: #fff;
--off-canvas-title-font-size: 16px;
& .ui-dialog-titlebar {
position: relative;
margin: 0 calc(-1 * var(--off-canvas-padding));
padding: var(--off-canvas-title-padding) 50px;
color: var(--off-canvas-title-text-color);
background-color: var(--off-canvas-title-background-color);
font-family: var(--off-canvas-title-font-family);
font-size: var(--off-canvas-title-font-size);
font-weight: bold;
/* The pencil icon. */
&::before {
position: absolute;
top: 0;
inset-inline-start: 1em;
display: block;
width: 20px;
height: 100%;
content: "";
background-color: currentColor;
mask-image: url(../../../icons/ffffff/pencil.svg);
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
@media (forced-colors: active) {
background-color: canvastext;
}
}
}
/* Close button. */
& .ui-dialog-titlebar-close {
position: absolute;
top: 50%;
inset-inline: auto 10px;
overflow: hidden;
width: 30px;
height: 30px;
cursor: pointer;
transform: translateY(-50%);
text-indent: -9999px;
color: inherit;
border: 1px solid transparent;
background-color: transparent;
appearance: none;
&:focus {
outline: solid 2px currentColor;
outline-offset: 2px;
}
/* The plus icon. */
&::before,
&::after {
position: absolute;
top: calc(50% - 1px);
left: 50%;
width: 50%;
height: 0;
content: "";
border-top: solid 2px currentColor;
}
&::before {
transform: translate(-50%, 50%) rotate(-45deg);
}
&::after {
transform: translate(-50%, 50%) rotate(45deg);
}
/* Hide the default jQuery UI dialog close button. */
& .ui-icon {
display: none;
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* Re-create utility styles for off-canvas dialog that are removed in the reset.
*
* @internal
*/
#drupal-off-canvas-wrapper .hidden {
display: none;
}
#drupal-off-canvas-wrapper .visually-hidden {
position: absolute !important;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
width: 1px !important;
height: 1px !important;
word-wrap: normal;
}
#drupal-off-canvas-wrapper .visually-hidden.focusable:is(:active, :focus) {
position: static !important;
overflow: visible;
clip: auto;
width: auto !important;
height: auto !important;
}
#drupal-off-canvas-wrapper .invisible {
visibility: hidden;
}

View File

@@ -0,0 +1,33 @@
/**
* @file
* Re-create utility styles for off-canvas dialog that are removed in the reset.
*
* @internal
*/
#drupal-off-canvas-wrapper {
& .hidden {
display: none;
}
& .visually-hidden {
position: absolute !important;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
width: 1px !important;
height: 1px !important;
word-wrap: normal;
&.focusable:is(:active, :focus) {
position: static !important;
overflow: visible;
clip: auto;
width: auto !important;
height: auto !important;
}
}
& .invisible {
visibility: hidden;
}
}

View File

@@ -0,0 +1,52 @@
/*
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/3084859
* @preserve
*/
/**
* @file
* CSS for off-canvas dialog wrapper.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-wrapper-box-shadow: 0 0 0.25rem 2px rgba(0, 0, 0, 0.3);
--off-canvas-wrapper-border-color: #2d2d2d;
--off-canvas-wrapper-border-width: 1px;
z-index: 501; /* Layer the dialog just under the toolbar. */
overflow: auto;
box-sizing: border-box;
height: 100%;
border-inline-start: solid var(--off-canvas-wrapper-border-width) var(--off-canvas-wrapper-border-color);
box-shadow: var(--off-canvas-wrapper-box-shadow);
/*
* Force the off-canvas dialog to be 100% width at the same breakpoint the
* dialog system uses to expand dialog widths.
*/
}
@media (max-width: 48rem) {
#drupal-off-canvas-wrapper {
width: 100% !important;
}
}
/* When off-canvas dialog is at 100% width stop the body from scrolling */
@media (max-width: 48rem) {
body.js-off-canvas-dialog-open {
position: fixed;
}
}
/* This is a page level content wrapper that shrinks when off-canvas is open. */
.dialog-off-canvas-main-canvas {
transition:
padding-right 0.7s ease,
padding-left 0.7s ease,
padding-top 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
.dialog-off-canvas-main-canvas {
transition: none;
}
}

View File

@@ -0,0 +1,45 @@
/**
* @file
* CSS for off-canvas dialog wrapper.
*
* @internal
*/
#drupal-off-canvas-wrapper {
--off-canvas-wrapper-box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.3);
--off-canvas-wrapper-border-color: #2d2d2d;
--off-canvas-wrapper-border-width: 1px;
z-index: 501; /* Layer the dialog just under the toolbar. */
overflow: auto;
box-sizing: border-box;
height: 100%;
border-inline-start: solid var(--off-canvas-wrapper-border-width) var(--off-canvas-wrapper-border-color);
box-shadow: var(--off-canvas-wrapper-box-shadow);
/*
* Force the off-canvas dialog to be 100% width at the same breakpoint the
* dialog system uses to expand dialog widths.
*/
@media (max-width: 768px) {
width: 100% !important;
}
}
/* When off-canvas dialog is at 100% width stop the body from scrolling */
body.js-off-canvas-dialog-open {
@media (max-width: 768px) {
position: fixed;
}
}
/* This is a page level content wrapper that shrinks when off-canvas is open. */
.dialog-off-canvas-main-canvas {
transition:
padding-right 0.7s ease,
padding-left 0.7s ease,
padding-top 0.3s ease;
@media (prefers-reduced-motion: reduce) {
transition: none;
}
}

View File

@@ -0,0 +1,384 @@
/**
* @file
* Drupal's off-canvas library.
*/
(($, Drupal, debounce, displace) => {
/**
* Off-canvas dialog implementation using jQuery Dialog.
*
* Transforms the regular dialogs created using Drupal.dialog when the dialog
* element equals '#drupal-off-canvas' into a side-loading dialog.
*
* @namespace
*/
Drupal.offCanvas = {
/**
* Storage for position information about the tray.
*
* @type {?String}
*/
position: null,
/**
* The minimum height of the tray when opened at the top of the page.
*
* @type {Number}
*/
minimumHeight: 30,
/**
* The minimum width to use body displace needs to match the width at which
* the tray will be 100% width. @see core/misc/dialog/off-canvas.css
*
* @type {Number}
*/
minDisplaceWidth: 768,
/**
* Wrapper used to position off-canvas dialog.
*
* @type {jQuery}
*/
$mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
/**
* Determines if an element is an off-canvas dialog.
*
* @param {jQuery} $element
* The dialog element.
*
* @return {boolean}
* True this is currently an off-canvas dialog.
*/
isOffCanvas($element) {
return $element[0].id === 'drupal-off-canvas';
},
/**
* Remove off-canvas dialog events.
*
* @param {jQuery} $element
* The target element.
*/
removeOffCanvasEvents($element) {
$element.off('.off-canvas');
$(document).off('.off-canvas');
$(window).off('.off-canvas');
},
/**
* Handler fired before an off-canvas dialog has been opened.
*
* @param {Object} settings
* Settings related to the composition of the dialog.
*
* @return {undefined}
*/
beforeCreate({ settings, $element }) {
// Clean up previous dialog event handlers.
Drupal.offCanvas.removeOffCanvasEvents($element);
$('body').addClass('js-off-canvas-dialog-open');
// @see http://api.jqueryui.com/position/
settings.position = {
my: 'left top',
at: `${Drupal.offCanvas.getEdge()} top`,
of: window,
};
/**
* Applies initial height and with to dialog based depending on position.
* @see http://api.jqueryui.com/dialog for all dialog options.
*/
const position = settings.drupalOffCanvasPosition;
const height = position === 'side' ? $(window).height() : settings.height;
const width = position === 'side' ? settings.width : '100%';
settings.height = height;
settings.width = width;
},
/**
* Handler fired after an off-canvas dialog has been closed.
*
* @return {undefined}
*/
beforeClose({ $element }) {
$('body').removeClass('js-off-canvas-dialog-open');
// Remove all *.off-canvas events
Drupal.offCanvas.removeOffCanvasEvents($element);
Drupal.offCanvas.resetPadding();
},
/**
* Handler fired when an off-canvas dialog has been opened.
*
* @param {jQuery} $element
* The off-canvas dialog element.
* @param {Object} settings
* Settings related to the composition of the dialog.
*
* @return {undefined}
*/
afterCreate({ $element, settings }) {
const eventData = { settings, $element, offCanvasDialog: this };
$element
.on(
'dialogContentResize.off-canvas',
eventData,
Drupal.offCanvas.handleDialogResize,
)
.on(
'dialogContentResize.off-canvas',
eventData,
Drupal.offCanvas.bodyPadding,
);
Drupal.offCanvas
.getContainer($element)
.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
$(window)
.on(
'resize.off-canvas',
eventData,
debounce(Drupal.offCanvas.resetSize, 100, true),
)
.trigger('resize.off-canvas');
},
/**
* Toggle classes based on title existence.
* Called with Drupal.offCanvas.afterCreate.
*
* @param {Object} settings
* Settings related to the composition of the dialog.
*
* @return {undefined}
*/
render({ settings }) {
$(
'.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar',
).toggleClass('ui-dialog-empty-title', !settings.title);
$('.ui-dialog-off-canvas').attr('id', 'drupal-off-canvas-wrapper');
},
/**
* Adjusts the dialog on resize.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} event.data
* Data attached to the event.
*/
handleDialogResize(event) {
const $element = event.data.$element;
const $container = Drupal.offCanvas.getContainer($element);
const $offsets = $container.find(
'> :not(#drupal-off-canvas, .ui-resizable-handle)',
);
let offset = 0;
// Let scroll element take all the height available.
$element[0].style.height = 'auto';
const modalHeight = $container.height();
$offsets.each((i, e) => {
offset += $(e).outerHeight();
});
// Take internal padding into account.
const scrollOffset = $element.outerHeight() - $element.height();
$element.height(modalHeight - offset - scrollOffset);
},
/**
* Resets the size of the dialog.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} event.data
* Data attached to the event.
*/
resetSize(event) {
const $element = event.data.$element;
const container = Drupal.offCanvas.getContainer($element);
const position = event.data.settings.drupalOffCanvasPosition;
// Only remove the `data-offset-*` attribute if the value previously
// exists and the orientation is changing.
if (Drupal.offCanvas.position && Drupal.offCanvas.position !== position) {
container.removeAttr(`data-offset-${Drupal.offCanvas.position}`);
}
// Set a minimum height on $element
if (position === 'top') {
$element[0].style.minHeight = `${Drupal.offCanvas.minimumHeight}px`;
}
displace();
const offsets = displace.offsets;
const topPosition =
position === 'side' && offsets.top !== 0 ? `+${offsets.top}` : '';
const adjustedOptions = {
// @see http://api.jqueryui.com/position/
position: {
my: `${Drupal.offCanvas.getEdge()} top`,
at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
of: window,
},
};
const height =
position === 'side'
? `${$(window).height() - (offsets.top + offsets.bottom)}px`
: event.data.settings.height;
Object.assign(container[0].style, {
position: 'fixed',
height: Number.isNaN(parseFloat(height))
? height
: `${parseFloat(height)}px`,
});
$element.dialog('option', adjustedOptions);
$element
?.get(0)
?.dispatchEvent(
new CustomEvent('dialogContentResize', { bubbles: true }),
);
Drupal.offCanvas.position = position;
},
/**
* Adjusts the body padding when the dialog is resized.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} event.data
* Data attached to the event.
*/
bodyPadding(event) {
const position = event.data.settings.drupalOffCanvasPosition;
if (
position === 'side' &&
$('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth
) {
return;
}
Drupal.offCanvas.resetPadding();
const $element = event.data.$element;
const $container = Drupal.offCanvas.getContainer($element);
const mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper[0];
const width = $container.outerWidth();
const mainCanvasPadding =
window.getComputedStyle(mainCanvasWrapper)[
`padding-${Drupal.offCanvas.getEdge()}`
];
if (position === 'side' && width !== mainCanvasPadding) {
mainCanvasWrapper.style[`padding-${Drupal.offCanvas.getEdge()}`] =
`${width}px`;
$container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
displace();
}
const height = $container.outerHeight();
if (position === 'top') {
mainCanvasWrapper.style.paddingTop = `${height}px`;
$container.attr('data-offset-top', height);
displace();
}
},
/**
* The HTML element that surrounds the dialog.
* @param {HTMLElement} $element
* The dialog element.
*
* @return {HTMLElement}
* The containing element.
*/
getContainer($element) {
return $element.dialog('widget');
},
/**
* The edge of the screen that the dialog should appear on.
*
* @return {string}
* The edge the tray will be shown on, left or right.
*/
getEdge() {
return document.documentElement.dir === 'rtl' ? 'left' : 'right';
},
/**
* Resets main canvas wrapper and toolbar padding / margin.
*/
resetPadding() {
Drupal.offCanvas.$mainCanvasWrapper[0].style[
`padding-${Drupal.offCanvas.getEdge()}`
] = 0;
Drupal.offCanvas.$mainCanvasWrapper[0].style.paddingTop = 0;
displace();
},
};
/**
* Attaches off-canvas dialog behaviors.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches event listeners for off-canvas dialogs.
*/
Drupal.behaviors.offCanvasEvents = {
attach: () => {
if (!once('off-canvas', 'html').length) {
return;
}
window.addEventListener('dialog:beforecreate', (e) => {
const $element = $(e.target);
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.beforeCreate({
$element,
settings: e.settings,
});
}
});
window.addEventListener('dialog:aftercreate', (e) => {
const $element = $(e.target);
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.render({
$element,
dialog: e.dialog,
settings: e.settings,
});
Drupal.offCanvas.afterCreate({
$element,
settings: e.settings,
});
}
});
window.addEventListener('dialog:beforeclose', (e) => {
const $element = $(e.target);
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.beforeClose({
$element,
});
}
});
},
};
})(jQuery, Drupal, Drupal.debounce, Drupal.displace);

267
core/misc/displace.js Executable file
View File

@@ -0,0 +1,267 @@
/**
* @file
* Manages elements that can offset the size of the viewport.
*
* Measures and reports viewport offset dimensions from elements like the
* toolbar that can potentially displace the positioning of other elements.
*/
/**
* @typedef {object} Drupal~displaceOffset
*
* @prop {number} top
* @prop {number} left
* @prop {number} right
* @prop {number} bottom
*/
/**
* Triggers when layout of the page changes.
*
* This is used to position fixed element on the page during page resize and
* Toolbar toggling.
*
* @event drupalViewportOffsetChange
*/
(function ($, Drupal, debounce) {
/**
*
* @type {Drupal~displaceOffset}
*/
const cache = {
right: 0,
left: 0,
bottom: 0,
top: 0,
};
/**
* The prefix used for the css custom variable name.
*
* @type {string}
*/
const cssVarPrefix = '--drupal-displace-offset';
const documentStyle = document.documentElement.style;
const offsetKeys = Object.keys(cache);
/**
* The object with accessors that update the CSS variable on value update.
*
* @type {Drupal~displaceOffset}
*/
const offsetProps = {};
offsetKeys.forEach((edge) => {
offsetProps[edge] = {
// Show this property when using Object.keys().
enumerable: true,
get() {
return cache[edge];
},
set(value) {
// Only update the CSS custom variable when the value changed.
if (value !== cache[edge]) {
documentStyle.setProperty(`${cssVarPrefix}-${edge}`, `${value}px`);
}
cache[edge] = value;
},
};
});
/**
* Current value of the size of margins on the page.
*
* This property is read-only and the object is sealed to prevent key name
* modifications since key names are used to dynamically construct CSS custom
* variable names.
*
* @name Drupal.displace.offsets
*
* @type {Drupal~displaceOffset}
*/
const offsets = Object.seal(Object.defineProperties({}, offsetProps));
/**
* Calculates displacement for element based on its dimensions and placement.
*
* @param {HTMLElement} el
* The element whose dimensions and placement will be measured.
*
* @param {string} edge
* The name of the edge of the viewport that the element is associated
* with.
*
* @return {number}
* The viewport displacement distance for the requested edge.
*/
function getRawOffset(el, edge) {
const $el = $(el);
const documentElement = document.documentElement;
let displacement = 0;
const horizontal = edge === 'left' || edge === 'right';
// Get the offset of the element itself.
let placement = $el.offset()[horizontal ? 'left' : 'top'];
// Subtract scroll distance from placement to get the distance
// to the edge of the viewport.
placement -=
window[`scroll${horizontal ? 'X' : 'Y'}`] ||
document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] ||
0;
// Find the displacement value according to the edge.
switch (edge) {
// Left and top elements displace as a sum of their own offset value
// plus their size.
case 'top':
// Total displacement is the sum of the elements placement and size.
displacement = placement + $el.outerHeight();
break;
case 'left':
// Total displacement is the sum of the elements placement and size.
displacement = placement + $el.outerWidth();
break;
// Right and bottom elements displace according to their left and
// top offset. Their size isn't important.
case 'bottom':
displacement = documentElement.clientHeight - placement;
break;
case 'right':
displacement = documentElement.clientWidth - placement;
break;
default:
displacement = 0;
}
return displacement;
}
/**
* Gets a specific edge's offset.
*
* Any element with the attribute data-offset-{edge} e.g. data-offset-top will
* be considered in the viewport offset calculations. If the attribute has a
* numeric value, that value will be used. If no value is provided, one will
* be calculated using the element's dimensions and placement.
*
* @function Drupal.displace.calculateOffset
*
* @param {string} edge
* The name of the edge to calculate. Can be 'top', 'right',
* 'bottom' or 'left'.
*
* @return {number}
* The viewport displacement distance for the requested edge.
*/
function calculateOffset(edge) {
let edgeOffset = 0;
const displacingElements = document.querySelectorAll(
`[data-offset-${edge}]`,
);
const n = displacingElements.length;
for (let i = 0; i < n; i++) {
const el = displacingElements[i];
// If the element is not visible, do consider its dimensions.
if (el.style.display === 'none') {
continue;
}
// If the offset data attribute contains a displacing value, use it.
let displacement = parseInt(el.getAttribute(`data-offset-${edge}`), 10);
// If the element's offset data attribute exits
// but is not a valid number then get the displacement
// dimensions directly from the element.
// eslint-disable-next-line no-restricted-globals
if (isNaN(displacement)) {
displacement = getRawOffset(el, edge);
}
// If the displacement value is larger than the current value for this
// edge, use the displacement value.
edgeOffset = Math.max(edgeOffset, displacement);
}
return edgeOffset;
}
/**
* Informs listeners of the current offset dimensions.
*
* Corresponding CSS custom variables are also updated.
* Corresponding CSS custom variables names are:
* - `--drupal-displace-offset-top`
* - `--drupal-displace-offset-right`
* - `--drupal-displace-offset-bottom`
* - `--drupal-displace-offset-left`
*
* @function Drupal.displace
*
* @prop {Drupal~displaceOffset} offsets
*
* @param {boolean} [broadcast=true]
* When true, causes the recalculated offsets values to be
* broadcast to listeners. If none is given, defaults to true.
*
* @return {Drupal~displaceOffset}
* An object whose keys are the for sides an element -- top, right, bottom
* and left. The value of each key is the viewport displacement distance for
* that edge.
*
* @fires event:drupalViewportOffsetChange
*/
function displace(broadcast = true) {
const newOffsets = {};
// Getting the offset and setting the offset needs to be separated because
// of performance concerns. Only do DOM/style reading happening here.
offsetKeys.forEach((edge) => {
newOffsets[edge] = calculateOffset(edge);
});
// Once we have all the values, write to the DOM/style.
offsetKeys.forEach((edge) => {
// Updating the value in place also update Drupal.displace.offsets.
offsets[edge] = newOffsets[edge];
});
if (broadcast) {
$(document).trigger('drupalViewportOffsetChange', offsets);
}
return offsets;
}
/**
* Registers a resize handler on the window.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.drupalDisplace = {
attach() {
// Mark this behavior as processed on the first pass.
if (this.displaceProcessed) {
return;
}
this.displaceProcessed = true;
$(window).on('resize.drupalDisplace', debounce(displace, 200));
},
};
/**
* Assign the displace function to a property of the Drupal global object.
*
* @ignore
*/
Drupal.displace = displace;
/**
* Expose offsets to other scripts to avoid having to recalculate offsets.
*
* @ignore
*/
Object.defineProperty(Drupal.displace, 'offsets', {
value: offsets,
// Make sure other scripts don't replace this object.
writable: false,
});
/**
* Expose method to compute a single edge offsets.
*
* @ignore
*/
Drupal.displace.calculateOffset = calculateOffset;
})(jQuery, Drupal, Drupal.debounce);

View File

@@ -0,0 +1,163 @@
/**
* @file
* Base styles for dropbuttons.
*/
/**
* When a dropbutton has only one option, it is simply a button.
*/
.dropbutton-wrapper,
.dropbutton-wrapper div {
box-sizing: border-box;
}
.js .dropbutton-wrapper,
.js .dropbutton-widget {
position: relative;
display: block;
}
@media screen and (max-width: 600px) {
.js .dropbutton-wrapper {
width: 100%;
}
}
/* Splitbuttons */
@media screen and (min-width: 600px) {
.form-actions .dropbutton-wrapper {
float: left; /* LTR */
}
[dir="rtl"] .form-actions .dropbutton-wrapper {
float: right;
}
}
.js .form-actions .dropbutton-widget {
position: static;
}
.js td .dropbutton-multiple .dropbutton-widget {
position: absolute;
}
.js td .dropbutton-wrapper {
min-height: 2em;
}
.js td .dropbutton-multiple {
max-width: 100%;
margin-right: 2em; /* LTR */
padding-right: 10em; /* LTR */
}
[dir="rtl"].js td .dropbutton-multiple {
margin-right: 0;
margin-left: 2em;
padding-right: 0;
padding-left: 10em;
}
.js td .dropbutton-multiple .dropbutton-action a,
.js td .dropbutton-multiple .dropbutton-action input,
.js td .dropbutton-multiple .dropbutton-action button {
width: auto;
}
/* UL styles are over-scoped in core, so this selector needs weight parity. */
.js .dropbutton-widget .dropbutton {
overflow: hidden;
margin: 0;
padding: 0;
list-style-type: none;
list-style-image: none;
}
.js .dropbutton li,
.js .dropbutton a {
display: block;
outline: none;
}
.js .dropbutton li:hover,
.js .dropbutton li:focus,
.js .dropbutton a:hover,
.js .dropbutton a:focus {
outline: initial;
}
/**
* The dropbutton styling.
*
* A dropbutton is a widget that displays a list of action links as a button
* with a primary action. Secondary actions are hidden behind a click on a
* twisty arrow.
*
* The arrow is created using border on a zero-width, zero-height span.
* The arrow inherits the link color, but can be overridden with border colors.
*/
.js .dropbutton-multiple .dropbutton-widget {
padding-right: 2em; /* LTR */
}
.js[dir="rtl"] .dropbutton-multiple .dropbutton-widget {
padding-right: 0;
padding-left: 2em;
}
.dropbutton-multiple.open,
.dropbutton-multiple.open .dropbutton-widget {
max-width: none;
}
.dropbutton-multiple.open {
z-index: 100;
}
.dropbutton-multiple .dropbutton .secondary-action {
display: none;
}
.dropbutton-multiple.open .dropbutton .secondary-action {
display: block;
}
.dropbutton-toggle {
position: absolute;
top: 0;
right: 0; /* LTR */
bottom: 0;
display: block;
width: 2em;
white-space: nowrap;
text-indent: 110%;
}
[dir="rtl"] .dropbutton-toggle {
right: auto;
left: 0;
}
.dropbutton-toggle button {
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
cursor: pointer;
border: 0;
background: none;
}
.dropbutton-toggle button:hover,
.dropbutton-toggle button:focus {
outline: initial;
}
.dropbutton-arrow {
position: absolute;
top: 50%;
right: 40%; /* 0.6667em; */ /* LTR */
display: block;
overflow: hidden;
width: 0;
height: 0;
margin-top: -0.1666em;
border-width: 0.3333em 0.3333em 0;
border-style: solid;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
line-height: 0;
}
[dir="rtl"] .dropbutton-arrow {
right: auto;
left: 0.6667em;
}
.dropbutton-multiple.open .dropbutton-arrow {
top: 0.6667em;
border-top-color: transparent;
border-bottom: 0.3333em solid;
}

View File

@@ -0,0 +1,236 @@
/**
* @file
* Dropbutton feature.
*/
(function ($, Drupal) {
/**
* A DropButton presents an HTML list as a button with a primary action.
*
* All secondary actions beyond the first in the list are presented in a
* dropdown list accessible through a toggle arrow associated with the button.
*
* @constructor Drupal.DropButton
*
* @param {HTMLElement} dropbutton
* A DOM element.
* @param {object} settings
* A list of options including:
* @param {string} settings.title
* The text inside the toggle link element. This text is hidden
* from visual UAs.
*/
function DropButton(dropbutton, settings) {
// Merge defaults with settings.
const options = $.extend(
{ title: Drupal.t('List additional actions') },
settings,
);
const $dropbutton = $(dropbutton);
/**
* @type {jQuery}
*/
this.$dropbutton = $dropbutton;
/**
* @type {jQuery}
*/
this.$list = $dropbutton.find('.dropbutton');
/**
* Find actions and mark them.
*
* @type {jQuery}
*/
this.$actions = this.$list.find('li').addClass('dropbutton-action');
// Add the special dropdown only if there are hidden actions.
if (this.$actions.length > 1) {
// Identify the first element of the collection.
const $primary = this.$actions.slice(0, 1);
// Identify the secondary actions.
const $secondary = this.$actions.slice(1);
$secondary.addClass('secondary-action');
// Add toggle link.
$primary.after(Drupal.theme('dropbuttonToggle', options));
// Bind mouse events.
this.$dropbutton.addClass('dropbutton-multiple').on({
/**
* Adds a timeout to close the dropdown on mouseleave.
*
* @ignore
*/
'mouseleave.dropbutton': this.hoverOut.bind(this),
/**
* Clears timeout when mouseout of the dropdown.
*
* @ignore
*/
'mouseenter.dropbutton': this.hoverIn.bind(this),
/**
* Similar to mouseleave/mouseenter, but for keyboard navigation.
*
* @ignore
*/
'focusout.dropbutton': this.focusOut.bind(this),
/**
* @ignore
*/
'focusin.dropbutton': this.focusIn.bind(this),
});
} else {
this.$dropbutton.addClass('dropbutton-single');
}
}
/**
* Delegated callback for opening and closing dropbutton secondary actions.
*
* @function Drupal.DropButton~dropbuttonClickHandler
*
* @param {jQuery.Event} e
* The event triggered.
*/
function dropbuttonClickHandler(e) {
e.preventDefault();
$(e.target).closest('.dropbutton-wrapper').toggleClass('open');
}
/**
* Process elements with the .dropbutton class on page load.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches dropButton behaviors.
*/
Drupal.behaviors.dropButton = {
attach(context, settings) {
const dropbuttons = once('dropbutton', '.dropbutton-wrapper', context);
if (dropbuttons.length) {
// Adds the delegated handler that will toggle dropdowns on click.
const body = once('dropbutton-click', 'body');
if (body.length) {
$(body).on('click', '.dropbutton-toggle', dropbuttonClickHandler);
}
// Initialize all buttons.
dropbuttons.forEach((dropbutton) => {
DropButton.dropbuttons.push(
new DropButton(dropbutton, settings.dropbutton),
);
});
}
},
};
/**
* Extend the DropButton constructor.
*/
$.extend(
DropButton,
/** @lends Drupal.DropButton */ {
/**
* Store all processed DropButtons.
*
* @type {Array.<Drupal.DropButton>}
*/
dropbuttons: [],
},
);
/**
* Extend the DropButton prototype.
*/
$.extend(
DropButton.prototype,
/** @lends Drupal.DropButton# */ {
/**
* Toggle the dropbutton open and closed.
*
* @param {boolean} [show]
* Force the dropbutton to open by passing true or to close by
* passing false.
*/
toggle(show) {
const isBool = typeof show === 'boolean';
show = isBool ? show : !this.$dropbutton.hasClass('open');
this.$dropbutton.toggleClass('open', show);
},
/**
* @method
*/
hoverIn() {
// Clear any previous timer we were using.
if (this.timerID) {
window.clearTimeout(this.timerID);
}
},
/**
* @method
*/
hoverOut() {
// Wait half a second before closing.
this.timerID = window.setTimeout(this.close.bind(this), 500);
},
/**
* @method
*/
open() {
this.toggle(true);
},
/**
* @method
*/
close() {
this.toggle(false);
},
/**
* @param {jQuery.Event} e
* The event triggered.
*/
focusOut(e) {
this.hoverOut.call(this, e);
},
/**
* @param {jQuery.Event} e
* The event triggered.
*/
focusIn(e) {
this.hoverIn.call(this, e);
},
},
);
$.extend(
Drupal.theme,
/** @lends Drupal.theme */ {
/**
* A toggle is an interactive element often bound to a click handler.
*
* @param {object} options
* Options object.
* @param {string} [options.title]
* The button text.
*
* @return {string}
* A string representing a DOM fragment.
*/
dropbuttonToggle(options) {
return `<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">${options.title}</span></span></button></li>`;
},
},
);
// Expose constructor in the public space.
Drupal.DropButton = DropButton;
})(jQuery, Drupal);

34
core/misc/drupal.init.js Executable file
View File

@@ -0,0 +1,34 @@
// Allow other JavaScript libraries to use $.
if (window.jQuery) {
jQuery.noConflict();
}
// Class indicating that JS is enabled; used for styling purpose.
document.documentElement.className += ' js';
// JavaScript should be made compatible with libraries other than jQuery by
// wrapping it in an anonymous closure.
(function (Drupal, drupalSettings) {
/**
* Calls callback when document ready.
*
* @param {function} callback
* The function to be called on document ready.
*/
const domReady = (callback) => {
const listener = () => {
callback();
document.removeEventListener('DOMContentLoaded', listener);
};
if (document.readyState !== 'loading') {
setTimeout(callback, 0);
} else {
document.addEventListener('DOMContentLoaded', listener);
}
};
// Attach all behaviors.
domReady(() => {
Drupal.attachBehaviors(document, drupalSettings);
});
})(Drupal, window.drupalSettings);

685
core/misc/drupal.js Executable file
View File

@@ -0,0 +1,685 @@
/**
* @file
* Defines the Drupal JavaScript API.
*/
/**
* A jQuery object, typically the return value from a `$(selector)` call.
*
* Holds an HTMLElement or a collection of HTMLElements.
*
* @typedef {object} jQuery
*
* @prop {number} length=0
* Number of elements contained in the jQuery object.
*/
/**
* Variable generated by Drupal that holds all translated strings from PHP.
*
* Content of this variable is automatically created by Drupal when using the
* Interface Translation module. It holds the translation of strings used on
* the page.
*
* This variable is used to pass data from the backend to the frontend. Data
* contained in `drupalSettings` is used during behavior initialization.
*
* @global
*
* @var {object} drupalTranslations
*/
/**
* Global Drupal object.
*
* All Drupal JavaScript APIs are contained in this namespace.
*
* @global
*
* @namespace
*/
window.Drupal = { behaviors: {}, locale: {} };
// JavaScript should be made compatible with libraries other than jQuery by
// wrapping it in an anonymous closure.
(function (
Drupal,
drupalSettings,
drupalTranslations,
console,
Proxy,
Reflect,
) {
/**
* Helper to rethrow errors asynchronously.
*
* This way Errors bubbles up outside of the original callstack, making it
* easier to debug errors in the browser.
*
* @param {Error|string} error
* The error to be thrown.
*/
Drupal.throwError = function (error) {
setTimeout(() => {
throw error;
}, 0);
};
/**
* Custom error thrown after attach/detach if one or more behaviors failed.
* Initializes the JavaScript behaviors for page loads and Ajax requests.
*
* @callback Drupal~behaviorAttach
*
* @param {Document|HTMLElement} context
* An element to detach behaviors from.
* @param {?object} settings
* An object containing settings for the current context. It is rarely used.
*
* @see Drupal.attachBehaviors
*/
/**
* Reverts and cleans up JavaScript behavior initialization.
*
* @callback Drupal~behaviorDetach
*
* @param {Document|HTMLElement} context
* An element to attach behaviors to.
* @param {object} settings
* An object containing settings for the current context.
* @param {string} trigger
* One of `'unload'`, `'move'`, or `'serialize'`.
*
* @see Drupal.detachBehaviors
*/
/**
* @typedef {object} Drupal~behavior
*
* @prop {Drupal~behaviorAttach} attach
* Function run on page load and after an Ajax call.
* @prop {Drupal~behaviorDetach} [detach]
* Function run when content is serialized or removed from the page.
*/
/**
* Holds all initialization methods.
*
* @namespace Drupal.behaviors
*
* @type {Object.<string, Drupal~behavior>}
*/
/**
* Defines a behavior to be run during attach and detach phases.
*
* Attaches all registered behaviors to a page element.
*
* Behaviors are event-triggered actions that attach to page elements,
* enhancing default non-JavaScript UIs. Behaviors are registered in the
* {@link Drupal.behaviors} object using the method 'attach' and optionally
* also 'detach'.
*
* {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event
* and therefore runs on initial page load. Developers implementing Ajax in
* their solutions should also call this function after new page content has
* been loaded, feeding in an element to be processed, in order to attach all
* behaviors to the new content.
*
* Behaviors should use `var elements =
* once('behavior-name', selector, context);` to ensure the behavior is
* attached only once to a given element. (Doing so enables the reprocessing
* of given elements, which may be needed on occasion despite the ability to
* limit behavior attachment to a particular element.)
*
* @example
* Drupal.behaviors.behaviorName = {
* attach: function (context, settings) {
* // ...
* },
* detach: function (context, settings, trigger) {
* // ...
* }
* };
*
* @param {Document|HTMLElement} [context=document]
* An element to attach behaviors to.
* @param {object} [settings=drupalSettings]
* An object containing settings for the current context. If none is given,
* the global {@link drupalSettings} object is used.
*
* @see Drupal~behaviorAttach
* @see Drupal.detachBehaviors
*
* @throws {Drupal~DrupalBehaviorError}
*/
Drupal.attachBehaviors = function (context, settings) {
context = context || document;
settings = settings || drupalSettings;
const behaviors = Drupal.behaviors;
// Execute all of them.
Object.keys(behaviors || {}).forEach((i) => {
if (typeof behaviors[i].attach === 'function') {
// Don't stop the execution of behaviors in case of an error.
try {
behaviors[i].attach(context, settings);
} catch (e) {
Drupal.throwError(e);
}
}
});
};
/**
* Detaches registered behaviors from a page element.
*
* Developers implementing Ajax in their solutions should call this function
* before page content is about to be removed, feeding in an element to be
* processed, in order to allow special behaviors to detach from the content.
*
* Such implementations should use `once.filter()` and `once.remove()` to find
* elements with their corresponding `Drupal.behaviors.behaviorName.attach`
* implementation, i.e. `once.remove('behaviorName', selector, context)`,
* to ensure the behavior is detached only from previously processed elements.
*
* @param {Document|HTMLElement} [context=document]
* An element to detach behaviors from.
* @param {object} [settings=drupalSettings]
* An object containing settings for the current context. If none given,
* the global {@link drupalSettings} object is used.
* @param {string} [trigger='unload']
* A string containing what's causing the behaviors to be detached. The
* possible triggers are:
* - `'unload'`: The context element is being removed from the DOM.
* - `'move'`: The element is about to be moved within the DOM (for example,
* during a tabledrag row swap). After the move is completed,
* {@link Drupal.attachBehaviors} is called, so that the behavior can undo
* whatever it did in response to the move. Many behaviors won't need to
* do anything simply in response to the element being moved, but because
* IFRAME elements reload their "src" when being moved within the DOM,
* behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
* take some action.
* - `'serialize'`: When an Ajax form is submitted, this is called with the
* form as the context. This provides every behavior within the form an
* opportunity to ensure that the field elements have correct content
* in them before the form is serialized. The canonical use-case is so
* that WYSIWYG editors can update the hidden textarea to which they are
* bound.
*
* @throws {Drupal~DrupalBehaviorError}
*
* @see Drupal~behaviorDetach
* @see Drupal.attachBehaviors
*/
Drupal.detachBehaviors = function (context, settings, trigger) {
context = context || document;
settings = settings || drupalSettings;
trigger = trigger || 'unload';
const behaviors = Drupal.behaviors;
// Execute all of them.
Object.keys(behaviors || {}).forEach((i) => {
if (typeof behaviors[i].detach === 'function') {
// Don't stop the execution of behaviors in case of an error.
try {
behaviors[i].detach(context, settings, trigger);
} catch (e) {
Drupal.throwError(e);
}
}
});
};
/**
* Encodes special characters in a plain-text string for display as HTML.
*
* @param {string} str
* The string to be encoded.
*
* @return {string}
* The encoded string.
*
* @ingroup sanitization
*/
Drupal.checkPlain = function (str) {
str = str
.toString()
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return str;
};
/**
* Replaces placeholders with sanitized values in a string.
*
* @param {string} str
* A string with placeholders.
* @param {object} args
* An object of replacements pairs to make. Incidences of any key in this
* array are replaced with the corresponding value. Based on the first
* character of the key, the value is escaped and/or themed:
* - `'!variable'`: inserted as is.
* - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}).
* - `'%variable'`: escape text and theme as a placeholder for user-
* submitted content ({@link Drupal.checkPlain} +
* `{@link Drupal.theme}('placeholder')`).
*
* @return {string}
* The formatted string.
*
* @see Drupal.t
*/
Drupal.formatString = function (str, args) {
// Keep args intact.
const processedArgs = {};
// Transform arguments before inserting them.
Object.keys(args || {}).forEach((key) => {
switch (key.charAt(0)) {
// Escaped only.
case '@':
processedArgs[key] = Drupal.checkPlain(args[key]);
break;
// Pass-through.
case '!':
processedArgs[key] = args[key];
break;
// Escaped and placeholder.
default:
processedArgs[key] = Drupal.theme('placeholder', args[key]);
break;
}
});
return Drupal.stringReplace(str, processedArgs, null);
};
/**
* Replaces substring.
*
* The longest keys will be tried first. Once a substring has been replaced,
* its new value will not be searched again.
*
* @param {string} str
* A string with placeholders.
* @param {object} args
* Key-value pairs.
* @param {Array|null} keys
* Array of keys from `args`. Internal use only.
*
* @return {string}
* The replaced string.
*/
Drupal.stringReplace = function (str, args, keys) {
if (str.length === 0) {
return str;
}
// If the array of keys is not passed then collect the keys from the args.
if (!Array.isArray(keys)) {
keys = Object.keys(args || {});
// Order the keys by the character length. The shortest one is the first.
keys.sort((a, b) => a.length - b.length);
}
if (keys.length === 0) {
return str;
}
// Take next longest one from the end.
const key = keys.pop();
const fragments = str.split(key);
if (keys.length) {
for (let i = 0; i < fragments.length; i++) {
// Process each fragment with a copy of remaining keys.
fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));
}
}
return fragments.join(args[key]);
};
/**
* Translates strings to the page language, or a given language.
*
* See the documentation of the server-side t() function for further details.
*
* @param {string} str
* A string containing the English text to translate.
* @param {Object.<string, string>} [args]
* An object of replacements pairs to make after translation. Incidences
* of any key in this array are replaced with the corresponding value.
* See {@link Drupal.formatString}.
* @param {object} [options]
* Additional options for translation.
* @param {string} [options.context='']
* The context the source string belongs to.
*
* @return {string}
* The formatted string.
* The translated string.
*/
Drupal.t = function (str, args, options) {
options = options || {};
options.context = options.context || '';
// Fetch the localized version of the string.
if (
typeof drupalTranslations !== 'undefined' &&
drupalTranslations.strings &&
drupalTranslations.strings[options.context] &&
drupalTranslations.strings[options.context][str]
) {
str = drupalTranslations.strings[options.context][str];
}
if (args) {
str = Drupal.formatString(str, args);
}
return str;
};
/**
* Returns the URL to a Drupal page.
*
* @param {string} path
* Drupal path to transform to URL.
*
* @return {string}
* The full URL.
*/
Drupal.url = function (path) {
return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path;
};
/**
* Returns the passed in URL as an absolute URL.
*
* @param {string} url
* The URL string to be normalized to an absolute URL.
*
* @return {string}
* The normalized, absolute URL.
*
* @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
* @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
* @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
*/
Drupal.url.toAbsolute = function (url) {
const urlParsingNode = document.createElement('a');
// Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
// strings may throw an exception.
try {
url = decodeURIComponent(url);
} catch (e) {
// Empty.
}
urlParsingNode.setAttribute('href', url);
// IE <= 7 normalizes the URL when assigned to the anchor node similar to
// the other browsers.
return urlParsingNode.cloneNode(false).href;
};
/**
* Returns true if the URL is within Drupal's base path.
*
* @param {string} url
* The URL string to be tested.
*
* @return {boolean}
* `true` if local.
*
* @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
*/
Drupal.url.isLocal = function (url) {
// Always use browser-derived absolute URLs in the comparison, to avoid
// attempts to break out of the base path using directory traversal.
let absoluteUrl = Drupal.url.toAbsolute(url);
let { protocol } = window.location;
// Consider URLs that match this site's base URL but use HTTPS instead of HTTP
// as local as well.
if (protocol === 'http:' && absoluteUrl.startsWith('https:')) {
protocol = 'https:';
}
let baseUrl = `${protocol}//${
window.location.host
}${drupalSettings.path.baseUrl.slice(0, -1)}`;
// Decoding non-UTF-8 strings may throw an exception.
try {
absoluteUrl = decodeURIComponent(absoluteUrl);
} catch (e) {
// Empty.
}
try {
baseUrl = decodeURIComponent(baseUrl);
} catch (e) {
// Empty.
}
// The given URL matches the site's base URL, or has a path under the site's
// base URL.
return absoluteUrl === baseUrl || absoluteUrl.startsWith(`${baseUrl}/`);
};
/**
* Formats a string containing a count of items.
*
* This function ensures that the string is pluralized correctly. Since
* {@link Drupal.t} is called by this function, make sure not to pass
* already-localized strings to it.
*
* See the documentation of the server-side
* \Drupal\Core\StringTranslation\TranslationInterface::formatPlural()
* function for more details.
*
* @param {number} count
* The item count to display.
* @param {string} singular
* The string for the singular case. Make sure it is clear this is singular,
* to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
* use @count in the singular string.
* @param {string} plural
* The string for the plural case. Make sure it is clear this is plural, to
* ease translation. Use @count in place of the item count, as in "@count
* new comments".
* @param {object} [args]
* An object of replacements pairs to make after translation. Incidences
* of any key in this array are replaced with the corresponding value.
* See {@link Drupal.formatString}.
* Note that you do not need to include @count in this array.
* This replacement is done automatically for the plural case.
* @param {object} [options]
* The options to pass to the {@link Drupal.t} function.
*
* @return {string}
* A translated string.
*/
Drupal.formatPlural = function (count, singular, plural, args, options) {
args = args || {};
args['@count'] = count;
const pluralDelimiter = drupalSettings.pluralDelimiter;
const translations = Drupal.t(
singular + pluralDelimiter + plural,
args,
options,
).split(pluralDelimiter);
let index = 0;
// Determine the index of the plural form.
if (
typeof drupalTranslations !== 'undefined' &&
drupalTranslations.pluralFormula
) {
index =
count in drupalTranslations.pluralFormula
? drupalTranslations.pluralFormula[count]
: drupalTranslations.pluralFormula.default;
} else if (args['@count'] !== 1) {
index = 1;
}
return translations[index];
};
/**
* Encodes a Drupal path for use in a URL.
*
* For aesthetic reasons slashes are not escaped.
*
* @param {string} item
* Unencoded path.
*
* @return {string}
* The encoded path.
*/
Drupal.encodePath = function (item) {
return window.encodeURIComponent(item).replace(/%2F/g, '/');
};
/**
* Triggers deprecation error.
*
* Deprecation errors are only triggered if deprecation errors haven't
* been suppressed.
*
* @param {Object} deprecation
* The deprecation options.
* @param {string} deprecation.message
* The deprecation message.
*
* @see https://www.drupal.org/core/deprecation#javascript
*/
Drupal.deprecationError = ({ message }) => {
if (
drupalSettings.suppressDeprecationErrors === false &&
typeof console !== 'undefined' &&
console.warn
) {
console.warn(`[Deprecation] ${message}`);
}
};
/**
* Triggers deprecation error when object property is being used.
*
* @param {Object} deprecation
* The deprecation options.
* @param {Object} deprecation.target
* The targeted object.
* @param {string} deprecation.deprecatedProperty
* A key of the deprecated property.
* @param {string} deprecation.message
* The deprecation message.
* @returns {Object}
*
* @see https://www.drupal.org/core/deprecation#javascript
*/
Drupal.deprecatedProperty = ({ target, deprecatedProperty, message }) => {
// Proxy and Reflect are not supported by all browsers. Unsupported browsers
// are ignored since this is a development feature.
if (!Proxy || !Reflect) {
return target;
}
return new Proxy(target, {
get: (target, key, ...rest) => {
if (key === deprecatedProperty) {
Drupal.deprecationError({ message });
}
return Reflect.get(target, key, ...rest);
},
});
};
/**
* Generates the themed representation of a Drupal object.
*
* All requests for themed output must go through this function. It examines
* the request and routes it to the appropriate theme function. If the current
* theme does not provide an override function, the generic theme function is
* called.
*
* @example
* <caption>To retrieve the HTML for text that should be emphasized and
* displayed as a placeholder inside a sentence.</caption>
* Drupal.theme('placeholder', text);
*
* @namespace
*
* @param {function} func
* The name of the theme function to call.
* @param {...args}
* Additional arguments to pass along to the theme function.
*
* @return {string|object|HTMLElement|jQuery}
* Any data the theme function returns. This could be a plain HTML string,
* but also a complex object.
*/
Drupal.theme = function (func, ...args) {
if (func in Drupal.theme) {
return Drupal.theme[func](...args);
}
};
/**
* Formats text for emphasized display in a placeholder inside a sentence.
*
* @param {string} str
* The text to format (plain-text).
*
* @return {string}
* The formatted text (html).
*/
Drupal.theme.placeholder = function (str) {
return `<em class="placeholder">${Drupal.checkPlain(str)}</em>`;
};
/**
* Determine if an element is visible.
*
* @param {HTMLElement} elem
* The element to check.
*
* @return {boolean}
* True if the element is visible.
*/
Drupal.elementIsVisible = function (elem) {
return !!(
elem.offsetWidth ||
elem.offsetHeight ||
elem.getClientRects().length
);
};
/**
* Determine if an element is hidden.
*
* @param {HTMLElement} elem
* The element to check.
*
* @return {boolean}
* True if the element is hidden.
*/
Drupal.elementIsHidden = function (elem) {
return !Drupal.elementIsVisible(elem);
};
})(
Drupal,
window.drupalSettings,
window.drupalTranslations,
window.console,
window.Proxy,
window.Reflect,
);

View File

@@ -0,0 +1,24 @@
/**
* @file
* Parse inline JSON and initialize the drupalSettings global object.
*/
(function () {
// Use direct child elements to harden against XSS exploits when CSP is on.
const settingsElement = document.querySelector(
'head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]',
);
/**
* Variable generated by Drupal with all the configuration created from PHP.
*
* @global
*
* @type {object}
*/
window.drupalSettings = {};
if (settingsElement !== null) {
window.drupalSettings = JSON.parse(settingsElement.textContent);
}
})();

BIN
core/misc/druplicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

71
core/misc/entity-form.js Executable file
View File

@@ -0,0 +1,71 @@
/**
* @file
* Defines JavaScript behaviors for the block_content module.
*/
(function ($, Drupal) {
/**
* Sets summaries about revision and translation of entities.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behavior entity form tabs.
*
* Specifically, it updates summaries to the revision information and the
* translation options.
*/
Drupal.behaviors.entityContentDetailsSummaries = {
attach(context) {
const $context = $(context);
$context
.find('.entity-content-form-revision-information')
.drupalSetSummary((context) => {
const $revisionContext = $(context);
const revisionCheckbox = $revisionContext.find(
'.js-form-item-revision input',
);
// Return 'New revision' if the 'Create new revision' checkbox is checked,
// or if the checkbox doesn't exist, but the revision log does. For users
// without the "Administer content" permission the checkbox won't appear,
// but the revision log will if the content type is set to auto-revision.
if (
(revisionCheckbox.length && revisionCheckbox[0].checked) ||
(!revisionCheckbox.length &&
$revisionContext.find('.js-form-item-revision-log textarea')
.length)
) {
return Drupal.t('New revision');
}
return Drupal.t('No revision');
});
$context
.find('details.entity-translation-options')
.drupalSetSummary((context) => {
const $translationContext = $(context);
let translate;
let $checkbox = $translationContext.find(
'.js-form-item-translation-translate input',
);
if ($checkbox.length) {
translate = $checkbox[0].checked
? Drupal.t('Needs to be updated')
: Drupal.t('Does not need to be updated');
} else {
$checkbox = $translationContext.find(
'.js-form-item-translation-retranslate input',
);
translate = $checkbox[0]?.checked
? Drupal.t('Flag other translations as outdated')
: Drupal.t('Do not flag other translations as outdated');
}
return translate;
});
},
};
})(jQuery, Drupal);

BIN
core/misc/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

8
core/misc/feed.svg Executable file
View File

@@ -0,0 +1,8 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<rect fill="#ff9900" width="16" height="16" x="0" y="0" rx="3" ry="3"/>
<g fill="#ffffff">
<circle cx="4.25" cy="11.812" r="1.5"/>
<path d="M10,13.312H7.875c0-2.83-2.295-5.125-5.125-5.125l0,0V6.062C6.754,6.062,10,9.308,10,13.312z"/>
<path d="M11.5,13.312c0-4.833-3.917-8.75-8.75-8.75V2.375c6.041,0,10.937,4.896,10.937,10.937H11.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 462 B

View File

@@ -0,0 +1,69 @@
/**
* @file
* Attaches behaviors for Drupal's field list keyboard navigation.
*/
(function (Drupal, { isFocusable }) {
/**
* Attaches the focus shifting functionality.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches behaviors.
*/
Drupal.behaviors.fieldListKeyboardNavigation = {
attach() {
once(
'keyboardNavigation',
'input[type="text"], input[type="number"]',
document.querySelector('[data-field-list-table]'),
).forEach((element) =>
element.addEventListener('keypress', (event) => {
if (event.key !== 'Enter') {
return;
}
event.preventDefault();
const currentElement = event.target;
// Function to find the next focusable element.
const findNextFocusableElement = (element) => {
const currentRow = element.closest('tr');
const inputElements = currentRow.querySelectorAll(
'input[type="text"], input[type="number"]',
);
const afterIndex = [...inputElements].indexOf(element) + 1;
// eslint-disable-next-line no-restricted-syntax
for (const inputElement of [...inputElements].slice(afterIndex)) {
if (isFocusable(inputElement)) {
return inputElement;
}
}
const nextRow = currentRow.nextElementSibling;
if (nextRow) {
return findNextFocusableElement(nextRow);
}
return null;
};
const nextFocusableElement = findNextFocusableElement(currentElement);
// If a focusable element is found, move focus there.
if (nextFocusableElement) {
nextFocusableElement.focus();
// Move cursor to the end of the input.
const value = nextFocusableElement.value;
nextFocusableElement.value = '';
nextFocusableElement.value = value;
return;
}
// If no focusable element is found, add another item to the list.
event.target
.closest('[data-field-list-table]')
.parentNode.querySelector('[data-field-list-button]')
.dispatchEvent(new Event('mousedown'));
}),
);
},
};
})(Drupal, window.tabbable);

334
core/misc/form.js Executable file
View File

@@ -0,0 +1,334 @@
/**
* @file
* Form features.
*/
/**
* Triggers when a value in the form changed.
*
* The event triggers when content is typed or pasted in a text field, before
* the change event triggers.
*
* @event formUpdated
*/
/**
* Triggers when a click on a page fragment link or hash change is detected.
*
* The event triggers when the fragment in the URL changes (a hash change) and
* when a link containing a fragment identifier is clicked. In case the hash
* changes due to a click this event will only be triggered once.
*
* @event formFragmentLinkClickOrHashChange
*/
(function ($, Drupal, debounce) {
/**
* Retrieves the summary for the first element.
*
* @return {string}
* The text of the summary.
*/
$.fn.drupalGetSummary = function () {
const callback = this.data('summaryCallback');
if (!this[0] || !callback) {
return '';
}
const result = callback(this[0]);
return result ? result.trim() : '';
};
/**
* Sets the summary for all matched elements.
*
* @param {function} callback
* Either a function that will be called each time the summary is
* retrieved or a string (which is returned each time).
*
* @return {jQuery}
* jQuery collection of the current element.
*
* @fires event:summaryUpdated
*
* @listens event:formUpdated
*/
$.fn.drupalSetSummary = function (callback) {
const self = this;
// To facilitate things, the callback should always be a function. If it's
// not, we wrap it into an anonymous function which just returns the value.
if (typeof callback !== 'function') {
const val = callback;
callback = function () {
return val;
};
}
return (
this.data('summaryCallback', callback)
// To prevent duplicate events, the handlers are first removed and then
// (re-)added.
.off('formUpdated.summary')
.on('formUpdated.summary', () => {
self.trigger('summaryUpdated');
})
// The actual summaryUpdated handler doesn't fire when the callback is
// changed, so we have to do this manually.
.trigger('summaryUpdated')
);
};
/**
* Prevents consecutive form submissions of identical form values.
*
* Repetitive form submissions that would submit the identical form values
* are prevented, unless the form values are different to the previously
* submitted values.
*
* This is a simplified re-implementation of a user-agent behavior that
* should be natively supported by major web browsers, but at this time, only
* Firefox has a built-in protection.
*
* A form value-based approach ensures that the constraint is triggered for
* consecutive, identical form submissions only. Compared to that, a form
* button-based approach would (1) rely on [visible] buttons to exist where
* technically not required and (2) require more complex state management if
* there are multiple buttons in a form.
*
* This implementation is based on form-level submit events only and relies
* on jQuery's serialize() method to determine submitted form values. As such,
* the following limitations exist:
*
* - Event handlers on form buttons that preventDefault() do not receive a
* double-submit protection. That is deemed to be fine, since such button
* events typically trigger reversible client-side or server-side
* operations that are local to the context of a form only.
* - Changed values in advanced form controls, such as file inputs, are not
* part of the form values being compared between consecutive form submits
* (due to limitations of jQuery.serialize()). That is deemed to be
* acceptable, because if the user forgot to attach a file, then the size of
* HTTP payload will most likely be small enough to be fully passed to the
* server endpoint within seconds, or even milliseconds. If a user
* mistakenly attached a wrong file and is technically versed enough to
* cancel the form submission (and HTTP payload) in order to attach a
* different file, then that edge-case is not supported here.
*
* Lastly, all forms submitted via HTTP GET are idempotent by definition of
* HTTP standards, so excluded in this implementation.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.formSingleSubmit = {
attach() {
function onFormSubmit(e) {
const $form = $(e.currentTarget);
const formValues = new URLSearchParams(
new FormData(e.target),
).toString();
const previousValues = $form.attr('data-drupal-form-submit-last');
if (previousValues === formValues) {
e.preventDefault();
} else {
$form.attr('data-drupal-form-submit-last', formValues);
}
}
$(once('form-single-submit', 'body')).on(
'submit.singleSubmit',
'form:not([method~="GET"])',
onFormSubmit,
);
},
};
/**
* Sends a 'formUpdated' event each time a form element is modified.
*
* @param {HTMLElement} element
* The element to trigger a form updated event on.
*
* @fires event:formUpdated
*/
function triggerFormUpdated(element) {
$(element).trigger('formUpdated');
}
/**
* Collects the IDs of all form fields in the given form.
*
* @param {HTMLFormElement} form
* The form element to search.
*
* @return {Array}
* Array of IDs for form fields.
*/
function fieldsList(form) {
// We use id to avoid name duplicates on radio fields and filter out
// elements with a name but no id.
return [].map.call(form.querySelectorAll('[name][id]'), (el) => el.id);
}
/**
* Triggers the 'formUpdated' event on form elements when they are modified.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches formUpdated behaviors.
* @prop {Drupal~behaviorDetach} detach
* Detaches formUpdated behaviors.
*
* @fires event:formUpdated
*/
Drupal.behaviors.formUpdated = {
attach(context) {
const $context = $(context);
const contextIsForm = context.tagName === 'FORM';
const $forms = $(
once('form-updated', contextIsForm ? $context : $context.find('form')),
);
let formFields;
if ($forms.length) {
// Initialize form behaviors, use $.makeArray to be able to use native
// forEach array method and have the callback parameters in the right
// order.
$.makeArray($forms).forEach((form) => {
const events = 'change.formUpdated input.formUpdated ';
const eventHandler = debounce((event) => {
triggerFormUpdated(event.target);
}, 300);
formFields = fieldsList(form).join(',');
form.setAttribute('data-drupal-form-fields', formFields);
$(form).on(events, eventHandler);
});
}
// On ajax requests context is the form element.
if (contextIsForm) {
formFields = fieldsList(context).join(',');
// @todo replace with form.getAttribute() when #1979468 is in.
const currentFields = $(context).attr('data-drupal-form-fields');
// If there has been a change in the fields or their order, trigger
// formUpdated.
if (formFields !== currentFields) {
triggerFormUpdated(context);
}
}
},
detach(context, settings, trigger) {
const $context = $(context);
const contextIsForm = context.tagName === 'FORM';
if (trigger === 'unload') {
once
.remove(
'form-updated',
contextIsForm ? $context : $context.find('form'),
)
.forEach((form) => {
form.removeAttribute('data-drupal-form-fields');
$(form).off('.formUpdated');
});
}
},
};
/**
* Prepopulate form fields with information from the visitor browser.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the behavior for filling user info from browser.
*/
Drupal.behaviors.fillUserInfoFromBrowser = {
attach(context, settings) {
const userInfo = ['name', 'mail', 'homepage'];
const $forms = $(
once('user-info-from-browser', '[data-user-info-from-browser]'),
);
if ($forms.length) {
userInfo.forEach((info) => {
const $element = $forms.find(`[name=${info}]`);
const browserData = localStorage.getItem(`Drupal.visitor.${info}`);
if (!$element.length) {
return;
}
const emptyValue = $element[0].value === '';
const defaultValue =
$element.attr('data-drupal-default-value') === $element[0].value;
if (browserData && (emptyValue || defaultValue)) {
$element.each(function (index, item) {
item.value = browserData;
});
}
});
}
$forms.on('submit', () => {
userInfo.forEach((info) => {
const $element = $forms.find(`[name=${info}]`);
if ($element.length) {
localStorage.setItem(`Drupal.visitor.${info}`, $element[0].value);
}
});
});
},
};
/**
* Sends a fragment interaction event on a hash change or fragment link click.
*
* @param {jQuery.Event} e
* The event triggered.
*
* @fires event:formFragmentLinkClickOrHashChange
*/
const handleFragmentLinkClickOrHashChange = (e) => {
let url;
if (e.type === 'click') {
url = e.currentTarget.location
? e.currentTarget.location
: e.currentTarget;
} else {
url = window.location;
}
const hash = url.hash.substring(1);
if (hash) {
const $target = $(`#${hash}`);
$('body').trigger('formFragmentLinkClickOrHashChange', [$target]);
/**
* Clicking a fragment link or a hash change should focus the target
* element, but event timing issues in multiple browsers require a timeout.
*/
setTimeout(() => $target.trigger('focus'), 300);
}
};
const debouncedHandleFragmentLinkClickOrHashChange = debounce(
handleFragmentLinkClickOrHashChange,
300,
true,
);
// Binds a listener to handle URL fragment changes.
$(window).on(
'hashchange.form-fragment',
debouncedHandleFragmentLinkClickOrHashChange,
);
/**
* Binds a listener to handle clicks on fragment links and absolute URL links
* containing a fragment, this is needed next to the hash change listener
* because clicking such links doesn't trigger a hash change when the fragment
* is already in the URL.
*/
$(document).on(
'click.form-fragment',
'a[href*="#"]',
debouncedHandleFragmentLinkClickOrHashChange,
);
})(jQuery, Drupal, Drupal.debounce);

BIN
core/misc/help.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#000000" d="M4 13.529c0 .275-.225.5-.5.5h-3c-.275 0-.5-.225-.5-.5v-4.25c0-.274.225-.5.5-.5h3c.275 0 .5.226.5.5v4.25zM10.002 13.529c0 .275-.225.5-.5.5h-3.002c-.275 0-.5-.225-.5-.5v-13c0-.275.225-.5.5-.5h3.002c.275 0 .5.225.5.5v13zM16.002 13.529c0 .275-.225.5-.5.5h-3c-.275 0-.5-.225-.5-.5v-9.5c0-.275.225-.5.5-.5h3c.275 0 .5.225.5.5v9.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M7.951 7.645c-.193.196-.193.516 0 .71l3.258 3.29c.193.193.191.519-.002.709l-1.371 1.371c-.193.192-.512.191-.707 0l-5.335-5.371c-.194-.194-.194-.514 0-.708l5.335-5.369c.195-.195.514-.195.707-.001l1.371 1.371c.193.194.195.513.002.709l-3.258 3.289z"/></svg>

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M8.053 8.355c.193-.195.193-.517 0-.711l-3.26-3.289c-.193-.195-.192-.514.002-.709l1.371-1.371c.194-.194.512-.193.706.001l5.335 5.369c.195.195.195.515 0 .708l-5.335 5.37c-.194.192-.512.193-.706.002l-1.371-1.371c-.194-.195-.195-.514-.002-.709l3.26-3.29z"/></svg>

After

Width:  |  Height:  |  Size: 348 B

1
core/misc/icons/000000/ex.svg Executable file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M3.51 13.925c.194.194.512.195.706.001l3.432-3.431c.194-.194.514-.194.708 0l3.432 3.431c.192.194.514.193.707-.001l1.405-1.417c.191-.195.189-.514-.002-.709l-3.397-3.4c-.192-.193-.192-.514-.002-.708l3.401-3.43c.189-.195.189-.515 0-.709l-1.407-1.418c-.195-.195-.513-.195-.707-.001l-3.43 3.431c-.195.194-.516.194-.708 0l-3.432-3.431c-.195-.195-.512-.194-.706.001l-1.407 1.417c-.194.195-.194.515 0 .71l3.403 3.429c.193.195.193.514-.001.708l-3.4 3.399c-.194.195-.195.516-.001.709l1.406 1.419z"/></svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#000000" d="M12.502 7h-5c-.276 0-.502-.225-.502-.5v-5c0-.275-.225-.5-.5-.5h-3c-.275 0-.5.225-.5.5v12.029c0 .275.225.5.5.5h9.002c.275 0 .5-.225.5-.5v-6.029c0-.275-.225-.5-.5-.5zM8.502 6h4c.275 0 .34-.159.146-.354l-4.293-4.292c-.195-.195-.353-.129-.353.146v4c0 .275.225.5.5.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 366 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M14.904 7.753l-2.373-2.372c-.291-.292-.529-.193-.529.22v1.399h-3v-3h1.398c.414 0 .512-.239.221-.53l-2.371-2.372c-.137-.136-.36-.136-.497 0l-2.372 2.372c-.292.292-.193.53.22.53h1.399v3h-3v-1.369c0-.413-.239-.511-.53-.22l-2.372 2.372c-.136.136-.136.359 0 .494l2.372 2.372c.291.292.53.192.53-.219v-1.43h3v3h-1.4c-.413 0-.511.238-.22.529l2.374 2.373c.137.137.36.137.495 0l2.373-2.373c.29-.291.19-.529-.222-.529h-1.398v-3h3v1.4c0 .412.238.511.529.219l2.373-2.371c.137-.137.137-.359 0-.495z"/></svg>

After

Width:  |  Height:  |  Size: 581 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px"><path d="M15.002,11.277c0-0.721,0-1.471,0-2.014c0-1.456-0.824-2.25-2.25-2.25c-1.428,0-3.5,0-3.5,0c-0.139,0-0.25-0.112-0.25-0.25v-2.04c0.596-0.346,1-0.984,1-1.723c0-1.104-0.895-2-2-2C6.896,1,6,1.896,6,3c0,0.738,0.405,1.376,1,1.722v2.042c0,0.138-0.112,0.25-0.25,0.25c0,0-2.138,0-3.5,0S1,7.932,1,9.266c0,0.521,0,1.277,0,2.012c-0.595,0.353-1,0.984-1,1.729c0,1.104,0.896,2,2,2s2-0.896,2-2c0-0.732-0.405-1.377-1-1.729V9.266c0-0.139,0.112-0.25,0.25-0.25h3.536C6.904,9.034,7,9.126,7,9.25v2.027C6.405,11.624,6,12.26,6,13c0,1.104,0.896,2,2.002,2c1.105,0,2-0.896,2-2c0-0.738-0.404-1.376-1-1.723V9.25c0-0.124,0.098-0.216,0.215-0.234h3.535c0.137,0,0.25,0.111,0.25,0.25v2.012c-0.596,0.353-1,0.984-1,1.729c0,1.104,0.896,2,2,2s2-0.896,2-2C16.002,12.262,15.598,11.623,15.002,11.277z"/></svg>

After

Width:  |  Height:  |  Size: 842 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M8.184 7.928l-1.905 1.983-3.538-2.436 4.714-4.713 2.623 3.183-1.894 1.983zm-1.746-7.523c-.236-.416-.803-.649-1.346.083-.259.349-4.727 4.764-4.91 4.983-.182.218-.294.721.044.976.34.258 5.611 3.933 5.611 3.933l-.225.229c.7.729.816.854 1.046.863.75.016 2.035-1.457 2.578-.854.541.604 3.537 3.979 3.537 3.979.51.531 1.305.559 1.815.041.521-.521.541-1.311.025-1.848 0 0-2.742-2.635-3.904-3.619-.578-.479.869-2.051.854-2.839-.008-.238-.125-.361-.823-1.095l-.22.243c0 .003-3.846-4.659-4.082-5.075z"/></svg>

After

Width:  |  Height:  |  Size: 587 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#000000" d="M6.722 11.291l.451-.17-.165-.32c-.536-1.039-.685-1.934-.761-2.672-.082-.808-.144-2.831 1.053-4.189.244-.278.493-.493.743-.675.012-.826-.135-1.766-.646-2.345-.618-.7-1.4-.787-1.4-.787l-.497-.055-.498.055s-.783.087-1.398.787c-.617.702-.717 1.948-.625 2.855.06.583.17 1.263.574 2.05.274.533.341.617.355 1.01.022.595.027 1.153-.671 1.538-.697.383-1.564.508-2.403 1.088-.596.41-.709 1.033-.78 1.459l-.051.41c-.029.273.173.498.448.498h5.012c.457-.24.89-.402 1.259-.537zM5.064 15.096c.07-.427.184-1.05.78-1.46.838-.581 1.708-.706 2.404-1.089.699-.385.693-.943.672-1.537-.014-.393-.08-.477-.354-1.01-.406-.787-.515-1.467-.576-2.049-.093-.909.008-2.155.625-2.856.615-.7 1.398-.787 1.398-.787l.492-.055h.002l.496.055s.781.087 1.396.787c.615.701.72 1.947.623 2.855-.062.583-.172 1.262-.571 2.049-.271.533-.341.617-.354 1.01-.021.595-.062 1.22.637 1.604.697.385 1.604.527 2.438 1.104.923.641.822 1.783.822 1.783-.022.275-.269.5-.542.5h-9.991c-.275 0-.477-.223-.448-.496l.051-.408z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M11.002 11v2.529c0 .275-.225.471-.5.471h-3c-.827 0-1.112-.754-.604-1.316l.81-.725c.509-.562.513-1.461-.097-2.01-.383-.344-1.027-.728-2.111-.728s-1.727.386-2.109.731c-.609.549-.606 1.45-.097 2.015l.808.717c.509.562.223 1.316-.602 1.316h-3c-.276 0-.5-.193-.5-.471v-9.029c0-.276.224-.5.5-.5h3c.825 0 1.111-.768.602-1.332l-.808-.73c-.51-.563-.513-1.465.097-2.014.382-.344 1.025-.731 2.109-.731s1.728.387 2.111.731c.608.548.606 1.45.097 2.014l-.81.73c-.509.564-.223 1.332.603 1.332h3c.274 0 .5.224.5.5v2.5c0 .825.642 1.111 1.233.602l.771-.808c.599-.51 1.547-.513 2.127.097.364.383.772 1.025.772 2.109s-.408 1.727-.771 2.109c-.58.604-1.529.604-2.127.097l-.77-.808c-.593-.509-1.234-.223-1.234.602z"/></svg>

After

Width:  |  Height:  |  Size: 787 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M8.002 1c-3.868 0-7.002 3.134-7.002 7s3.134 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm3 5c0 .551-.16 1.085-.477 1.586l-.158.22c-.07.093-.189.241-.361.393-.168.148-.35.299-.545.447l-.203.189-.141.129-.096.17-.021.235v.63h-2.001v-.704c.026-.396.078-.73.204-.999.125-.269.271-.498.439-.688l.225-.21-.01-.015.176-.14.137-.128c.186-.139.357-.277.516-.417l.148-.18c.098-.152.168-.323.168-.518 0-.552-.447-1-1-1s-1.002.448-1.002 1h-2c0-1.657 1.343-3 3.002-3 1.656 0 3 1.343 3 3zm-1.75 6.619c0 .344-.281.625-.625.625h-1.25c-.345 0-.626-.281-.626-.625v-1.238c0-.344.281-.625.626-.625h1.25c.344 0 .625.281.625.625v1.238z"/></svg>

After

Width:  |  Height:  |  Size: 709 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#000000" d="M14.416 11.586l-.01-.008v-.001l-5.656-5.656c.15-.449.252-.921.252-1.421 0-2.485-2.016-4.5-4.502-4.5-.505 0-.981.102-1.434.255l2.431 2.431-.588 2.196-2.196.588-2.445-2.445c-.162.464-.268.956-.268 1.475 0 2.486 2.014 4.5 4.5 4.5.5 0 .972-.102 1.421-.251l5.667 5.665c.781.781 2.047.781 2.828 0s.781-2.047 0-2.828z"/></svg>

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#004875" d="M2.611 5.393c-.17-.216-.084-.393.191-.393h10.397c.275 0 .361.177.191.393l-5.08 6.464c-.17.216-.452.216-.622 0l-5.077-6.464z"/></svg>

After

Width:  |  Height:  |  Size: 220 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#004875" d="M13.391 10.607c.17.216.084.393-.191.393h-10.398c-.275 0-.361-.177-.191-.393l5.08-6.464c.17-.216.45-.216.62 0l5.08 6.464z"/></svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#0074bd" d="M7.951 7.645c-.193.196-.193.516 0 .71l3.258 3.29c.193.193.191.519-.002.709l-1.371 1.371c-.193.192-.512.191-.707 0l-5.335-5.371c-.194-.194-.194-.514 0-.708l5.335-5.369c.195-.195.514-.195.707-.001l1.371 1.371c.193.194.195.513.002.709l-3.258 3.289z"/></svg>

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#0074bd" d="M8.053 8.355c.193-.195.193-.517 0-.711l-3.26-3.289c-.193-.195-.192-.514.002-.709l1.371-1.371c.194-.194.512-.193.706.001l5.335 5.369c.195.195.195.515 0 .708l-5.335 5.37c-.194.192-.512.193-.706.002l-1.371-1.371c-.194-.195-.195-.514-.002-.709l3.26-3.29z"/></svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#008ee6" d="M2.611 5.393c-.17-.216-.084-.393.191-.393h10.397c.275 0 .361.177.191.393l-5.08 6.464c-.17.216-.452.216-.622 0l-5.077-6.464z"/></svg>

After

Width:  |  Height:  |  Size: 220 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#008ee6" d="M13.391 10.607c.17.216.084.393-.191.393h-10.398c-.275 0-.361-.177-.191-.393l5.08-6.464c.17-.216.45-.216.62 0l5.08 6.464z"/></svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#333333" d="M3.8 5.4c-.165-.22-.075-.4.2-.4h8.002c.275 0 .365.18.199.4l-3.898 5.2c-.166.221-.436.221-.6 0l-3.903-5.2z"/></svg>

After

Width:  |  Height:  |  Size: 202 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#333333" d="M10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3A1.5 1.5 0 0 1 10.5 9zm-8 0h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3A1.5 1.5 0 0 1 2.5 9zm8-8h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3A1.5 1.5 0 0 1 10.5 1zm-8 0h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3A1.5 1.5 0 0 1 2.5 1z"/></svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#333333" d="M7 15a1.5 1.5 0 0 1 0-3h7a1.5 1.5 0 0 1 0 3H7zm-4.5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM7 9.5a1.5 1.5 0 0 1 0-3h7a1.5 1.5 0 0 1 0 3H7zm-4.5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM7 4a1.5 1.5 0 0 1 0-3h7a1.5 1.5 0 0 1 0 3H7zM2.5 4a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/></svg>

After

Width:  |  Height:  |  Size: 366 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#424242" d="M14.648 12.788l-4.23-4.228c.525-.855.834-1.858.834-2.938 0-3.105-2.52-5.624-5.627-5.624-3.106.002-5.625 2.521-5.625 5.627 0 3.105 2.519 5.625 5.625 5.625 1.076 0 2.08-.309 2.936-.832l4.229 4.229c.194.195.515.195.707 0l1.151-1.146c.194-.2.194-.519 0-.713zm-13.35-7.163c0-2.39 1.938-4.327 4.327-4.327 2.391 0 4.328 1.937 4.328 4.327 0 2.391-1.936 4.327-4.328 4.327-2.39 0-4.327-1.936-4.327-4.327z"/></svg>

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#505050" d="M14.648 12.788l-4.23-4.228c.525-.855.834-1.858.834-2.938 0-3.105-2.52-5.624-5.627-5.624-3.106.002-5.625 2.521-5.625 5.627 0 3.105 2.519 5.625 5.625 5.625 1.076 0 2.08-.309 2.936-.832l4.229 4.229c.194.195.515.195.707 0l1.151-1.146c.194-.2.194-.519 0-.713zm-13.35-7.163c0-2.39 1.938-4.327 4.327-4.327 2.391 0 4.328 1.937 4.328 4.327 0 2.391-1.936 4.327-4.328 4.327-2.39 0-4.327-1.936-4.327-4.327z"/></svg>

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5181C6" d="M8.002 1c-3.869 0-7.002 3.134-7.002 7s3.133 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm4.459 6.336l-4.105 4.105c-.196.189-.515.189-.708 0l-4.107-4.105c-.194-.194-.194-.513 0-.707l.977-.978c.194-.194.513-.194.707 0l2.422 2.421c.192.195.513.195.708 0l2.422-2.42c.188-.194.512-.194.707 0l.977.977c.193.194.193.513 0 .707z"/></svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5181C6" d="M8.002 1c-3.867 0-7.002 3.134-7.002 7s3.135 7 7.002 7 7-3.134 7-7-3.133-7-7-7zm4.462 8.37l-.979.979c-.19.19-.516.19-.707 0l-2.422-2.419c-.196-.194-.515-.194-.708 0l-2.423 2.417c-.194.193-.513.193-.707 0l-.977-.976c-.194-.194-.194-.514 0-.707l4.106-4.106c.193-.194.515-.194.708 0l4.109 4.105c.19.192.19.513 0 .707z"/></svg>

After

Width:  |  Height:  |  Size: 410 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#5181C6" d="M14.545 3.042l-1.586-1.585c-.389-.389-1.025-.389-1.414 0l-1.293 1.293 3 3 1.293-1.293c.389-.389.389-1.026 0-1.415z"/><rect fill="#5181C6" x="5.129" y="3.8" transform="matrix(-.707 -.707 .707 -.707 6.189 20.064)" width="4.243" height="9.899"/><path fill="#5181C6" d="M.908 14.775c-.087.262.055.397.316.312l2.001-.667-1.65-1.646-.667 2.001z"/></g></svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5181C6" d="M2.611 5.393c-.17-.216-.084-.393.191-.393h10.397c.275 0 .361.177.191.393l-5.08 6.464c-.17.216-.452.216-.622 0l-5.077-6.464z"/></svg>

After

Width:  |  Height:  |  Size: 220 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5181C6" d="M13.391 10.607c.17.216.084.393-.191.393h-10.398c-.275 0-.361-.177-.191-.393l5.08-6.464c.17-.216.45-.216.62 0l5.08 6.464z"/></svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 9"><path fill="none" stroke-width="1.5" d="M1 1l6 6 6-6" stroke="#545560"/></svg>

After

Width:  |  Height:  |  Size: 137 B

1
core/misc/icons/545560/ex.svg Executable file
View File

@@ -0,0 +1 @@
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.344 2.343l11.313 11.313M2.344 13.657L13.657 2.343" stroke="#55565B" stroke-width="3"/></svg>

After

Width:  |  Height:  |  Size: 179 B

View File

@@ -0,0 +1 @@
<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m11.02 6.064c-2.287.164-4.788 1.165-6.58 2.634-1.741 1.427-3.084 3.366-3.786 5.466-.852 2.547-.853 5.12-.006 7.656 1.506 4.503 5.535 7.679 10.302 8.119.884.082 13.216.082 14.1 0 5.287-.488 9.574-4.301 10.683-9.502.649-3.043.026-6.328-1.707-8.989a11.927 11.927 0 0 0 -9.157-5.386c-.977-.071-12.861-.069-13.849.002m14.422 2.542c4.167.683 7.319 3.848 7.953 7.984.165 1.079.088 2.688-.182 3.75-.944 3.727-4.045 6.501-7.923 7.088-.789.12-13.787.12-14.58.001-3.514-.53-6.376-2.828-7.627-6.126-.631-1.664-.746-3.857-.295-5.645.918-3.647 3.936-6.404 7.712-7.047.692-.118 14.227-.122 14.942-.005m-2.702 2.548c-2.256.498-3.999 2.206-4.569 4.476-.156.618-.219 2.389-.115 3.18.4 3.027 2.917 5.25 5.944 5.25a5.87 5.87 0 0 0 4.37-1.894 6.1 6.1 0 0 0 1.576-3.415c.1-.847.038-2.503-.117-3.121-.446-1.782-1.586-3.196-3.219-3.994-.879-.43-1.377-.546-2.46-.573-.72-.017-1.002.001-1.41.091" fill="#55565b"/></svg>

After

Width:  |  Height:  |  Size: 985 B

View File

@@ -0,0 +1 @@
<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m21.0909 34-4.3273-5.4634h-8.03633c-.40988 0-.80297-.1645-1.0928-.4572-.28983-.2928-.45265-.6898-.45265-1.1038v-17.00994c0-.414.16282-.81104.45265-1.10378s.68292-.4572 1.0928-.4572h24.72723c.4099 0 .803.16446 1.0928.4572.2899.29274.4527.68978.4527 1.10378v17.00994c0 .414-.1628.811-.4527 1.1038-.2898.2927-.6829.4572-1.0928.4572h-8.0363zm2.8421-8.5854h7.9761v-13.888h-21.6364v13.888h7.9761l2.8421 3.5872zm-21.38755-23.4146h26.27275v3.12195h-24.72729v17.17075h-3.09091v-18.73172c0-.414.16282-.81104.45265-1.10378s.68292-.4572 1.0928-.4572z" fill="#55565b"/></svg>

After

Width:  |  Height:  |  Size: 654 B

View File

@@ -0,0 +1 @@
<svg height="33" viewBox="0 0 36 33" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m7.08 1.44v1.44h-5.976l-.318.158a1.326 1.326 0 0 0 -.726.941c-.048.224-.061 3.678-.048 12.311l.018 12 .131.246c.073.135.228.329.345.432.448.393-.104.373 9.978.372l9.14-.002.288.346c.479.574 1.348 1.362 1.936 1.755a9.006 9.006 0 0 0 8.182.98c4.629-1.704 7.072-6.881 5.452-11.555-.939-2.711-3.044-4.81-5.725-5.709l-.627-.211-.03-5.537-.03-5.537-.133-.249c-.162-.303-.513-.603-.797-.682-.125-.035-1.57-.058-3.555-.059h-3.345v-2.88h-2.82v2.88h-8.52v-2.88h-2.82zm18.84 10.912v2.391l-.342.041c-.542.063-1.317.269-1.969.521-2.825 1.095-4.943 3.609-5.613 6.664-.235 1.07-.219 2.683.039 3.936l.04.195h-14.835v-16.14h22.68zm1.185 2.332a2.601 2.601 0 0 1 -.45 0c-.124-.013-.022-.024.225-.024s.349.011.225.024m1.332 3.012c.586.148 1.445.539 1.976.899a6.322 6.322 0 0 1 2.746 5.525c-.079 1.624-.71 3.058-1.845 4.194a5.756 5.756 0 0 1 -1.756 1.24c-.918.435-1.576.581-2.618.583-.585.001-1.008-.03-1.292-.094-2.621-.594-4.532-2.609-4.95-5.219-.107-.664-.045-1.976.121-2.594.636-2.361 2.568-4.177 4.912-4.62.665-.125 2.042-.081 2.706.086m-2.563 5.119.016 3.255 2.415.016 2.415.015v-1.859l-1.605-.016-1.605-.016-.016-2.325-.015-2.325h-1.62z" fill="#55565b"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m18.905 3.0034c-4.8818-.0181-9.89368 2.11996-12.75421 6.14588-2.97685 4.12852-3.72724 9.57882-2.74182 14.50242.69215 3.2862 2.61415 6.3919 5.55652 8.109 3.35621 2.0297 7.44861 2.3335 11.27361 1.8957 1.9198-.2327 3.8097-.7456 5.5656-1.549 0-1.2133 0-2.4267 0-3.64-3.9461 1.6362-8.4574 2.4333-12.6514 1.2587-2.9465-.8305-5.34152-3.3703-5.98961-6.3624-.77366-3.0458-.58571-6.3211.39477-9.2927 1.05002-2.9697 3.32674-5.53882 6.31624-6.61934 2.9829-1.16767 6.4097-1.27462 9.4541-.26651 2.7133.99524 4.9245 3.33755 5.6015 6.14525.7038 2.5698.6228 5.4088-.3714 7.8826-.4383 1.0424-1.4289 2.1055-2.6643 1.867-.6836-.1102-1.2174-.6898-1.2841-1.374-.3646-1.7236.0832-3.4856.0543-5.2278.0939-1.7622.1876-3.5244.2846-5.2865-2.7816-.8329-5.7863-1.36856-8.6563-.6962-2.9057.7966-5.1346 3.4114-5.6209 6.3736-.4246 2.2055-.2402 4.5877.7799 6.5936.9431 1.7193 2.7689 2.9433 4.7485 3.0192 2.1842.205 4.5109-.7068 5.752-2.5513.808 1.8442 2.9703 2.8932 4.9355 2.5197 2.3445-.3217 4.2363-2.1564 5.0624-4.3086 1.3658-3.1906 1.3042-6.8642.3573-10.1616-1.129-3.63941-3.9388-6.75356-7.5656-8.02092-1.8577-.69892-3.8521-.9948-5.8372-.95578zm-.2305 10.5789c.7719-.0025 1.547.0602 2.296.2236-.2194 2.5144.0496 5.147-.9169 7.5287-.4626 1.006-1.4737 1.788-2.6009 1.773-1.18.1157-2.4907-.5777-2.7663-1.7944-.5272-1.6144-.3716-3.4013.1106-5.0038.5405-1.4722 1.9158-2.6924 3.5363-2.7087.1134-.0098.2273-.016.3412-.0184z" fill="#55565b"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m3.87 1.163c-.361.17-.581.394-.745.757-.122.269-.122.303-.123 16.08l-.001 15.81.141.303c.166.355.54.699.87.797.173.052 3.612.07 13.532.07 14.778 0 13.513.037 13.978-.408.128-.122.282-.33.344-.462.107-.232.111-.689.113-12.93l.001-12.69-3.735-3.735-3.735-3.735-10.17.001h-10.17zm19.11 5.857v3h6v21.96h-22.98v-27.96h16.98zm-9.215 11.981-3.703 3.949 1.959.016 1.959.016v5.998h7.02v-5.998l1.969-.016 1.968-.016-3.684-3.93c-2.027-2.162-3.707-3.938-3.734-3.949-.028-.01-1.717 1.759-3.754 3.93" fill="#55565b"/></svg>

After

Width:  |  Height:  |  Size: 601 B

Some files were not shown because too many files have changed in this diff Show More