-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make HTMLSlotElement.assign()
accept sequence
#7321
Comments
I don't think this is compelling; the spread operator is a fine language feature and something we designed around; there's no need to change APIs to make it "voluntary". We chose a variadic design because most cases benefit from a known selection of nodes being slotted in, with an array being the exception. For the exceptional case you can afford to type the extra three characters. |
What is the (future) use-cases for
|
On language and syntax. My point is not a general assessment of the spread operator. That's fine! I even find it elegant... and functional... And I am sure that most JS experts in this forum feels the same way :) My point is: is the spread operator "fine" for the beginner? Is average Joe JS developer comfortable with it? Will he use it? will he understand it when he uses it? And if not, will it cause poor Joe unnecessary frustration? will it cause bugs? will it make Joe give up on his first attempts to make web components, quit JS, and thereby rob the world of that 100th version of that same web component that would have sequestered carbon? I think there is a proverb that says something along the lines of 'idiots make systems for experts - experts make systems for idiots'. I don't know if a variadic |
Is there evidence that spread is a major point of confusion or particularly advanced? When I've worked with / helped train new JS devs, it's not stood out as a pitfall - if anything it seems like one of the bits folks pick up on their own. |
Spread also matches precedent in the DOM Standard for APIs such as We should just update the documentation. cc @whatwg/documentation |
Thank you both for pointing out a) the need for evidence and b) that
|
To be clear, the DOM Standard also uses this for We also cannot take away the use of the spread operator at this point as this feature has shipped in multiple implementations iirc. |
Not sure if there are more related to the DOM, but it may be worth noting that JS itself, all platform APIs aside, includes variadic functions out of the box, e.g. String.fromCodePoint. Some of these, like Math.min / Math.max, would tend to be pretty inconvenient without spreading. Template tags are inherently variadic, too. (Perhaps it's because I didn't learn JS until the ES2015+ era, but |
But. They still pass the voluntary test, no? The use of And. This same voluntary test can be applied to let min = numbers[0];
for (let n of numbers)
min = Math.min(min, n); That is not the case with |
No, if you use |
It's also worth noting that the "voluntary test" is something you created, and not part of how we design APIs. We instead prefer values like consistency. |
Hehe, I am not exactly reassured by hearing that the Consistency is precisely my issue, thanks:) If all other uses of the DOM do not require the use of the spread operator, then the My argument is that we should keep consistency in what is being required knowledge of developers. And that when all else is being equal, we should keep "required knowledge" as low as possible considering those less fortunate. |
I'm not sure what to tell you, but insert, remove, and replace all children, are all important primitives of node trees. You are welcome to disagree, but that won't change the facts. As is insertion of a |
Thanks! And no, I didn't know that And again... sorry... the for(let n of nodes)
this.shadowRoot.append(n); However. You are unsure of what you can tell me. So I should maybe be more direct in what response I would like from whatwg representatives on this issue:
|
But I would like to make one request. Can some insiders from whatwg try to argue both sides of this issue? I feel that the discussion has taken on an outsider vs. insiders dynamic, and I am not sure if that is helpful for the issue... I make this request based on my assumption that if |
This may have an answer in an earlier comment:
(I.E. optimizing for cases like
FWIW, not everybody in the convo is WHATWG - I'm a fellow non-WHATWG web dev who's interested in standards. I hope the discussion hasn't seemed alienating. It can be tricky when an argument being presented fundamentally rests on a belief - in this case, something like "spread isn't an ordinary, everyday part of js" - which other folks likely don't share.
As far as I'm aware, WHATWG doesn't relitigate / issue secondary verdicts on new JS syntax after TC39 standardizes it. The closest it gets is probably that leveraging some features can imply updates to Web IDL and this in turn might mean constraining them in some manner. |
Ok. Describing use-cases for the imperative slotting API part 2. In 99% of the use-cases where you in slot.assign(...[...this.childNodes].filter(some=>thing));
//or
slot.assign(...this.querySelectorAll(":scope > li")); This is the listOf- In the use-cases where you in slot.assign(this.children[0]);
//or
slot.assign(this.querySelector(":scope > lh")); This is the Now, ask yourself, if somebody put a gun to your head and said: "you have to choose between default The problem is that the earlier comment you are referring to holds the use-cases in reverse priority. "most cases" refers to the imperative equivalent of But. I don't know. I am not sure that the real issue here is the use cases. First, I am an optimist. I interpret the silence from the other participants on the topic of use case prevalence as "silently not-disagreeing anymore". But, second, I think the main problem lies elsewhere, namely in the structure of |
What I am relitigating here, is not the use of spread. I heart spread... I am relitigating The real question here is what does I state that A correct implementation of the variadic pattern would do no state changes outside the inner iteration. Functions that behave like So, even if we make the awful assumptions that we don't care about developer's freedom of choice and the misguided assumption that |
Imho, primitives are there to serve a purpose, and consistency is, rightly so, a priority. The freedom we all have with JS is that this whole debate can be solved via: const assign = (slot, sequence) => slot.assign(...sequence); and new comers are just developers that need to learn more JS, not people to accommodate with their lack of knowledge forever. There is const arr = [1, 2, 3];
// this works "like assign"
arr.splice(0, arr.length, ...[4, 5, 6]);
// this doesn't do the same at all
arr.splice(0, arr.length, [4, 5, 6]); plus I understand the documentation gotcha, and indeed that should be fixed, but I wouldn't consider the current variadic signature an issue, quite the opposite, it reasons well with all others. |
const sequence = [1,2,3];
const deleteCount = 1;
const position = 1;
const target1 = [1,2,3];
const target2 = [1,2,3];
const target3 = [1,2,3];
//nice `apply` way
target1.splice(position, deleteCount, ...sequence);
//the verbose literal, `call` way to expose the conceptual variadic inner loop of splice
for (let i = 0; i < sequence.length; i++) {
if(i === 0){
target2.splice(position, deleteCount);
} else {
target2.splice(position, 0);
}
target2.splice(position + i, 0, sequence[i]);
}
//the more normal `call` way to get the same output, that would also be a more likely way to implement `splice()`
target3.splice(position, deleteCount);
for (let i = 0; i < sequence.length; i++)
target3.splice(position + i, 0, sequence[i]);
console.log(target1, target2, target3); What can we learn from And. The second parameter I do not think So. There is something even stranger with |
I have used Asking for an API that brand check if the first argument is iterable, makes list of iterables "impossible to deal with", so it's also bad as generic signature ... and sticking with
|
Just learning |
Yes, the Actually, you are bringing up the flatten problem from two different angles.. The "impossible to deal with" feedback on the polymorphic alternative has to do with flattening too, no? And your critique here resonates with me too: I too feel that if you have a variadic function, and that function iterates recursively into nested sequences (as
Agree. A variadic |
also consider the previously mentioned fragment scenario, which also adds potentially already unexpected behavior in the mix, because it can contain various nodes in between, even as single non-sequence entry. As summary: brand check for iterable arguments are not the way to go. Slower, proven legacy mistake in JS, promote ambiguous cases/scenarios, instead of promoting more pure signatures. I am not sure we're moving forward, but I am sure the more examples we talk about, the more this proposal looks undesired, not just not-so-compelling. |
Ahh. I see the misunderstanding. And I am sorry, it is my fault. If I could start from scratch, I also would prefer a variadic version of //good variadic `assign()`/`unAssign()`, echoes `.append()` and `.removeChild()`
class HTMLSlotElement {
assign(...nodes) {
for (let n of nodes) {
this.#listOfAssigned.push(n);
n.#assignedSlot = this;
}
}
unAssign(...nodes){
for (let n of nodes) {
const pos = this.#listOfAssigned.indexOf(n);
if(pos === -1) throw new Error("probably shouldn't just ignore this");
this.#listOfAssigned.splice(pos, 1);
delete n.#assignedSlot;
}
}
replaceAssignedNodesPrimitive(newNodes){
this.unAssign(...this.#listOfAssigned);
this.assign(...newNodes);
}
}
//then, inside the function that reacts to slottable children changes, you could do the following
class WebComponent extends HTMLElement {
#slot1;
#slot2;
//bla bla bla
callbackWhenYouNeedToCheckAndAssignChildNodesFromTheHostNode(){
this.#slot1.unAssign(...this.#slot1.assignedNodes());
this.#slot2.unAssign(...this.#slot2.assignedNodes());
const lhs = [...this.children].filter(n => n.tagName === 'LH');
const lis = [...this.children].filter(n => n.tagName === 'LI');
this.#slot1.assign(...lhs);
this.#slot2.assign(...lis);
}
} And. About the initial proposal that you call "Brand check" and that I call polymorphic. I am not hung up on that. I propose one way to solve the bad variadic |
I should also probably explain how this good First, the obvious:
Then, the "can it be callbackWhenYouNeedToCheckAndAssignChildNodesFromTheHostNode() {
for (let n of this.#slot1.assignedNodes())
this.#slot1.unAssign(n);
const lis = [...this.children].filter(n => n.tagName === 'LI');
for (let n of lis)
this.#slot2.assign(n);
} Why does the bad
And then things start to get problematic..
This "corner" (ie. you must use And, as the "good" variadic examples shows, it is completely unnecessary. |
Why
|
Let's be clear, we cannot change the existing API shape. It has shipped. If you find traction for your argument with TC39, it might be worth revisiting this to some extent, but that seems unlikely to me. It's also clearly not a 1000 vs 1. There's many functions for which I created mdn/content#10533 to get the documentation fixed. |
Arguing the point seems pointless :) So instead I spent the weekend analyzing the problem and explaining it in a tutorial. And, and this is just for the record.
I 100% agree.
Nope. 2 of ~1001. Afaik / shown in this discussion. So here I 999/1001 disagree. |
That's only correct if you pretend that (DOM) |
Yes. You got me! That is EXACTLY what I'm pretending :) I will go even further and say that untill this morning I still 100% believed that If you want, please let this issue and me personally be the anecdotal evidence that at least one obviously stupid guy exists that a) made this inference and b) were unable to shake it, even after being shown contradictory examples and while checking his assumptions for fear of embarrassment. No problem, I volunteer :D But, what I am trying to get an answer to is why this should not be considered a "wat?!"? Ok. You show me 666 white swans. Then you ask me, what color has swan 667? I say white. You say nope. Not exactly. It is only all white when you look at it normally, superficially. You see, if you lift the wing of swan 667 (and swans 668-672 btw), then you will see that we have painted the feathers on the underside of the wing in a slightly greyish color by making a slightly different MutationRecord and calling the connectedCallback() against a DOM that looks different (not your words, but my current understanding). When you then ask me what color swan 673 will have, you can literally see the rainbow in my brain shine through my eyes. However, even if we accept this as a "good pattern", which I don't, even then First. Let's say that there are 5-10 other variadic DOM methods like Second, we have ugly duckling Third, There seems to be no obvious reason to make I really can't understand: what needs to happen behind the scenes/or in the anticipated use-cases that makes designing |
I personally believe that the burden of justification should be on the other side of this argument. I think that the change that you are proposing is something like this: """ Now, we would like to change that. We like the new Therefore, we are proposing that from now on we are going to produce API functions to solve future use-cases where you must use the Yes, there are variations around these functions that would allow for both Here is our justification for breaking "JS functions can always be |
I propose to make the
HTMLSlotElement.assign()
method accept a sequence of nodes as its first argument, preferably as a polymorphic feature that coexists with the current variadic design.Background
HTMLSlotElement.assign()
method as a variadic function withNode
parameters. Both Firefox and Chrome implement this behavior according to spec.HTMLSlotElement.assign()
as a function accepting one argument with a sequence ofNode
s. (I think earlier implementations of Chrome also did this?)Many use-cases must pass a sequence of nodes to
HTMLSlotElement.assign()
. For example, if you build a web component for a custom<ol>
, you will do something like this:slot.assign(...[...this.children].filter(el=>el.tagName === 'LI'))
orslot.assign(...this.querySelectorAll(":scope > li"))
.The same goes for a custom variant of
<tr>/<td>
,<details>/..
,<select>/<option>
, etc. With the current spec, this requires either...
or reflectionFunction.apply(...)
.The current variadic
HTMLSlotElement.assign(...nodes)
method looks good for experienced JS developers and works well. The (old/MDN/sequence) version ofHTMLSlotElement.assign([nodes])
also looked good and worked well.Why change?
Here are my assumptions:
[]
before "more advanced" JS syntax such as variadic functions and the...
rest operator.HTMLSlotElement.assign()
and for them the difference between variadic parameters and a single sequence parameter is insignificant.HTMLSlotElement.assign()
for whom the variadic parameters and/or rest operator will be unfamiliar and for whom this "more advanced" syntax can cause uncertainty, frustration and errors. Thus, for these "more normal" JS developers, there is a significant difference between the two alternative signatures.IMHO, the argument boils down to:
options
argument in the future?HTMLSlotElement.assign
be? Will users ofHTMLSlotElement.assign()
be 342 "more normal literal array guys" vs. 5 million "more advanced spread guys"? Or will 5 million users ofHTMLSlotElement.assign()
be uncomfortable with passing sequences to variadic functions using the spread operator?Suggestion for solution:
I personally like the variadic API. I just think that the use of the spread operator should be voluntary for a platform function's main uses. One alternative approach that would accomplish voluntariness would be to make
HTMLSlotElement.assign()
polymorphic: iff the first argument is a sequence, thenHTMLSlotElement.assign()
would work as MDN describe, otherwise run as the spec describes. (illustrated in the below monkey patch).The text was updated successfully, but these errors were encountered: