Releases: mixpanel/panel
Fixes for special cases around root node in template
- If Snabbdom creates a new top-level element rather than patching DOMPatcher's existing element (for instance, when a
key
or tag change forces the creation of a new element), update DOMPatcher's internal element reference to point to it - If Snabbdom recycles the existing top-level element that DOMPatcher newly created, it won't fire the
insert
hook since it didn't insert any new element into the DOM, so we run the hook from DOMPatcher directly if it's been set on the root node
Contexts
"Contexts" allow Panel components to inject arbitrary class/object dependencies into all descendants at runtime, similarly to React Context ("Context provides a way to pass data through the component tree without having to pass props down manually at every level"). This is especially useful for data which needs to be shared globally.
Components can provide named "default contexts" for themselves and descendants in their config objects. Components similarly declare which contexts they need by providing their names in the contexts
config entry. Contexts can be accessed with the getContext()
method, for example for use in view templates:
get config() {
return {
// provided to all descendants, including itself
defaultContexts: {
apiCache: new SharedCache(),
moreSharedData: {foo: `bar`},
},
// used in this component
contexts: [`apiCache`, `moreSharedData`],
// calling a method on a context provided to the current component
template: () => h(`p`, {}, `A value from the API Cache: ${this.getContext('apiCache').someMethod()}`),
};
}
State-initialization fixes
- Component
state
is now initialized with thedefaultState
config value in the constructor, ensuring that default values are set even before the element is attached to the DOM - State updates within
child
components made before they enter the component tree are automatically flushed up to the shared state when the child is attached. (This now matches the existing behavior of theappState
partial shared state mechanism.)
Webpack 4 support for Hot Module Replacement
v1.5.1 1.5.1
Router unregisters listeners when Component disconnects from DOM
This will restore history.pushState
/history.replaceState
/popstate
behavior when a Component that registered route handlers is removed from the DOM. Particularly useful for test suites which create/remove apps for individual test cases.
Support for declarative HTML attributes schema
For components that accept HTML attributes, attrsSchema
lets them define names, types, and default values which will be populated automatically in the component's attrs
prop. For example:
class MyWidget extends Component {
static get attrsSchema() {
return {
...super.attrsSchema,
'closeable': `boolean`,
'complex-stuff': `json`,
'title-text': {type: `string`, default: `Hello world`},
};
}
}
// access within component code:
this.attrs.closeable // false
// also accessible within templates via $attrs:
$attrs[`complex-stuff`] // pre-parsed from JSON
This is an optional mechanism that helps standardize attribute-parsing and reduce attributeChangedCallback
boilerplate.
Add support for `getNumberAttribute()`
ProxyComponent
Adds the ProxyComponent
wrapper for switching between different component implementations at runtime, e.g.:
class MyWidget extends ProxyComponent {
getTargetElementTag() {
if (window.location.search.includes('enable_beta') {
return 'my-widget-v2';
} else {
return 'my-widget-v1';
}
}
}
Other optional component utilities such as StateController
and ControlledComponent
have been moved into lib/component-utils
, but are still exported at top level (so can still be imported like import {ProxyComponent, StateController} from 'panel';
). This will however affect consumers which import directly from panel/build/
(which is not a public API and is not recommended).
`preUpdate` and `postUpdate` callback hooks
This obviates the need to override the Component update()
method in order to run code on every state update (for instance for logging state changes). Overriding update()
potentially required explicit overrides for every component in the tree, whereas the hooks work gracefully with cascading updates. Example usage:
get config() {
return {
// ...
hooks: {
postUpdate: () => localStorage.setItem(`latestFoo`, this.state.foo),
},
};
}