diff --git a/src/client/app.jsx b/src/client/app.jsx index 38f520f..f86d2a9 100644 --- a/src/client/app.jsx +++ b/src/client/app.jsx @@ -16,7 +16,7 @@ const App = () => { const [typeRenderers] = useState(typeRenderMap()); return ( - + ); }; diff --git a/src/client/types/literal.type-renderer.jsx b/src/client/types/literal.type-renderer.jsx index 27cbad5..b04bb7f 100644 --- a/src/client/types/literal.type-renderer.jsx +++ b/src/client/types/literal.type-renderer.jsx @@ -5,8 +5,7 @@ import React from 'react'; import useTypeRenderers from '~/client/hooks/useTypeRenderers'; import propTypes from './prop-types'; -import {contextStepsList} from './steps-lists'; -import {typeFromLiteral} from './typing'; +import {stepOptions, typeFromLiteral} from './typing'; const decorate = compose( contempo(require('./literal.raw.scss')), @@ -20,21 +19,15 @@ const Literal = ({ const typeRenderers = useTypeRenderers(); const type = typeFromLiteral(value.value); const Component = typeRenderers[type]; - const stepsList = contextStepsList(context, type); - const tierOptions = Object.keys(stepsList.reduce((r, optionSteps) => { - if (!optionSteps[0] || !optionSteps[0].key) { - return r; - } - return {...r, [optionSteps[0].key]: true}; - }, {})); - tierOptions.push(''); + const options = stepOptions(context, [], 0, type); + options.push(''); return (
{Component ? : null} diff --git a/src/client/types/traversal.type-renderer.jsx b/src/client/types/traversal.type-renderer.jsx index 1a0a19b..926c138 100644 --- a/src/client/types/traversal.type-renderer.jsx +++ b/src/client/types/traversal.type-renderer.jsx @@ -5,8 +5,7 @@ import React from 'react'; import Value from '~/client/value'; import propTypes from './prop-types'; -import {contextStepsList} from './steps-lists'; -import {typeFromSteps} from './typing'; +import {stepOptions, typeFromSteps} from './typing'; const decorate = compose( contempo(require('./traversal.raw.scss')), @@ -15,9 +14,10 @@ const decorate = compose( const Traversal = (props) => { const { context, + dispatchers, value: {steps, value}, } = props; - const stepsList = contextStepsList(context, typeFromSteps(context, steps)); + const stepsType = typeFromSteps(context, steps); return (
{steps.map((step, i) => ( @@ -27,27 +27,14 @@ const Traversal = (props) => { (() => { switch (step.type) { case 'key': { - const tierOptions = Object.keys(stepsList.reduce((r, optionSteps) => { - if (!optionSteps[i] || !optionSteps[i].key) { - return r; - } - for (let j = 0; j < i; ++j) { - if (steps[j].key !== optionSteps[j].key) { - return r; - } - } - return {...r, [optionSteps[i].key]: true}; - }, {})); - if (0 === i) { - tierOptions.push(''); - } + const options = stepOptions(context, steps, i, stepsType); return ( - - { - tierOptions + options .sort((l, r) => (l.toLowerCase() < r.toLowerCase() ? -1 : 1)) - .map((tierOption) => ) + .map((option) => ) } @@ -61,7 +48,11 @@ const Traversal = (props) => {
))} @@ -79,7 +70,11 @@ const Traversal = (props) => { value && ( = - + ) } @@ -92,6 +87,6 @@ Traversal.propTypes = { }; export default { - type: '-traversal', + type: 'traversal', Component: decorate(Traversal), }; diff --git a/src/client/types/typing.js b/src/client/types/typing.js index 2a84abf..f197199 100644 --- a/src/client/types/typing.js +++ b/src/client/types/typing.js @@ -1,5 +1,9 @@ import {Context} from '@avocado/behavior'; +let digraph; +// eslint-disable-next-line no-return-assign +const cachedDigraph = () => (digraph || (digraph = Context.allTypesInvertedDigraph())); + export function isBehaviorItem(valueOrItem) { if ('object' !== typeof valueOrItem) { return false; @@ -42,27 +46,60 @@ export function typeFromValue(v) { return isBehaviorItem(v) ? v.type : undefined; } -export function typeFromSteps(context, steps) { - if (!steps || 0 === steps.length) { - return 'undefined'; +const stepTo = ([variable, type], step) => { + const {key} = step; + if (key) { + const description = Context.typeDescription(type, variable); + return [ + 'object' === typeof variable ? variable[key] : undefined, + description[key] ? description[key].type : type, + ]; } - const [, finalType] = steps.slice(1).reduce( - ([v, type], step) => { - const {key} = step; - if (key) { - const description = Context.typeDescription(type, v); - return [ - 'object' === typeof v ? v[key] : undefined, - description[key] ? description[key].type : type, - ]; - } - return [v, type]; - }, - context.get(steps[0].key), - ); - return finalType; + return [variable, type]; +}; + +export function descriptionFromSteps(context, steps) { + if (!steps || 0 === steps.length) { + return {}; + } + const [variable, type] = steps.slice(1).reduce(stepTo, context.get(steps[0].key)); + return Context.typeDescription(type, variable); } export function typeFits(reference, candidate) { return 'any' === reference || -1 !== (reference.split('|') || []).indexOf(candidate); } + +export function stepOptions(context, steps, i, type) { + let description; + if (0 === i) { + description = Object.entries(context.all()) + .reduce((r, [key, tuple]) => ({...r, [key]: {type: tuple[1]}}), {}); + } + else { + description = descriptionFromSteps(context, steps.slice(0, i)); + } + let options; + if ('any' !== type) { + const inverted = cachedDigraph(); + const candidates = (inverted[type] || []).concat(type); + options = Object.entries(description) + .reduce((r, [key, spec]) => ( + candidates.find((candidate) => typeFits(candidate, spec.type)) + ? r.concat(key) + : r + ), []); + } + else { + options = Object.keys(description); + } + return options; +} + +export function typeFromSteps(context, steps) { + if (!steps || 0 === steps.length) { + return 'undefined'; + } + const [, type] = steps.slice(1).reduce(stepTo, context.get(steps[0].key)); + return type; +}