Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: preactjs/preact
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 10.19.4
Choose a base ref
...
head repository: preactjs/preact
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 10.19.5
Choose a head ref
  • 7 commits
  • 12 files changed
  • 3 contributors

Commits on Feb 8, 2024

  1. Setting translate through direct access does not work (#3800)

    * Setting translate through direct access does not work
    
    * add test
    
    * alternative fix
    JoviDeCroock authored Feb 8, 2024
    Copy the full SHA
    72cbd2d View commit details

Commits on Feb 9, 2024

  1. Copy the full SHA
    24e47b4 View commit details
  2. Merge pull request #4276 from novari/patch-1

    Add dpub aria 1.0 role JSX types
    rschristian authored Feb 9, 2024
    Copy the full SHA
    238d580 View commit details

Commits on Feb 14, 2024

  1. Copy the full SHA
    65310c6 View commit details

Commits on Feb 15, 2024

  1. fix: correctly restore _original (#4280)

    * correctly restore _original
    
    * Update test/browser/components.test.js
    JoviDeCroock authored Feb 15, 2024
    Copy the full SHA
    f808dcb View commit details

Commits on Feb 16, 2024

  1. fix: address scenario where we would crash when replacing a matched v…

    …node with null (#4281)
    
    * add test for the issue, TODO convert to class components later
    
    * wip
    
    * correct assertion
    
    * avoid crasshing when replacing existing node
    
    * move test to core
    JoviDeCroock authored Feb 16, 2024
    Copy the full SHA
    2f44635 View commit details
  2. 10.19.5 (#4282)

    JoviDeCroock authored Feb 16, 2024
    Copy the full SHA
    a003d42 View commit details
2 changes: 2 additions & 0 deletions compat/src/render.js
Original file line number Diff line number Diff line change
@@ -143,6 +143,8 @@ function handleDomVNode(vnode) {
// value will be used as the file name and the file will be called
// "true" upon downloading it.
value = '';
} else if (lowerCased === 'translate' && value === 'no') {
value = false;
} else if (lowerCased === 'ondoubleclick') {
i = 'ondblclick';
} else if (
10 changes: 10 additions & 0 deletions compat/test/browser/render.test.js
Original file line number Diff line number Diff line change
@@ -499,6 +499,16 @@ describe('compat render', () => {
expect(updateSpy).to.not.be.calledOnce;
});

it('should support the translate attribute w/ yes as a string', () => {
render(<b translate="yes">Bold</b>, scratch);
expect(scratch.innerHTML).to.equal('<b translate="yes">Bold</b>');
});

it('should support the translate attribute w/ no as a string', () => {
render(<b translate="no">Bold</b>, scratch);
expect(scratch.innerHTML).to.equal('<b translate="no">Bold</b>');
});

it('should support false aria-* attributes', () => {
render(<div aria-checked={false} />, scratch);
expect(scratch.firstChild.getAttribute('aria-checked')).to.equal('false');
2 changes: 1 addition & 1 deletion devtools/src/devtools.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact';

export function initDevTools() {
if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) {
window.__PREACT_DEVTOOLS__.attachPreact('10.19.4', options, {
window.__PREACT_DEVTOOLS__.attachPreact('10.19.5', options, {
Fragment,
Component
});
2 changes: 1 addition & 1 deletion hooks/src/index.js
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ options._diff = vnode => {
};

options._root = (vnode, parentDom) => {
if (parentDom._children && parentDom._children._mask) {
if (vnode && parentDom._children && parentDom._children._mask) {
vnode._mask = parentDom._children._mask;
}

21 changes: 21 additions & 0 deletions hooks/test/browser/useId.test.js
Original file line number Diff line number Diff line change
@@ -456,4 +456,25 @@ describe('useId', () => {
'<div><div>My id is P0-0</div><div>My id is P0-1</div></div>'
);
});

it('should not crash for rendering null after a non-null render', () => {
const Id = () => {
const id = useId();
return <div>My id is {id}</div>;
};

const App = props => {
return (
<div>
<Id />
{props.secondId ? <Id /> : null}
</div>
);
};

render(createElement(App, { secondId: false }), scratch);
expect(scratch.innerHTML).to.equal('<div><div>My id is P0-0</div></div>');
render(null, scratch);
expect(scratch.innerHTML).to.equal('');
});
});
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "preact",
"amdName": "preact",
"version": "10.19.4",
"version": "10.19.5",
"private": false,
"description": "Fast 3kb React-compatible Virtual DOM library.",
"main": "dist/preact.js",
1 change: 1 addition & 0 deletions src/component.js
Original file line number Diff line number Diff line change
@@ -143,6 +143,7 @@ function renderComponent(component, commitQueue, refQueue) {
refQueue
);

newVNode._original = oldVNode._original;
newVNode._parent._children[newVNode._index] = newVNode;

newVNode._nextDom = undefined;
10 changes: 6 additions & 4 deletions src/diff/children.js
Original file line number Diff line number Diff line change
@@ -62,7 +62,6 @@ export function diffChildren(

for (i = 0; i < newChildrenLength; i++) {
childVNode = newParentVNode._children[i];

if (
childVNode == null ||
typeof childVNode == 'boolean' ||
@@ -231,11 +230,15 @@ function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) {
// Handle unmounting null placeholders, i.e. VNode => null in unkeyed children
if (childVNode == null) {
oldVNode = oldChildren[i];
if (oldVNode && oldVNode.key == null && oldVNode._dom) {
if (
oldVNode &&
oldVNode.key == null &&
oldVNode._dom &&
(oldVNode._flags & MATCHED) === 0
) {
if (oldVNode._dom == newParentVNode._nextDom) {
newParentVNode._nextDom = getDomSibling(oldVNode);
}

unmount(oldVNode, oldVNode, false);

// Explicitly nullify this position in oldChildren instead of just
@@ -250,7 +253,6 @@ function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) {
oldChildren[i] = null;
remainingOldChildren--;
}

continue;
}

48 changes: 46 additions & 2 deletions src/jsx.d.ts
Original file line number Diff line number Diff line change
@@ -1996,7 +1996,7 @@ export namespace JSXInternal {
}

// All the WAI-ARIA 1.2 role attribute values from https://www.w3.org/TR/wai-aria-1.2/#role_definitions
type AriaRole =
type WAIAriaRole =
| 'alert'
| 'alertdialog'
| 'application'
@@ -2093,6 +2093,50 @@ export namespace JSXInternal {
| 'window'
| 'none presentation';

// All the Digital Publishing WAI-ARIA 1.0 role attribute values from https://www.w3.org/TR/dpub-aria-1.0/#role_definitions
type DPubAriaRole =
| 'doc-abstract'
| 'doc-acknowledgments'
| 'doc-afterword'
| 'doc-appendix'
| 'doc-backlink'
| 'doc-biblioentry'
| 'doc-bibliography'
| 'doc-biblioref'
| 'doc-chapter'
| 'doc-colophon'
| 'doc-conclusion'
| 'doc-cover'
| 'doc-credit'
| 'doc-credits'
| 'doc-dedication'
| 'doc-endnote'
| 'doc-endnotes'
| 'doc-epigraph'
| 'doc-epilogue'
| 'doc-errata'
| 'doc-example'
| 'doc-footnote'
| 'doc-foreword'
| 'doc-glossary'
| 'doc-glossref'
| 'doc-index'
| 'doc-introduction'
| 'doc-noteref'
| 'doc-notice'
| 'doc-pagebreak'
| 'doc-pagelist'
| 'doc-part'
| 'doc-preface'
| 'doc-prologue'
| 'doc-pullquote'
| 'doc-qna'
| 'doc-subtitle'
| 'doc-tip'
| 'doc-toc';

type AriaRole = WAIAriaRole | DPubAriaRole;

export interface HTMLAttributes<RefType extends EventTarget = EventTarget>
extends ClassAttributes<RefType>,
DOMAttributes<RefType>,
@@ -2419,7 +2463,7 @@ export namespace JSXInternal {
| undefined
| SignalLike<boolean | undefined>;
results?: number | undefined | SignalLike<number | undefined>;
translate?: 'yes' | 'no' | undefined | SignalLike<'yes' | 'no' | undefined>;
translate?: boolean | undefined | SignalLike<boolean | undefined>;

// RDFa Attributes
about?: string | undefined | SignalLike<string | undefined>;
44 changes: 44 additions & 0 deletions test/browser/components.test.js
Original file line number Diff line number Diff line change
@@ -340,6 +340,50 @@ describe('Components', () => {
expect(scratch.innerHTML).to.equal('<p>B</p>');
});

it('should update children props correctly in subsequent renders', () => {
let update, update2;
class Counter extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
update2 = () => {
this.setState({ counter: this.state.counter + 1 });
};
}

render({ counter }) {
if (!counter) return null;
return (
<p>
{counter}-{this.state.counter}
</p>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
update = () => {
this.setState({ counter: this.state.counter + 1 });
};
}

render() {
return <Counter counter={this.state.counter} />;
}
}

render(<App />, scratch);
expect(scratch.innerHTML).to.equal('');

update2();
rerender();
update();
rerender();
expect(scratch.innerHTML).to.equal('<p>1-1</p>');
});

it("should render components that don't pass args into the Component constructor (unistore pattern)", () => {
// Pattern unistore uses for connect: https://github.com/developit/unistore/blob/1df7cf60ac6fa1a70859d745fbaea7ea3f1b8d30/src/integrations/preact.js#L23
function Wrapper() {
58 changes: 58 additions & 0 deletions test/browser/render.test.js
Original file line number Diff line number Diff line change
@@ -130,6 +130,16 @@ describe('render()', () => {
expect(scratch.firstChild).to.have.property('nodeName', 'X-BAR');
});

it('should support the translate attribute w/ false as a boolean', () => {
render(<b translate={false}>Bold</b>, scratch);
expect(scratch.innerHTML).to.equal('<b translate="no">Bold</b>');
});

it('should support the translate attribute w/ true as a boolean', () => {
render(<b translate>Bold</b>, scratch);
expect(scratch.innerHTML).to.equal('<b translate="yes">Bold</b>');
});

it('should support the form attribute', () => {
render(
<div>
@@ -1298,4 +1308,52 @@ describe('render()', () => {
expect(divs[1].hasAttribute('role')).to.equal(false);
expect(divs[2].hasAttribute('role')).to.equal(false);
});

it('should not crash or repeatedly add the same child when replacing a matched vnode with null', () => {
const B = () => <div>B</div>;

let update;
class App extends Component {
constructor(props) {
super(props);
this.state = { show: true };
update = () => {
this.setState(state => ({ show: !state.show }));
};
}

render() {
if (this.state.show) {
return (
<div>
<B />
<div />
</div>
);
}
return (
<div>
<div />
{null}
<B />
</div>
);
}
}

render(<App />, scratch);
expect(scratch.innerHTML).to.equal('<div><div>B</div><div></div></div>');

update();
rerender();
expect(scratch.innerHTML).to.equal('<div><div></div><div>B</div></div>');

update();
rerender();
expect(scratch.innerHTML).to.equal('<div><div>B</div><div></div></div>');

update();
rerender();
expect(scratch.innerHTML).to.equal('<div><div></div><div>B</div></div>');
});
});