@optionfactory/fml 9.0.0-rc7 → 9.0.0-rc8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/custom-elements.json +18 -4
- package/dist/fml.css +1 -1
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +151 -21
- package/dist/fml.iife.js.map +1 -1
- package/dist/fml.iife.min.js +1 -1
- package/dist/fml.iife.min.js.map +1 -1
- package/dist/ful.css +1 -1
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +20 -1
- package/dist/ful.iife.js +151 -21
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +151 -21
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +9 -1
- package/dist/web-types.json +21 -5
- package/package.json +1 -1
package/dist/ful.iife.js
CHANGED
|
@@ -4904,8 +4904,15 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4904
4904
|
* `ful-dialog-body` and `ful-dialog-footer` match at any depth, which is what a
|
|
4905
4905
|
* dialog whose content is wrapped in a form needs.
|
|
4906
4906
|
*/
|
|
4907
|
+
/**
|
|
4908
|
+
* How a dialog ended: `dismissed` tells a cancel from an answer, `result` carries
|
|
4909
|
+
* the `data-result` of the button that closed it and `response` what a submit
|
|
4910
|
+
* answered with, the one that did not happen being null.
|
|
4911
|
+
* @typedef {{ dismissed: boolean, result: string|null, response: any }} DialogOutcome
|
|
4912
|
+
*/
|
|
4913
|
+
|
|
4907
4914
|
class Dialog extends index_mjs.ParsedElement {
|
|
4908
|
-
static attributes = ['header', 'requires-answer:presence'];
|
|
4915
|
+
static attributes = ['header', 'requires-answer:presence', 'close-on-submit:presence'];
|
|
4909
4916
|
static slots = true;
|
|
4910
4917
|
static template = `
|
|
4911
4918
|
<dialog data-ref="dialog" class="ful-dialog">
|
|
@@ -4913,6 +4920,8 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4913
4920
|
<h2 data-tpl-if="header">{{ header }}</h2>
|
|
4914
4921
|
<button data-tpl-if="!requiresAnswer" type="button" data-ref="close" data-tpl-aria-label="#l10n:t('dialog.close')"><ful-icon name="x-lg" aria-hidden="true"></ful-icon></button>
|
|
4915
4922
|
</header>
|
|
4923
|
+
<section data-ref="loading" hidden><ful-spinner class="centered" role="status"><span class="ful-sr-only">{{ #l10n:t('spinner.loading') }}</span></ful-spinner></section>
|
|
4924
|
+
<section data-ref="error" role="alert" hidden></section>
|
|
4916
4925
|
<div data-ref="body" class="ful-dialog-body">{{{{ slots.default }}}}</div>
|
|
4917
4926
|
<footer class="ful-dialog-footer">
|
|
4918
4927
|
<button type="button" data-ref="acknowledge" data-result="acknowledged" data-tpl-if="!slots.buttons" data-tpl-aria-label="#l10n:t('dialog.acknowledge')">{{ #l10n:t('dialog.acknowledge') }}</button>
|
|
@@ -4922,8 +4931,15 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4922
4931
|
`;
|
|
4923
4932
|
#dialog;
|
|
4924
4933
|
#body;
|
|
4934
|
+
#loading;
|
|
4935
|
+
#error;
|
|
4925
4936
|
#requests = new SectionRequests();
|
|
4937
|
+
#updates = new Claims();
|
|
4926
4938
|
#resolvers = [];
|
|
4939
|
+
//the answer a submit closed the dialog with, which the return value cannot
|
|
4940
|
+
//carry: it is a string, and a response is whatever the server sent
|
|
4941
|
+
/** @type {DialogOutcome|null} */
|
|
4942
|
+
#answer = null;
|
|
4927
4943
|
render({ slots }) {
|
|
4928
4944
|
const requiresAnswer = this.declared('requires-answer');
|
|
4929
4945
|
const fragment = this.template()
|
|
@@ -4931,13 +4947,12 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4931
4947
|
.render();
|
|
4932
4948
|
this.#dialog = fragment.querySelector('[data-ref=dialog]');
|
|
4933
4949
|
this.#body = fragment.querySelector('[data-ref=body]');
|
|
4950
|
+
this.#loading = fragment.querySelector('[data-ref=loading]');
|
|
4951
|
+
this.#error = fragment.querySelector('[data-ref=error]');
|
|
4934
4952
|
this.#dialog.addEventListener('close', () => {
|
|
4935
|
-
this
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
}),
|
|
4939
|
-
);
|
|
4940
|
-
this.#settle();
|
|
4953
|
+
const outcome = this.#outcome();
|
|
4954
|
+
this.dispatchEvent(new CustomEvent('close', { detail: outcome }));
|
|
4955
|
+
this.#settle(outcome);
|
|
4941
4956
|
});
|
|
4942
4957
|
this.#dialog.addEventListener('click', (/** @type any */ e) => {
|
|
4943
4958
|
const result = e.target.closest('button[data-result]')?.dataset.result;
|
|
@@ -4945,11 +4960,26 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4945
4960
|
this.#dialog.close(result);
|
|
4946
4961
|
}
|
|
4947
4962
|
});
|
|
4948
|
-
//dismissal, not an answer: the waiters are settled with
|
|
4949
|
-
//Optional because a subclass overriding the template owns
|
|
4963
|
+
//dismissal, not an answer: the waiters are settled with a dismissal, as
|
|
4964
|
+
//Escape does. Optional because a subclass overriding the template owns
|
|
4965
|
+
//what it renders
|
|
4950
4966
|
fragment
|
|
4951
4967
|
.querySelector('[data-ref=close]')
|
|
4952
4968
|
?.addEventListener('click', () => this.#dialog.close(''));
|
|
4969
|
+
if (this.declared('close-on-submit')) {
|
|
4970
|
+
//delegated on the body rather than bound to the form, so a body
|
|
4971
|
+
//delivered later by update() is covered by the same listener. The
|
|
4972
|
+
//form must be the body's own: a ful-table wraps its filters in a
|
|
4973
|
+
//ful-form of its own, and a search in a table the dialog holds is
|
|
4974
|
+
//not the dialog being answered
|
|
4975
|
+
this.#body.addEventListener('submit:success', (/** @type any */ e) => {
|
|
4976
|
+
if (e.target !== index_mjs.Nodes.queryChildren(this.#body, 'ful-form')) {
|
|
4977
|
+
return;
|
|
4978
|
+
}
|
|
4979
|
+
this.#answer = { dismissed: false, result: null, response: e.detail.response };
|
|
4980
|
+
this.#dialog.close('submitted');
|
|
4981
|
+
});
|
|
4982
|
+
}
|
|
4953
4983
|
if (requiresAnswer) {
|
|
4954
4984
|
//the platform's own dismissal, refused where the dialog must be
|
|
4955
4985
|
//answered: cancel fires for Escape and for a close request the
|
|
@@ -4959,39 +4989,96 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4959
4989
|
this.replaceChildren(fragment);
|
|
4960
4990
|
wireTargets();
|
|
4961
4991
|
}
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4992
|
+
/**
|
|
4993
|
+
* How the dialog ended, in one shape for every way it can end: `dismissed`
|
|
4994
|
+
* alone tells a cancel from an answer, so a submit answering with no body at
|
|
4995
|
+
* all (a 204) is still an answer, where a bare `null` could not say which it
|
|
4996
|
+
* was. `result` carries the `data-result` of the button that closed it and
|
|
4997
|
+
* `response` what a submit answered with; the one that did not happen is null.
|
|
4998
|
+
*/
|
|
4999
|
+
#outcome() {
|
|
5000
|
+
if (this.#answer) {
|
|
5001
|
+
return this.#answer;
|
|
5002
|
+
}
|
|
5003
|
+
const result = this.#dialog.returnValue;
|
|
5004
|
+
return result === ''
|
|
5005
|
+
? { dismissed: true, result: null, response: null }
|
|
5006
|
+
: { dismissed: false, result, response: null };
|
|
5007
|
+
}
|
|
5008
|
+
//answers every waiter with the dialog's own answer: a dismissal while still
|
|
5009
|
+
//open or closed without a result, which is also the unanswered answer a
|
|
5010
|
+
//dialog leaving the document owes its waiters instead of hanging them
|
|
5011
|
+
#settle(outcome) {
|
|
4966
5012
|
const resolvers = this.#resolvers;
|
|
4967
5013
|
this.#resolvers = [];
|
|
4968
5014
|
for (const resolve of resolvers) {
|
|
4969
|
-
resolve(
|
|
5015
|
+
resolve(outcome);
|
|
4970
5016
|
}
|
|
4971
5017
|
}
|
|
4972
5018
|
disconnectedCallback() {
|
|
4973
|
-
this.#settle();
|
|
5019
|
+
this.#settle(this.#outcome());
|
|
4974
5020
|
}
|
|
4975
5021
|
open() {
|
|
4976
5022
|
return this.ask();
|
|
4977
5023
|
}
|
|
4978
5024
|
ask() {
|
|
4979
|
-
if (
|
|
4980
|
-
this.#
|
|
4981
|
-
this.#dialog.showModal();
|
|
5025
|
+
if (this.#show()) {
|
|
5026
|
+
this.#restChrome();
|
|
4982
5027
|
this.#request();
|
|
4983
5028
|
}
|
|
4984
5029
|
return new Promise((resolve) => {
|
|
4985
5030
|
this.#resolvers.push(resolve);
|
|
4986
5031
|
});
|
|
4987
5032
|
}
|
|
5033
|
+
/**
|
|
5034
|
+
* Opens the dialog and waits for the callback, as `ful-drawer`'s does: a
|
|
5035
|
+
* resolved value paints the body (which is returned), a rejection paints the
|
|
5036
|
+
* problems and travels to the caller, and an update superseded by a newer one
|
|
5037
|
+
* paints nothing. The title is the `header` attribute, configuration like the
|
|
5038
|
+
* rest of the dialog's chrome, so what update() owns is the body alone.
|
|
5039
|
+
*/
|
|
5040
|
+
async update(cb) {
|
|
5041
|
+
//the claim detaches any update still in flight: its outcome belongs to
|
|
5042
|
+
//an abandoned opening and must neither be painted nor own the dialog
|
|
5043
|
+
const claim = this.#updates.take();
|
|
5044
|
+
this.#body.replaceChildren();
|
|
5045
|
+
this.#restChrome();
|
|
5046
|
+
this.#loading?.removeAttribute('hidden');
|
|
5047
|
+
this.#body.setAttribute('hidden', '');
|
|
5048
|
+
//update owns its own open-answer-deliver cycle, so it shows the dialog
|
|
5049
|
+
//without going through ask(): a user reopen during the wait is a real
|
|
5050
|
+
//open and goes through ask()
|
|
5051
|
+
this.#show();
|
|
5052
|
+
try {
|
|
5053
|
+
const delivered = await cb();
|
|
5054
|
+
if (claim.stale) {
|
|
5055
|
+
return this.#body;
|
|
5056
|
+
}
|
|
5057
|
+
this.#body.replaceChildren(delivered);
|
|
5058
|
+
this.#loading?.setAttribute('hidden', '');
|
|
5059
|
+
this.#body.removeAttribute('hidden');
|
|
5060
|
+
return this.#body;
|
|
5061
|
+
} catch (/** @type any */ e) {
|
|
5062
|
+
if (!claim.stale) {
|
|
5063
|
+
//revealed before it is filled, so the live region announces the
|
|
5064
|
+
//change rather than being revealed already holding it
|
|
5065
|
+
this.#error?.removeAttribute('hidden');
|
|
5066
|
+
if (this.#error) {
|
|
5067
|
+
this.#error.textContent = index_mjs$1.Failure.problemsText(e);
|
|
5068
|
+
}
|
|
5069
|
+
this.#loading?.setAttribute('hidden', '');
|
|
5070
|
+
this.#body.setAttribute('hidden', '');
|
|
5071
|
+
}
|
|
5072
|
+
throw e;
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
4988
5075
|
#request() {
|
|
4989
5076
|
this.#requests.request(this, this.#body, null, null)?.catch(() => undefined);
|
|
4990
5077
|
}
|
|
4991
5078
|
/**
|
|
4992
5079
|
* Re-fires section:requested on the body, open or closed: the explicit
|
|
4993
5080
|
* request for a body that wants refreshing. A failed refresh paints its
|
|
4994
|
-
* problems, nothing rejects:
|
|
5081
|
+
* problems, nothing rejects: update() stays the rejecting call.
|
|
4995
5082
|
*/
|
|
4996
5083
|
refresh() {
|
|
4997
5084
|
return this.#requests.request(this, this.#body, null, null)?.then(undefined, () => undefined);
|
|
@@ -4999,6 +5086,25 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
4999
5086
|
close(result) {
|
|
5000
5087
|
this.#dialog.close(result ?? '');
|
|
5001
5088
|
}
|
|
5089
|
+
/** Shows the modal, answering whether this call is the one that opened it. */
|
|
5090
|
+
#show() {
|
|
5091
|
+
if (this.#dialog.open) {
|
|
5092
|
+
return false;
|
|
5093
|
+
}
|
|
5094
|
+
//an opening owes nothing to the one before it: the platform keeps
|
|
5095
|
+
//returnValue across a close with no result, and the answer a submit
|
|
5096
|
+
//left is just as stale
|
|
5097
|
+
this.#dialog.returnValue = '';
|
|
5098
|
+
this.#answer = null;
|
|
5099
|
+
this.#dialog.showModal();
|
|
5100
|
+
return true;
|
|
5101
|
+
}
|
|
5102
|
+
#restChrome() {
|
|
5103
|
+
this.#error?.replaceChildren();
|
|
5104
|
+
this.#error?.setAttribute('hidden', '');
|
|
5105
|
+
this.#loading?.setAttribute('hidden', '');
|
|
5106
|
+
this.#body?.removeAttribute('hidden');
|
|
5107
|
+
}
|
|
5002
5108
|
}
|
|
5003
5109
|
|
|
5004
5110
|
/**
|
|
@@ -5011,7 +5117,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
5011
5117
|
* it.
|
|
5012
5118
|
*/
|
|
5013
5119
|
class Drawer extends index_mjs.ParsedElement {
|
|
5014
|
-
static attributes = ['title', 'placement'];
|
|
5120
|
+
static attributes = ['title', 'placement', 'close-on-submit:presence'];
|
|
5015
5121
|
static slots = true;
|
|
5016
5122
|
static template = `
|
|
5017
5123
|
<dialog data-ref="dialog" class="ful-drawer">
|
|
@@ -5032,6 +5138,10 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
5032
5138
|
#content;
|
|
5033
5139
|
#requests = new SectionRequests();
|
|
5034
5140
|
#updates = new Claims();
|
|
5141
|
+
//what a submit closed the drawer with, told from a close of any other kind:
|
|
5142
|
+
//a save answering with no body at all is still a save
|
|
5143
|
+
/** @type {{ dismissed: boolean, response: any }|null} */
|
|
5144
|
+
#answer = null;
|
|
5035
5145
|
render({ slots }) {
|
|
5036
5146
|
const fragment = this.template()
|
|
5037
5147
|
.withOverlay({ slots, title: this.declared('title') ?? '' })
|
|
@@ -5047,8 +5157,25 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
5047
5157
|
}
|
|
5048
5158
|
fragment.querySelector('[data-ref=close]').addEventListener('click', () => this.close());
|
|
5049
5159
|
this.#dialog.addEventListener('close', () => {
|
|
5050
|
-
this.dispatchEvent(
|
|
5160
|
+
this.dispatchEvent(
|
|
5161
|
+
new CustomEvent('close', { detail: this.#answer ?? { dismissed: true, response: null } }),
|
|
5162
|
+
);
|
|
5051
5163
|
});
|
|
5164
|
+
if (this.declared('close-on-submit')) {
|
|
5165
|
+
//delegated on the content section rather than bound to the form: a
|
|
5166
|
+
//drawer's form usually arrives with an update() rather than with the
|
|
5167
|
+
//page, and the section outlives every delivery. The form must be the
|
|
5168
|
+
//content's own, a ful-table wrapping its filters in a ful-form of its
|
|
5169
|
+
//own and a search in a table the drawer holds not being the drawer
|
|
5170
|
+
//finishing
|
|
5171
|
+
this.#content.addEventListener('submit:success', (/** @type any */ e) => {
|
|
5172
|
+
if (e.target !== index_mjs.Nodes.queryChildren(this.#content, 'ful-form')) {
|
|
5173
|
+
return;
|
|
5174
|
+
}
|
|
5175
|
+
this.#answer = { dismissed: false, response: e.detail.response };
|
|
5176
|
+
this.close();
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5052
5179
|
this.replaceChildren(fragment);
|
|
5053
5180
|
wireTargets();
|
|
5054
5181
|
}
|
|
@@ -5121,6 +5248,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
5121
5248
|
if (this.#dialog.open) {
|
|
5122
5249
|
return false;
|
|
5123
5250
|
}
|
|
5251
|
+
//an opening owes nothing to the one before it: the answer a submit left
|
|
5252
|
+
//belongs to the drawer that closed on it
|
|
5253
|
+
this.#answer = null;
|
|
5124
5254
|
this.#dialog.showModal();
|
|
5125
5255
|
return true;
|
|
5126
5256
|
}
|