blob: 9b0615df92c000c0ede452042ce5779fcdb24a06 [file] [log] [blame]
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
// Check if a mithril component vnode has children
export function hasChildren<T>({children}: m.Vnode<T>): boolean {
return (
Array.isArray(children) &&
children.length > 0 &&
children.some((value) => value)
);
}
// A component which simply passes through it's children.
// Can be used for having something to attach lifecycle hooks to without having
// to add an extra HTML element to the DOM.
export const Passthrough = {
view({children}: m.VnodeDOM) {
return children;
},
};
export interface GateAttrs {
open: boolean;
}
// The gate component is a wrapper which can either be open or closed.
// - When open, children are rendered inside a div where display = contents.
// - When closed, children are rendered inside a div where display = none
// Use this component when we want to conditionally render certain children,
// but we want to maintain their state.
export const Gate = {
view({attrs, children}: m.VnodeDOM<GateAttrs>) {
return m(
'',
{
style: {display: attrs.open ? 'contents' : 'none'},
},
children,
);
},
};
/**
* Utility function to pre-bind some mithril attrs of a component, and leave
* the others unbound and passed at run-time.
* Example use case: the Page API Passes to the registered page a PageAttrs,
* which is {subpage:string}. Imagine you write a MyPage component that takes
* some extra input attrs (e.g. the App object) and you want to bind them
* onActivate(). The results looks like this:
*
* interface MyPageAttrs extends PageAttrs { app: App; }
*
* class MyPage extends m.classComponent<MyPageAttrs> {... view() {...} }
*
* onActivate(app: App) {
* pages.register(... bindMithrilApps(MyPage, {app: app});
* }
*
* The return value of bindMithrilApps is a mithril component that takes in
* input only a {subpage: string} and passes down to MyPage the combination
* of pre-bound and runtime attrs, that is {subpage, app}.
*/
export function bindMithrilAttrs<BaseAttrs, Attrs>(
component: m.ComponentTypes<Attrs>,
boundArgs: Omit<Attrs, keyof BaseAttrs>,
): m.Component<BaseAttrs> {
return {
view(vnode: m.Vnode<BaseAttrs>) {
const attrs = {...vnode.attrs, ...boundArgs} as Attrs;
const emptyAttrs: m.CommonAttributes<Attrs, {}> = {}; // Keep tsc happy.
return m<Attrs, {}>(component, {...attrs, ...emptyAttrs});
},
};
}